WPF仿QQ聊天框表情文字混排实现

2017-06-06 19:09:45来源:CSDN作者:leebin_20人点击

二话不说。先上图
这里写图片描述

图中分别有文件、文本+表情、纯文本的展示,对于同一个list不同的展示形式,很明显,应该用多个DataTemplate,那么也就需要DataTemplateSelector了:

class MessageDataTemplateSelector : DataTemplateSelector    {        public override System.Windows.DataTemplate SelectTemplate(object item, System.Windows.DependencyObject container)        {            Window win = Application.Current.MainWindow;            var myUserNo = UserLoginInfo.GetInstance().UserNo;            if (item!=null)            {                NIMIMMessage m = item as NIMIMMessage;                if (m.SenderID==myUserNo)                {                    switch (m.MessageType)                    {                        case NIMMessageType.kNIMMessageTypeAudio:                        case NIMMessageType.kNIMMessageTypeVideo:                            return win.FindResource("self_media") as DataTemplate;                        case NIMMessageType.kNIMMessageTypeFile:                            return win.FindResource("self_file") as DataTemplate;                        case NIMMessageType.kNIMMessageTypeImage:                            return win.FindResource("self_image") as DataTemplate;                        case NIMMessageType.kNIMMessageTypeText:                            return win.FindResource("self_text") as DataTemplate;                        default:                            break;                    }                }                else                {                    switch (m.MessageType)                    {                        case NIMMessageType.kNIMMessageTypeAudio:                        case NIMMessageType.kNIMMessageTypeVideo:                            return win.FindResource("friend_media") as DataTemplate;                        case NIMMessageType.kNIMMessageTypeFile:                            return win.FindResource("friend_file") as DataTemplate;                        case NIMMessageType.kNIMMessageTypeImage:                            return win.FindResource("friend_image") as DataTemplate;                        case NIMMessageType.kNIMMessageTypeText:                            return win.FindResource("friend_text") as DataTemplate;                        default:                            break;                    }                }            }            return null;        }    }

以上一共有8个DateTemplate,friend和self的区别就在于一个在左一个在右,我这边就放friend_text的样式代码好了,因为本篇主要说的是表情和文字的混排:

<Window.Resources><DataTemplate x:Key="friend_text">            <Grid Margin="12 6">                <Grid.ColumnDefinitions>                    <ColumnDefinition Width="32"/>                    <ColumnDefinition Width="*"/>                </Grid.ColumnDefinitions>                <Image Source="{Binding TalkID}"  HorizontalAlignment="Center" VerticalAlignment="Center">                    <Image.Clip>                        <EllipseGeometry RadiusX="16" RadiusY="16" Center="16 16"/>                    </Image.Clip>                </Image>                <Grid HorizontalAlignment="Left" Grid.Column="1"  Background="Transparent" VerticalAlignment="Center" Margin="12 0 0 0">                    <Border CornerRadius="8" Background="#F0F0F0" Padding="6" >                        <xctk:RichTextBox FontSize="14"                                          VerticalScrollBarVisibility="Auto"                                          HorizontalScrollBarVisibility="Disabled"                                          Text="{Binding TextContent,Converter={StaticResource ShowImageOrTextConverter}}"                                          VerticalAlignment="Center"                                          BorderThickness="0"                                          IsReadOnly="True"                                          Background="Transparent">                            <FlowDocument Name="rtbFlowDoc" PageWidth="{Binding MessageWidth}"/>                            <xctk:RichTextBox.TextFormatter>                                 <xctk:XamlFormatter/>                            </xctk:RichTextBox.TextFormatter>                        </xctk:RichTextBox>                    </Border>                </Grid>            </Grid>        </DataTemplate></Window.Resources>

以上可以看到,我们使用了RichTextBox这个控件,不过并不是原生的,而是xceed.wpf.toolkit下的,所以别忘了引入命名空间:
xmlns:xctk=”http://schemas.xceed.com/wpf/xaml/toolkit”
为什么要引用这个控件,因为它支持绑定Text -:)

上一篇已经提到过,我们的IM用的是网易云的SDK,在这个SDK里表情也是通过文本发送的,如[微笑]就代表微笑的表情。
那么问题就很明显了——怎么解析这段带有表情的文本,并且把表情显示出来。

private Text GenerateTextMessage(NIMTextMessage m, string senderId){      Text text = new Text();      text.TextContent = m.TextContent;      text.TalkID = friendHeadUrl;      if (!string.IsNullOrEmpty(senderId))      {            text.SenderID = senderId;      }      var txt = text.TextContent;      int length = 0;      if (txt.Contains("[") && txt.Contains("]"))      {            StringBuilder str = new StringBuilder();            List<EmoticonText> emoticonText = new List<EmoticonText>();            char[] chars = txt.ToCharArray();            for (int i = 0; i < chars.Length; i++)            {                char c = chars[i];                if (chars[i] == '[')                {                    emoticonText.Add(new EmoticonText { Key = "text", Value = str.ToString() });                    str.Clear();                    int f = txt.IndexOf(']', i);                    string es = txt.Substring(i, f - i + 1);                    XElement node = elementCollection.Where(a => a.Attribute("Tag").Value == es).FirstOrDefault();                    if (node == null)                    {                        str.Append(es);                        length += (f - i + 1) * 14;                    }                    else                    {                        emoticonText.Add(new EmoticonText { Key = "emoticon", Value = "../Resources/Emoticon/" + node.Attribute("File").Value });                        i = f;                        length += 32;                    }                }                else                {                    str.Append(c);                    length += 14;                }            }            text.TextContent = JsonConvert.SerializeObject(emoticonText);       }       else       {            List<EmoticonText> textStr = new List<EmoticonText>();            textStr.Add(new EmoticonText() { Key = "text", Value = txt });            text.TextContent = JsonConvert.SerializeObject(textStr);            length = txt.Length * 14;       }       length += 24;       if (length < 38 * 14)       {             text.MessageWidth = length.ToString();       }       return text;}

Text是自定义的一个实体,它包含需要绑定到xaml的属性。这里我用EmoticonText这个实体来区分是表情图片还是纯文本。另外,有的同学可能有疑问,这里的length是干嘛用的,看前面那个DataTemplate,其中PageWidth=”{Binding MessageWidth}”,所以这个length是计算当前RichTextBox宽度的,为什么要手动计算宽度呢?因为RichTextBox貌似没提供根据内容自适应宽度,如果我是用TextBox的话,其宽度就会根据其中显示内容的长短进行自适应;那为什么要乘14加28什么的呢?因为我这个是按字符个数来算宽度的,当以14为系数因子的时候,中文显示勉强满意,但是如果是纯英文或数字就不行了,这也是为什么截图里RichTextBox右边还空那么一块;最后加24是因为边距,38是一行最多显示38个中文,如果超过了38个中文还对其计算宽度的话,就会导致其不换行了。
如果有同学有自适应宽度更好的方法,欢迎不吝赐教唷!

都绑定好后,这个时候显示肯定还是不正确的,因为现在TextContent是一个Json字符串,所以我们还差一个Converter:

 class ShowImageOrText : IValueConverter    {        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)        {            var v = JsonConvert.DeserializeObject<List<EmoticonText>>((string)value);            StringBuilder sb = new StringBuilder();            foreach (var item in v)            {                if (item.Key=="text")                {                    sb.Append("<Run>");                    sb.Append(item.Value);                    sb.Append("</Run>");                }                else                {                    sb.Append("<Image Width=/"32/" Source=/"");                    sb.Append(item.Value);                    sb.Append("/"/>");                }            }            return @"<Section xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" FontFamily=""Microsoft YaHei"" xml:space=""preserve"" TextAlignment=""Left"" LineHeight=""Auto""><Paragraph>" + sb.ToString() + "</Paragraph></Section>";        }        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)        {            throw new NotImplementedException();        }    }

这段代码一看就懂,如果是文本则用run,如果是表情图片,则用image,最后将拼装好的xaml绑定到前端。

下来问题就出现了,当run里面是中文时,前端会显示为???(几个中文就几个?),也就是XamlFormatter并不能正确解析中文,哦到开~尝试了修改各种Language属性以及xml:lang=”en-us”,都是徒劳- -!
根据官网(http://wpftoolkit.codeplex.com/wikipage?title=RichTextBox)介绍,我们是可以自定义formater的,那到底怎么自定义呢,在看了xctk:RichTextBox针对xaml的formatter这块的源码后才明白,
其编码格式用的ASCII,我们只要将其换成UTF8即可:

class RTBXamlFormatter : ITextFormatter    {        public string GetText(System.Windows.Documents.FlowDocument document)        {            TextRange tr = new TextRange(document.ContentStart, document.ContentEnd);            using (MemoryStream ms = new MemoryStream())            {                tr.Save(ms, DataFormats.Xaml);                return ASCIIEncoding.Default.GetString(ms.ToArray());            }        }        public void SetText(System.Windows.Documents.FlowDocument document, string text)        {            try            {                if (String.IsNullOrEmpty(text))                {                    document.Blocks.Clear();                }                else                {                    TextRange tr = new TextRange(document.ContentStart, document.ContentEnd);                    using (MemoryStream ms = new MemoryStream(Encoding.**UTF8**.GetBytes(text)))                    {                        tr.Load(ms, DataFormats.Xaml);                    }                }            }            catch            {                throw new InvalidDataException("Data provided is not in the correct Xaml format.");            }        }    }

记得将DataTemplate中的 <xctk:XamlFormatter/>换成当前这个 <local:RTBXamlFormatter/>

-:)

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台