diff --git a/HandyControl/Controls/Base/SelectableItem.cs b/HandyControl/Controls/Base/SelectableItem.cs index 2556a181..a458f0ab 100644 --- a/HandyControl/Controls/Base/SelectableItem.cs +++ b/HandyControl/Controls/Base/SelectableItem.cs @@ -34,31 +34,33 @@ namespace HandyControl.Controls if (!IsSelected) { IsSelected = true; - RaiseEvent(new RoutedEventArgs(SelectedEvent, this)); + OnSelected(new RoutedEventArgs(SelectedEvent, this)); } else if (CanDeselect) { IsSelected = false; - RaiseEvent(new RoutedEventArgs(DeselectedEvent, this)); + OnSelected(new RoutedEventArgs(DeselectedEvent, this)); } } else { if (CanDeselect) { - RaiseEvent(IsSelected + OnSelected(IsSelected ? new RoutedEventArgs(DeselectedEvent, this) : new RoutedEventArgs(SelectedEvent, this)); } else { - RaiseEvent(new RoutedEventArgs(SelectedEvent, this)); + OnSelected(new RoutedEventArgs(SelectedEvent, this)); } } _isMouseLeftButtonDown = false; } } + protected virtual void OnSelected(RoutedEventArgs e) => RaiseEvent(e); + public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register( "IsSelected", typeof(bool), typeof(SelectableItem), new PropertyMetadata(ValueBoxes.FalseBox)); diff --git a/HandyControl/Controls/Other/ChatBubble.cs b/HandyControl/Controls/Other/ChatBubble.cs index 1ef28aa1..353ae9b1 100644 --- a/HandyControl/Controls/Other/ChatBubble.cs +++ b/HandyControl/Controls/Other/ChatBubble.cs @@ -1,10 +1,10 @@ -using System.Windows; -using System.Windows.Controls; +using System; +using System.Windows; using HandyControl.Data; namespace HandyControl.Controls { - public class ChatBubble : ContentControl + public class ChatBubble : SelectableItem { public static readonly DependencyProperty RoleProperty = DependencyProperty.Register( "Role", typeof(ChatRoleType), typeof(ChatBubble), new PropertyMetadata(default(ChatRoleType))); @@ -14,5 +14,39 @@ namespace HandyControl.Controls get => (ChatRoleType) GetValue(RoleProperty); set => SetValue(RoleProperty, value); } + + public static readonly DependencyProperty TypeProperty = DependencyProperty.Register( + "Type", typeof(ChatMessageType), typeof(ChatBubble), new PropertyMetadata(default(ChatMessageType))); + + public ChatMessageType Type + { + get => (ChatMessageType) GetValue(TypeProperty); + set => SetValue(TypeProperty, value); + } + + public static readonly DependencyProperty IsReadProperty = DependencyProperty.Register( + "IsRead", typeof(bool), typeof(ChatBubble), new PropertyMetadata(ValueBoxes.FalseBox)); + + public bool IsRead + { + get => (bool) GetValue(IsReadProperty); + set => SetValue(IsReadProperty, value); + } + + public static void SetMaxWidth(DependencyObject element, double value) + => element.SetValue(MaxWidthProperty, value); + + public static double GetMaxWidth(DependencyObject element) + => (double) element.GetValue(MaxWidthProperty); + + public Action ReadAction { get; set; } + + protected override void OnSelected(RoutedEventArgs e) + { + base.OnSelected(e); + + IsRead = true; + ReadAction?.Invoke(Content); + } } } \ No newline at end of file diff --git a/HandyControl/Controls/Window/MessageBox.cs b/HandyControl/Controls/Window/MessageBox.cs index b2e5400b..9e755973 100644 --- a/HandyControl/Controls/Window/MessageBox.cs +++ b/HandyControl/Controls/Window/MessageBox.cs @@ -289,7 +289,7 @@ namespace HandyControl.Controls throw new InvalidEnumArgumentException(nameof(defaultResult), (int)defaultResult, typeof(MessageBoxResult)); } - var ownerWindow = owner ?? Application.Current.Windows.OfType().SingleOrDefault(x => x.IsActive); + var ownerWindow = owner ?? VisualHelper.GetActiveWindow(); var ownerIsNull = ownerWindow is null; return new MessageBox diff --git a/HandyControl/Data/Enum/ChatMessageType.cs b/HandyControl/Data/Enum/ChatMessageType.cs new file mode 100644 index 00000000..47d2f58b --- /dev/null +++ b/HandyControl/Data/Enum/ChatMessageType.cs @@ -0,0 +1,10 @@ +namespace HandyControl.Data +{ + public enum ChatMessageType + { + String, + Image, + Audio, + Custom + } +} \ No newline at end of file diff --git a/HandyControl/HandyControl.csproj b/HandyControl/HandyControl.csproj index 24c5dc99..9d037710 100644 --- a/HandyControl/HandyControl.csproj +++ b/HandyControl/HandyControl.csproj @@ -157,6 +157,7 @@ + diff --git a/HandyControl/Themes/Styles/Base/ChatBubbleBaseStyle.xaml b/HandyControl/Themes/Styles/Base/ChatBubbleBaseStyle.xaml index 8b9853f7..0bfd9542 100644 --- a/HandyControl/Themes/Styles/Base/ChatBubbleBaseStyle.xaml +++ b/HandyControl/Themes/Styles/Base/ChatBubbleBaseStyle.xaml @@ -3,48 +3,134 @@ xmlns:controls="clr-namespace:HandyControl.Controls"> + - + - - - - - - - - - - - + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/HandyControl/Themes/Styles/Carousel.xaml b/HandyControl/Themes/Styles/Carousel.xaml index 2443404e..04e1a78e 100644 --- a/HandyControl/Themes/Styles/Carousel.xaml +++ b/HandyControl/Themes/Styles/Carousel.xaml @@ -9,7 +9,22 @@ - + + + + + + + + - - + - - - + + - + + + + + + + + + + + + \ No newline at end of file diff --git a/HandyControl/Tools/Helper/VisualHelper.cs b/HandyControl/Tools/Helper/VisualHelper.cs index 972318ec..115a74c8 100644 --- a/HandyControl/Tools/Helper/VisualHelper.cs +++ b/HandyControl/Tools/Helper/VisualHelper.cs @@ -4,9 +4,9 @@ using System.Windows.Media; namespace HandyControl.Tools { - internal class VisualHelper + public class VisualHelper { - public static VisualStateGroup TryGetVisualStateGroup(DependencyObject dependencyObject, string groupName) + internal static VisualStateGroup TryGetVisualStateGroup(DependencyObject dependencyObject, string groupName) { var root = GetImplementationRoot(dependencyObject); if (root == null) @@ -20,11 +20,13 @@ namespace HandyControl.Tools .FirstOrDefault(group => string.CompareOrdinal(groupName, group.Name) == 0); } - public static FrameworkElement GetImplementationRoot(DependencyObject dependencyObject) + internal static FrameworkElement GetImplementationRoot(DependencyObject dependencyObject) { return 1 == VisualTreeHelper.GetChildrenCount(dependencyObject) ? VisualTreeHelper.GetChild(dependencyObject, 0) as FrameworkElement : null; } + + public static Window GetActiveWindow() => Application.Current.Windows.OfType().SingleOrDefault(x => x.IsActive); } } \ No newline at end of file diff --git a/HandyControlDemo/Data/AppConfig.cs b/HandyControlDemo/Data/AppConfig.cs index 1768fb96..0084a625 100644 --- a/HandyControlDemo/Data/AppConfig.cs +++ b/HandyControlDemo/Data/AppConfig.cs @@ -5,7 +5,7 @@ namespace HandyControlDemo.Data { internal class AppConfig { - public static string SavePath = $"{AppDomain.CurrentDomain.BaseDirectory}AppConfig.json"; + public static readonly string SavePath = $"{AppDomain.CurrentDomain.BaseDirectory}AppConfig.json"; public string Lang { get; set; } = "zh-cn"; diff --git a/HandyControlDemo/Data/GlobalData.cs b/HandyControlDemo/Data/GlobalData.cs index 0846ca13..9c0f9f69 100644 --- a/HandyControlDemo/Data/GlobalData.cs +++ b/HandyControlDemo/Data/GlobalData.cs @@ -1,5 +1,4 @@ -using System; -using System.IO; +using System.IO; using Newtonsoft.Json; namespace HandyControlDemo.Data diff --git a/HandyControlDemo/Data/MessageToken.cs b/HandyControlDemo/Data/MessageToken.cs index 5cebeecb..1669743c 100644 --- a/HandyControlDemo/Data/MessageToken.cs +++ b/HandyControlDemo/Data/MessageToken.cs @@ -146,11 +146,7 @@ public static readonly string ClearLeftSelected = nameof(ClearLeftSelected); - public static readonly string SendChatString = nameof(SendChatString); - - public static readonly string SendChatImage = nameof(SendChatImage); - - public static readonly string SendChatAudio = nameof(SendChatAudio); + public static readonly string SendChatMessage = nameof(SendChatMessage); } } \ No newline at end of file diff --git a/HandyControlDemo/Data/MessageToken.tt b/HandyControlDemo/Data/MessageToken.tt index 7155e49d..f25d34ce 100644 --- a/HandyControlDemo/Data/MessageToken.tt +++ b/HandyControlDemo/Data/MessageToken.tt @@ -85,9 +85,7 @@ var messageList = new List "FullSwitch", "ContributorsView", "ClearLeftSelected", - "SendChatString", - "SendChatImage", - "SendChatAudio" + "SendChatMessage" }; #> namespace HandyControlDemo.Data diff --git a/HandyControlDemo/Data/Model/ChatInfoModel.cs b/HandyControlDemo/Data/Model/ChatInfoModel.cs index 9e3b795f..92b4e3a1 100644 --- a/HandyControlDemo/Data/Model/ChatInfoModel.cs +++ b/HandyControlDemo/Data/Model/ChatInfoModel.cs @@ -1,9 +1,17 @@ -namespace HandyControlDemo.Data +using HandyControl.Data; + +namespace HandyControlDemo.Data { - public class ChatInfoModel + public struct ChatInfoModel { - public string Message { get; set; } + public object Message { get; set; } public string SenderId { get; set; } + + public ChatRoleType Role { get; set; } + + public ChatMessageType Type { get; set; } + + public object Enclosure { get; set; } } } \ No newline at end of file diff --git a/HandyControlDemo/HandyControlDemo.csproj b/HandyControlDemo/HandyControlDemo.csproj index a26d040d..7dbb4f5e 100644 --- a/HandyControlDemo/HandyControlDemo.csproj +++ b/HandyControlDemo/HandyControlDemo.csproj @@ -118,9 +118,10 @@ Lang.en.resx - - + + + Avatar.xaml @@ -836,5 +837,6 @@ HandyControl + \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.Designer.cs b/HandyControlDemo/Properties/Langs/Lang.Designer.cs index b16f70c6..5c2fd6c9 100644 --- a/HandyControlDemo/Properties/Langs/Lang.Designer.cs +++ b/HandyControlDemo/Properties/Langs/Lang.Designer.cs @@ -915,6 +915,15 @@ namespace HandyControlDemo.Properties.Langs { } } + /// + /// 查找类似 秒 的本地化字符串。 + /// + public static string Second { + get { + return ResourceManager.GetString("Second", resourceCulture); + } + } + /// /// 查找类似 选中 的本地化字符串。 /// diff --git a/HandyControlDemo/Properties/Langs/Lang.en.resx b/HandyControlDemo/Properties/Langs/Lang.en.resx index 17285912..966dc242 100644 --- a/HandyControlDemo/Properties/Langs/Lang.en.resx +++ b/HandyControlDemo/Properties/Langs/Lang.en.resx @@ -492,4 +492,7 @@ Push to talk + + Sec + \ No newline at end of file diff --git a/HandyControlDemo/Properties/Langs/Lang.resx b/HandyControlDemo/Properties/Langs/Lang.resx index 2239e620..88e899a7 100644 --- a/HandyControlDemo/Properties/Langs/Lang.resx +++ b/HandyControlDemo/Properties/Langs/Lang.resx @@ -492,4 +492,7 @@ 按住说话 + + 秒 + \ No newline at end of file diff --git a/HandyControlDemo/Resources/Img/Chat/chat_back1.jpg b/HandyControlDemo/Resources/Img/Chat/chat_back1.jpg index 45672774..929b4d63 100644 Binary files a/HandyControlDemo/Resources/Img/Chat/chat_back1.jpg and b/HandyControlDemo/Resources/Img/Chat/chat_back1.jpg differ diff --git a/HandyControlDemo/Resources/Img/Chat/chat_back2.jpg b/HandyControlDemo/Resources/Img/Chat/chat_back2.jpg index f7120af2..c0938954 100644 Binary files a/HandyControlDemo/Resources/Img/Chat/chat_back2.jpg and b/HandyControlDemo/Resources/Img/Chat/chat_back2.jpg differ diff --git a/HandyControlDemo/Tools/AssemblyHelper.cs b/HandyControlDemo/Tools/Helper/AssemblyHelper.cs similarity index 100% rename from HandyControlDemo/Tools/AssemblyHelper.cs rename to HandyControlDemo/Tools/Helper/AssemblyHelper.cs diff --git a/HandyControlDemo/Tools/CommonHelper.cs b/HandyControlDemo/Tools/Helper/CommonHelper.cs similarity index 100% rename from HandyControlDemo/Tools/CommonHelper.cs rename to HandyControlDemo/Tools/Helper/CommonHelper.cs diff --git a/HandyControlDemo/Tools/Helper/ExternDllHelper.cs b/HandyControlDemo/Tools/Helper/ExternDllHelper.cs new file mode 100644 index 00000000..a5d0e53d --- /dev/null +++ b/HandyControlDemo/Tools/Helper/ExternDllHelper.cs @@ -0,0 +1,12 @@ +using System.Runtime.InteropServices; + +namespace HandyControlDemo.Tools +{ + internal class ExternDllHelper + { + private const string Winmm = "winmm.dll"; + + [DllImport(Winmm, EntryPoint = "mciSendString", CharSet = CharSet.Auto)] + public static extern int MciSendString(string lpstrCommand, string lpstrReturnString, int uReturnLength, int hwndCallback); + } +} \ No newline at end of file diff --git a/HandyControlDemo/UserControl/Basic/ChatBox.xaml b/HandyControlDemo/UserControl/Basic/ChatBox.xaml index a9c78463..c0141b01 100644 --- a/HandyControlDemo/UserControl/Basic/ChatBox.xaml +++ b/HandyControlDemo/UserControl/Basic/ChatBox.xaml @@ -5,8 +5,7 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:controls="clr-namespace:HandyControl.Controls;assembly=HandyControl" xmlns:langs="clr-namespace:HandyControlDemo.Properties.Langs" - xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity" - xmlns:command="http://www.galasoft.ch/mvvmlight" + xmlns:interactivity="clr-namespace:HandyControl.Interactivity;assembly=HandyControl" mc:Ignorable="d" Height="486" Effect="{StaticResource EffectShadow5}" @@ -18,13 +17,18 @@ - - - - - - - + + + + + + + + + + + + @@ -34,14 +38,23 @@ diff --git a/HandyControlDemo/UserControl/Main/ContributorsView.xaml b/HandyControlDemo/UserControl/Main/ContributorsView.xaml index 198f0dfe..59020930 100644 --- a/HandyControlDemo/UserControl/Main/ContributorsView.xaml +++ b/HandyControlDemo/UserControl/Main/ContributorsView.xaml @@ -4,7 +4,7 @@ xmlns:userControl="clr-namespace:HandyControlDemo.UserControl" DataContext="{Binding ContributorsView,Source={StaticResource Locator}}" Background="{DynamicResource SecondaryRegionBrush}"> - + diff --git a/HandyControlDemo/ViewModel/Basic/ChatBoxViewModel.cs b/HandyControlDemo/ViewModel/Basic/ChatBoxViewModel.cs index 65314d57..53b2cb21 100644 --- a/HandyControlDemo/ViewModel/Basic/ChatBoxViewModel.cs +++ b/HandyControlDemo/ViewModel/Basic/ChatBoxViewModel.cs @@ -1,38 +1,42 @@ using System; +using System.Collections.ObjectModel; +using System.Diagnostics; using System.IO; +using System.Media; +using System.Windows; +using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Media.Imaging; using GalaSoft.MvvmLight; using GalaSoft.MvvmLight.CommandWpf; using GalaSoft.MvvmLight.Messaging; +using HandyControl.Controls; +using HandyControl.Data; +using HandyControl.Tools; using HandyControlDemo.Data; +using HandyControlDemo.Tools; using Microsoft.Win32; namespace HandyControlDemo.ViewModel { public class ChatBoxViewModel : ViewModelBase { + private static readonly string AutioCachePath = $"{AppDomain.CurrentDomain.BaseDirectory}Cache"; + private readonly string _id = Guid.NewGuid().ToString(); + private readonly Stopwatch _stopwatch = new Lazy(() => new Stopwatch()).Value; + public ChatBoxViewModel() { - Messenger.Default.Register(this, MessageToken.SendChatString, ReceiveString); - Messenger.Default.Register(this, MessageToken.SendChatImage, ReceiveImage); - Messenger.Default.Register(this, MessageToken.SendChatAudio, ReceiveAudio); + Messenger.Default.Register(this, MessageToken.SendChatMessage, ReceiveMessage); } - private void ReceiveString(ChatInfoModel info) + private void ReceiveMessage(ChatInfoModel info) { - if (!_id.Equals(info.SenderId)) return; - } - - private void ReceiveImage(ChatInfoModel info) - { - if (!_id.Equals(info.SenderId)) return; - } - - private void ReceiveAudio(ChatInfoModel info) - { - if (!_id.Equals(info.SenderId)) return; + if (_id.Equals(info.SenderId)) return; + info.Role = ChatRoleType.Receiver; + ChatInfos.Add(info); } private string _chatString; @@ -43,6 +47,8 @@ namespace HandyControlDemo.ViewModel set => Set(ref _chatString, value); } + public ObservableCollection ChatInfos { get; set; } = new ObservableCollection(); + public RelayCommand SendStringCmd => new Lazy>(() => new RelayCommand(SendString)).Value; @@ -51,15 +57,94 @@ namespace HandyControlDemo.ViewModel if (e.Key == Key.Enter) { if (string.IsNullOrEmpty(ChatString)) return; - Messenger.Default.Send(new ChatInfoModel + var info = new ChatInfoModel { Message = ChatString, - SenderId = _id - }, MessageToken.SendChatString); + SenderId = _id, + Type = ChatMessageType.String, + Role = ChatRoleType.Sender + }; + ChatInfos.Add(info); + Messenger.Default.Send(info, MessageToken.SendChatMessage); ChatString = string.Empty; } } + public RelayCommand ReadMessageCmd => new Lazy>(() => + new RelayCommand(ReadMessage)).Value; + + private void ReadMessage(RoutedEventArgs e) + { + if (e.OriginalSource is FrameworkElement element && element.Tag is ChatInfoModel info) + { + if (info.Type == ChatMessageType.Image) + { + new ImageBrowser(new Uri(info.Enclosure.ToString())) + { + Owner = VisualHelper.GetActiveWindow() + }.Show(); + } + else if (info.Type == ChatMessageType.Audio) + { + var player = new SoundPlayer(info.Enclosure.ToString()); + player.PlaySync(); + } + } + } + + public RelayCommand StartRecordCmd => new Lazy(() => + new RelayCommand(StartRecord)).Value; + + private void StartRecord() + { + ExternDllHelper.MciSendString("set wave bitpersample 8", "", 0, 0); + ExternDllHelper.MciSendString("set wave samplespersec 20000", "", 0, 0); + ExternDllHelper.MciSendString("set wave channels 2", "", 0, 0); + ExternDllHelper.MciSendString("set wave format tag pcm", "", 0, 0); + ExternDllHelper.MciSendString("open new type WAVEAudio alias movie", "", 0, 0); + ExternDllHelper.MciSendString("record movie", "", 0, 0); + + _stopwatch.Reset(); + _stopwatch.Start(); + } + + public RelayCommand StopRecordCmd => new Lazy(() => + new RelayCommand(StopRecord)).Value; + + private void StopRecord() + { + if (!Directory.Exists(AutioCachePath)) + { + try + { + Directory.CreateDirectory(AutioCachePath); + } + catch (Exception e) + { + Growl.Error(e.Message); + return; + } + } + + var cachePath = $"{AutioCachePath}\\{Guid.NewGuid().ToString()}"; + ExternDllHelper.MciSendString("stop movie", "", 0, 0); + ExternDllHelper.MciSendString($"save movie {cachePath}", "", 0, 0); + ExternDllHelper.MciSendString("close movie", "", 0, 0); + + _stopwatch.Stop(); + + var info = new ChatInfoModel + { + Message = $"{_stopwatch.Elapsed.Seconds.ToString()} {Properties.Langs.Lang.Second}", + SenderId = _id, + Type = ChatMessageType.Audio, + Role = ChatRoleType.Sender, + Enclosure = cachePath + }; + ChatInfos.Add(info); + Messenger.Default.Send(info, MessageToken.SendChatMessage); + } + public RelayCommand OpenImageCmd => new Lazy(() => new RelayCommand(OpenImage)).Value; @@ -71,11 +156,16 @@ namespace HandyControlDemo.ViewModel var fileName = dialog.FileName; if (File.Exists(fileName)) { - Messenger.Default.Send(new ChatInfoModel + var info = new ChatInfoModel { - Message = fileName, - SenderId = _id - }, MessageToken.SendChatImage); + Message = BitmapFrame.Create(new Uri(fileName)), + SenderId = _id, + Type = ChatMessageType.Image, + Role = ChatRoleType.Sender, + Enclosure = fileName + }; + ChatInfos.Add(info); + Messenger.Default.Send(info, MessageToken.SendChatMessage); } } } diff --git a/HandyControlDemo/ViewModel/ViewModelLocator.cs b/HandyControlDemo/ViewModel/ViewModelLocator.cs index cda8e343..f49a8a61 100644 --- a/HandyControlDemo/ViewModel/ViewModelLocator.cs +++ b/HandyControlDemo/ViewModel/ViewModelLocator.cs @@ -46,7 +46,7 @@ namespace HandyControlDemo.ViewModel public PaginationDemoViewModel PaginationDemo => ServiceLocator.Current.GetInstance(); - public ChatBoxViewModel ChatBox => ServiceLocator.Current.GetInstance(); + public ChatBoxViewModel ChatBox => new ChatBoxViewModel(); #endregion } diff --git a/README.md b/README.md index 32f1721d..4d620e23 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,10 @@ ## Latest examples +### ChatBubble + +![ChatBubble](https://raw.githubusercontent.com/NaBian/HandyControl/master/Resources/ChatBubble.png) + ### Transfer ![Transfer](https://raw.githubusercontent.com/NaBian/HandyControl/master/Resources/Transfer.png) @@ -26,12 +30,12 @@ ![CoverView](https://raw.githubusercontent.com/NaBian/HandyControl/master/Resources/CoverView.gif) +## History publication + ### WaterfallPanel ![WaterfallPanel](https://raw.githubusercontent.com/NaBian/HandyControl/master/Resources/WaterfallPanel.png) -## History publication - ### Rate ![Rate](https://raw.githubusercontent.com/NaBian/HandyControl/master/Resources/Rate.png) diff --git a/Resources/ChatBubble.png b/Resources/ChatBubble.png new file mode 100644 index 00000000..f65a021f Binary files /dev/null and b/Resources/ChatBubble.png differ