最近公司有个项目,是要求实现类似 QQ 聊天这种功能的。
如下图
这没啥难的,稍微复杂的也就表情的解析而已。
表情在传输过程中的实现参考了新浪微博,采用半角中括号代表表情的方式。例如:“abc[doge]def”就会显示 abc,然后一个,再 def。
于是动手就干。
创建一个模板控件来进行封装,我就叫它 ChatMessageControl,有一个属性 Text,表示消息内容。内部使用一个 TextBlock 来实现。
于是博主三下五除二就写出了以下代码:
C#
[TemplatePart(Name = TextBlockTemplateName, Type = typeof(TextBlock))] public class ChatMessageControl : Control { public static readonly DependencyProperty TextProperty = DependencyProperty.Register(nameof(Text), typeof(string), typeof(ChatMessageControl), new PropertyMetadata(default(string), OnTextChanged)); private const string TextBlockTemplateName = "PART_TextBlock"; private static readonly Dictionary<string, string> Emotions = new Dictionary<string, string> { ["doge"] = "pack://application:,,,/WpfQQChat;component/Images/doge.png", ["喵喵"] = "pack://application:,,,/WpfQQChat;component/Images/喵喵.png" }; private TextBlock _textBlock; static ChatMessageControl() { DefaultStyleKeyProperty.OverrideMetadata(typeof(ChatMessageControl), new FrameworkPropertyMetadata(typeof(ChatMessageControl))); } public string Text { get => (string)GetValue(TextProperty); set => SetValue(TextProperty, value); } public override void OnApplyTemplate() { _textBlock = (TextBlock)GetTemplateChild(TextBlockTemplateName); UpdateVisual(); } private static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { var obj = (ChatMessageControl)d; obj.UpdateVisual(); } private void UpdateVisual() { if (_textBlock == null) { return; } _textBlock.Inlines.Clear(); var buffer = new StringBuilder(); foreach (var c in Text) { switch (c) { case '[': _textBlock.Inlines.Add(buffer.ToString()); buffer.Clear(); buffer.Append(c); break; case ']': var current = buffer.ToString(); if (current.StartsWith("[")) { var emotionName = current.Substring(1); if (Emotions.ContainsKey(emotionName)) { var image = new Image { Width = 16, Height = 16, Source = new BitmapImage(new Uri(Emotions[emotionName])) }; _textBlock.Inlines.Add(new InlineUIContainer(image)); buffer.Clear(); continue; } } buffer.Append(c); _textBlock.Inlines.Add(buffer.ToString()); buffer.Clear(); break; default: buffer.Append(c); break; } } _textBlock.Inlines.Add(buffer.ToString()); } }
因为这篇博文只是个演示,这里博主就只放两个表情好了,并且耦合在这个控件里。
XAML
<Style TargetType="local:ChatMessageControl"> <Setter Property="Template"> <Setter.Value> <ControlTemplate TargetType="local:ChatMessageControl"> <TextBlock x:Name="PART_TextBlock" TextWrapping="Wrap" /> </ControlTemplate> </Setter.Value> </Setter> </Style>
没啥好说的,就是包了一层而已。
效果:
自我感觉良好,于是乎博主就提交代码,发了个版本到测试环境了。
但是,第二天,测试却给博主提了个 bug。消息无法选择、复制。
在 UWP 里,TextBlock 控件是有 IsTextSelectionEnabled 属性的,然而 WPF 并没有。这下头大了,于是博主去查了一下 StackOverflow,大佬们回答都是说用一个 IsReadOnly 为 True 的 TextBox 来实现。因为我这里包含了表情,所以用 RichTextBox 来实现吧。不管行不行,先试试再说。
在原来的代码上修改一下,反正表情解析一样的,但这里博主为了方便写 blog,就新开一个控件好了。
C#