WPF显示富文本emoji表情+文本(类似微信)

Posted dotNET跨平台

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF显示富文本emoji表情+文本(类似微信)相关的知识,希望对你有一定的参考价值。

祝大家端午节安康!

WPF开发者QQ群: 340500857 

前言 

      有小伙伴需要实现类似微信一样的气泡聊天emoji表情+文本。

欢迎转发、分享、点赞,谢谢大家~。  

效果预览(更多效果请下载源码体验):

一、EmojiAndTextControl.cs代码如下:

/**
 Github https://github.com/yanjinhuagood/WPFDevelopers.git
 码云 https://gitee.com/yanjinhua/WPFDevelopers.git
 **/
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;


namespace WpfChatEmojiText
{
    [TemplatePart(Name = TextBlockTemplateName, Type = typeof(TextBlock))]
    [TemplatePart(Name = WrapPanelLeftTemplateName, Type = typeof(WrapPanel))]
    [TemplatePart(Name = BorderTemplateName, Type = typeof(Border))]
    [TemplatePart(Name = WrapPanelRightTemplateName, Type = typeof(WrapPanel))]
    [TemplatePart(Name = ImageBrushLeftTemplateName, Type = typeof(ImageBrush))]
    [TemplatePart(Name = ImageBrushRightTemplateName, Type = typeof(ImageBrush))]
    public class EmojiAndTextControl: Control
    {
        private static readonly Type _typeofSelf = typeof(EmojiAndTextControl);
        private const string TextBlockTemplateName = "PART_TextBlock";
        private const string WrapPanelLeftTemplateName = "PART_Left";
        private const string BorderTemplateName = "PART_Border";
        private const string WrapPanelRightTemplateName = "PART_Right";
        private const string ImageBrushLeftTemplateName = "LeftUser";
        private const string ImageBrushRightTemplateName = "RightUser";
        private TextBlock m_textBlock;
        private WrapPanel leftWrapPanel;
        private Border border;
        private WrapPanel rightWrapPanel;
        private ImageBrush leftImageBrush;
        private ImageBrush rightImageBrush;
        private bool m_IgnoreChanges = false;
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }
        static EmojiAndTextControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(_typeofSelf, new FrameworkPropertyMetadata(_typeofSelf));
        }
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();
            m_textBlock = GetTemplateChild(TextBlockTemplateName) as TextBlock;
            leftWrapPanel = GetTemplateChild(WrapPanelLeftTemplateName) as WrapPanel;
            border = GetTemplateChild(BorderTemplateName) as Border;
            rightWrapPanel = GetTemplateChild(WrapPanelRightTemplateName) as WrapPanel;
            leftImageBrush = GetTemplateChild(ImageBrushLeftTemplateName) as ImageBrush;
            rightImageBrush = GetTemplateChild(ImageBrushRightTemplateName) as ImageBrush;
            UpdateEmoji();
            UpdateIsRight();
            UpdateRightImageSource();
            UpdateLeftImageSource();
        }


        public static readonly DependencyProperty TextProperty =
          DependencyProperty.Register("Text", typeof(string), typeof(EmojiAndTextControl),
            new UIPropertyMetadata(string.Empty, OnTextChanged));


        static void OnTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EmojiAndTextControl emoji = (EmojiAndTextControl)d;
            emoji.UpdateEmoji();
        }


        void UpdateEmoji()
        {
            if (m_textBlock == null) return;
            if (!m_IgnoreChanges)
            {
                m_IgnoreChanges = true;
                //EmojiHelper.ParseText(m_textBlock);
                EmojiHelperLibrary.EmojiHelper.Instance.ParseText(m_textBlock);
                m_IgnoreChanges = false;
            }
        }






        


        public bool IsRight
        {
            get { return (bool)GetValue(IsRightProperty); }
            set { SetValue(IsRightProperty, value); }
        }


        public static readonly DependencyProperty IsRightProperty =
            DependencyProperty.Register("IsRight", typeof(bool), typeof(EmojiAndTextControl), new UIPropertyMetadata(OnIsRightChanged));


        static void OnIsRightChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EmojiAndTextControl control = (EmojiAndTextControl)d;
            control.UpdateIsRight();
        }


        void UpdateIsRight()
        {
            if (leftWrapPanel == null
                ||
                border == null
                ||
                rightWrapPanel == null) return;
            if (!IsRight)
            {
                leftWrapPanel.Visibility = Visibility.Visible;
                border.Background = Brushes.White;
                rightWrapPanel.Visibility = Visibility.Collapsed;
            }
        }


        public ImageSource LeftImageSource
        {
            get { return (ImageSource)GetValue(LeftImageSourceProperty); }
            set { SetValue(LeftImageSourceProperty, value); }
        }


        public static readonly DependencyProperty LeftImageSourceProperty =
            DependencyProperty.Register("LeftImageSource", typeof(ImageSource), typeof(EmojiAndTextControl), new UIPropertyMetadata(OnLeftImageSourceChanged));
        private static void OnLeftImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EmojiAndTextControl control = (EmojiAndTextControl)d;
            control.UpdateLeftImageSource();
        }


        void UpdateLeftImageSource()
        {
            if (leftImageBrush == null) return;
            leftImageBrush.ImageSource = LeftImageSource;
        }
        public ImageSource RightImageSource
        {
            get { return (ImageSource)GetValue(RightImageSourceProperty); }
            set { SetValue(RightImageSourceProperty, value); }
        }


        public static readonly DependencyProperty RightImageSourceProperty =
            DependencyProperty.Register("RightImageSource", typeof(ImageSource), typeof(EmojiAndTextControl), new UIPropertyMetadata(OnRightImageSourceChanged));


        private static void OnRightImageSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            EmojiAndTextControl control = (EmojiAndTextControl)d;
            control.UpdateRightImageSource();
        }
       
        void UpdateRightImageSource()
        {
            if (rightImageBrush == null) return;
            rightImageBrush.ImageSource = RightImageSource;
        }


    }
}


二、EmojiAndTextControlStyle.xaml代码如下:

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                    xmlns:local="clr-namespace:WpfChatEmojiText">
    <Style TargetType="local:EmojiAndTextControl">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:EmojiAndTextControl">
                    <Grid>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width="Auto"/>
                            <ColumnDefinition/>
                            <ColumnDefinition Width="Auto"/>
                        </Grid.ColumnDefinitions>
                        <WrapPanel Grid.Column="0" Name="PART_Left" 
                                   Visibility="Collapsed">
                            <Border Width="35" Height="35" 
                                VerticalAlignment="Top"
                                Margin="0,10"
                                CornerRadius="4"
                                UseLayoutRounding="True">
                                <Border.Background>
                                    <ImageBrush x:Name="LeftUser"
                                        ImageSource="{TemplateBinding LeftImageSource}" RenderOptions.BitmapScalingMode="LowQuality " Stretch="Fill"/>
                                </Border.Background>
                            </Border>
                            <Path Data="M365.714 256v512q0 14.857-10.857 25.714t-25.714 10.857-25.714-10.857l-256-256q-10.857-10.857-10.857-25.714t10.857-25.714l256-256q10.857-10.857 25.714-10.857t25.714 10.857 10.857 25.714z"
                              Fill="White" Width="10" Height="10" Stretch="Fill"
                              StrokeThickness="0" Grid.Column="1"
                              VerticalAlignment="Top" Margin="0,20,-14,0"
                              
                              UseLayoutRounding="True" SnapsToDevicePixels="True"/>
                        </WrapPanel>
                        
                        
                        <Border CornerRadius="4" Background="#9EEA6A"
                            UseLayoutRounding="True" SnapsToDevicePixels="True"
                            Grid.Column="1" Margin="10"
                            Name="PART_Border">
                            <TextBlock Name="PART_TextBlock" FontSize="15" Text="{TemplateBinding Text}"
                                   Padding="6" TextWrapping="Wrap" VerticalAlignment="Center"/>
                        </Border>
                        <WrapPanel Grid.Column="2" Name="PART_Right">
                            <Path Data="M329.143 512q0 14.857-10.857 25.714l-256 256q-10.857 10.857-25.714 10.857t-25.714-10.857-10.857-25.714v-512q0-14.857 10.857-25.714t25.714-10.857 25.714 10.857l256 256q10.857 10.857 10.857 25.714z"
                              Fill="#9EEA6A" Width="10" Height="10" Stretch="Fill"
                              StrokeThickness="0" 
                              VerticalAlignment="Top" Margin="-14,20,0,0"
                              UseLayoutRounding="True" SnapsToDevicePixels="True"/>
                            <Border Width="35" Height="35"
                                VerticalAlignment="Top"
                                Margin="0,10"
                                CornerRadius="4"
                                UseLayoutRounding="True">
                                <Border.Background>
                                    <ImageBrush x:Name="RightUser"
                                        ImageSource="{TemplateBinding RightImageSource}" RenderOptions.BitmapScalingMode="LowQuality" Stretch="Fill"/>
                                </Border.Background>
                            </Border>
                        </WrapPanel>
                       
                    </Grid>
                    <!--<ControlTemplate.Triggers>
                        <DataTrigger Binding="{Binding IsRight}" Value="false">
                            <Setter Property="Visibility" TargetName="PART_Left" Value="Visible"/>
                            <Setter Property="Background" TargetName="PART_Border" Value="White"/>
                            <Setter Property="Visibility" TargetName="PART_Right" Value="Collapsed"/>
                        </DataTrigger>
                    </ControlTemplate.Triggers>-->
                </ControlTemplate>
            </Setter.Value>
        </Setter>
</Style>
</ResourceDictionary>

三、MainWindow.xaml代码如下

<Window x:Class="WpfChatEmojiText.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfChatEmojiText"
        mc:Ignorable="d"
        Title="WPFDevelopers" Height="800" Width="800"
        TextOptions.TextFormattingMode="Display" UseLayoutRounding="True"
        SnapsToDevicePixels="True" RenderOptions.BitmapScalingMode="NearestNeighbor">
    <UniformGrid Columns="2">
        <Grid Margin="10,0">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
            <RichTextBox x:Name="_LeftChat" IsReadOnly="True">
                <RichTextBox.Background>
                    <ImageBrush ImageSource="Images\\Left.jpg" Stretch="Fill"/>
                </RichTextBox.Background>
            </RichTextBox>
            <Grid Grid.Row="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <ToggleButton x:Name="LeftButtonEmoji" Margin="10,0" Content="Emoji" Width="40" HorizontalAlignment="Left"/>
                <Popup Placement="Top" PlacementTarget="{Binding ElementName=LeftButtonEmoji}" 
                       IsOpen="{Binding ElementName=LeftButtonEmoji,Path=IsChecked}" AllowsTransparency="True" 
                       VerticalOffset="-4">
                    <Border Margin="10" Background="White" CornerRadius="4"
                            Width="76">
                        <Border.Effect>
                            <DropShadowEffect ShadowDepth="0" BlurRadius="10" Opacity="0.2" Color="#80000000"/>
                        </Border.Effect>
                        <ItemsControl ItemsSource="{Binding EmojiArray,RelativeSource={RelativeSource AncestorType=local:MainWindow}}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Border x:Name="PART_Border" CornerRadius="2"
                                            PreviewMouseLeftButtonDown="PART_Border_PreviewMouseLeftButtonDown"
                                            Tag="{Binding Key}">
                                        <Image Source="{Binding Value}" ToolTip="{Binding Name}"
                                               Width="30" Height="30" Margin="4"
                                               IsHitTestVisible="True"/>
                                    </Border>
                                    <DataTemplate.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter Property="Background" TargetName="PART_Border" Value="#FFD8D1D1"/>
                                        </Trigger>
                                    </DataTemplate.Triggers>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </Border>
                </Popup>
                <Grid Grid.Row="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBox x:Name="LeftInput"/>
                    <Button Grid.Column="1" Content="发送" x:Name="LeftSend" Click="LeftSend_Click"></Button>
                </Grid>
            </Grid>
        </Grid>
        <Grid Margin="10,0">
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition Height="100"/>
            </Grid.RowDefinitions>
            <RichTextBox x:Name="_RightChat" IsReadOnly="True">
                <RichTextBox.Background>
                    <ImageBrush ImageSource="Images\\Right.jpg" Stretch="Fill"/>
                </RichTextBox.Background>
            </RichTextBox>
          
            <Grid Grid.Row="1">
                <Grid.RowDefinitions>
                    <RowDefinition Height="Auto"/>
                    <RowDefinition/>
                </Grid.RowDefinitions>
                <ToggleButton x:Name="RightButtonEmoji" Margin="10,0" Content="Emoji" Width="40" HorizontalAlignment="Left"/>
                <Popup Placement="Top" PlacementTarget="{Binding ElementName=RightButtonEmoji}" 
                       IsOpen="{Binding ElementName=RightButtonEmoji,Path=IsChecked}" AllowsTransparency="True" 
                       VerticalOffset="-4">
                    <Border Margin="10" Background="White" CornerRadius="4"
                            Width="76">
                        <Border.Effect>
                            <DropShadowEffect ShadowDepth="0" BlurRadius="10" Opacity="0.2" Color="#80000000"/>
                        </Border.Effect>
                        <ItemsControl ItemsSource="{Binding EmojiArray,RelativeSource={RelativeSource AncestorType=local:MainWindow}}">
                            <ItemsControl.ItemTemplate>
                                <DataTemplate>
                                    <Border x:Name="PART_Border" CornerRadius="2"
                                            PreviewMouseLeftButtonDown="PART_Border_RightPreviewMouseLeftButtonDown"
                                            Tag="{Binding Key}">
                                        <Image Source="{Binding Value}" ToolTip="{Binding Name}"
                                               Width="30" Height="30" Margin="4"
                                               IsHitTestVisible="True"/>
                                    </Border>
                                    <DataTemplate.Triggers>
                                        <Trigger Property="IsMouseOver" Value="True">
                                            <Setter Property="Background" TargetName="PART_Border" Value="#FFD8D1D1"/>
                                        </Trigger>
                                    </DataTemplate.Triggers>
                                </DataTemplate>
                            </ItemsControl.ItemTemplate>
                            <ItemsControl.ItemsPanel>
                                <ItemsPanelTemplate>
                                    <WrapPanel/>
                                </ItemsPanelTemplate>
                            </ItemsControl.ItemsPanel>
                        </ItemsControl>
                    </Border>
                </Popup>
                <Grid Grid.Row="1">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition/>
                        <ColumnDefinition Width="Auto"/>
                    </Grid.ColumnDefinitions>
                    <TextBox x:Name="RightInput"/>
                    <Button Grid.Column="1" Content="发送" x:Name="RightSend" Click="RightSend_Click"></Button>
                </Grid>
            </Grid>
        </Grid>
    </UniformGrid>
</Window>

四、MainWindow.cs.xaml代码如下

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;


namespace WpfChatEmojiText
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {
        public IEnumerable EmojiArray
        {
            get { return (IEnumerable)GetValue(EmojiArrayProperty); }
            set { SetValue(EmojiArrayProperty, value); }
        }


        public static readonly DependencyProperty EmojiArrayProperty =
            DependencyProperty.Register("EmojiArray", typeof(IEnumerable), typeof(MainWindow), new PropertyMetadata(null));






        public MainWindow()
        {
            InitializeComponent();


            var emojiModels = new List<EmojiModel>();




            EmojiHelperLibrary.EmojiHelper.Instance._emojiHeight = 30;
            EmojiHelperLibrary.EmojiHelper.Instance._emojiWidth = 30;


            var m_Emojis = new Dictionary<string, string>();
            var emojiPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "emoji");
            var directory = new DirectoryInfo(emojiPath);
            foreach (var item in directory.GetFiles())
            {
                var _key = $"[{Path.GetFileNameWithoutExtension(item.Name)}]";
                m_Emojis.Add(_key, item.FullName);
                emojiModels.Add(new EmojiModel { Name = Path.GetFileNameWithoutExtension(item.Name), Key = _key, Value = item.FullName });
            }
            EmojiHelperLibrary.EmojiHelper.Instance.m_Emojis = m_Emojis;


            EmojiArray = emojiModels;


        }


        private void PART_Border_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            LeftButtonEmoji.IsChecked = false;
            var send = sender as Border;
            LeftInput.Text += send.Tag.ToString();
            LeftInput.Focus();
            LeftInput.SelectionStart = LeftInput.Text.Length;
        }


        private void LeftSend_Click(object sender, RoutedEventArgs e)
        {
            LeftChat();




        }
        void LeftChat()
        {
            var leftText = new EmojiAndTextControl();
            leftText.IsRight = true;
            leftText.RightImageSource = new BitmapImage(new Uri("pack://application:,,,/Images/UserImages/jingtao.png"));
            leftText.Text = LeftInput.Text;
            var leftPara = new Paragraph();
            leftPara.TextAlignment = TextAlignment.Right;
            leftPara.Inlines.Add(leftText);
            _LeftChat.Document.Blocks.Add(leftPara);




            var rightText = new EmojiAndTextControl();
            rightText.IsRight = false;
            rightText.LeftImageSource = new BitmapImage(new Uri("pack://application:,,,/Images/UserImages/jingtao.png"));
            rightText.Text = LeftInput.Text;
            var rightPara = new Paragraph();
            rightPara.TextAlignment = TextAlignment.Left;
            rightPara.Inlines.Add(rightText);
            _RightChat.Document.Blocks.Add(rightPara);


            LeftInput.Text = string.Empty;
            LeftInput.Focus();
        }
        void RightChat()
        {
            var leftText = new EmojiAndTextControl();
            leftText.IsRight = true;
            leftText.RightImageSource = new BitmapImage(new Uri("pack://application:,,,/Images/UserImages/yanjinhua.png"));
            leftText.Text = RightInput.Text;
            var leftPara = new Paragraph();
            leftPara.TextAlignment = TextAlignment.Right;
            leftPara.Inlines.Add(leftText);
            _RightChat.Document.Blocks.Add(leftPara);




            var rightText = new EmojiAndTextControl();
            rightText.IsRight = false;
            rightText.LeftImageSource = new BitmapImage(new Uri("pack://application:,,,/Images/UserImages/yanjinhua.png"));
            rightText.Text = RightInput.Text;
            var rightPara = new Paragraph();
            rightPara.TextAlignment = TextAlignment.Left;
            rightPara.Inlines.Add(rightText);
            _LeftChat.Document.Blocks.Add(rightPara);


            RightInput.Text = string.Empty;
            RightInput.Focus();
        }


        private void RightSend_Click(object sender, RoutedEventArgs e)
        {
            RightChat();
        }


        private void PART_Border_RightPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            RightButtonEmoji.IsChecked = false;
            var send = sender as Border;
            RightInput.Text += send.Tag.ToString();
            RightInput.Focus();
            RightInput.SelectionStart = RightInput.Text.Length;
        }
    }
    public class EmojiModel
    {
        public string Name { get; set; }
        public string Key { get; set; }
        public string Value { get; set; }
    }
}

源码地址

github:https://github.com/yanjinhuagood/WPFDevelopers.git

gitee:https://gitee.com/yanjinhua/WPFDevelopers.git

WPF开发者QQ群: 340500857 

blogs: https://www.cnblogs.com/yanjinhua

Github:https://github.com/yanjinhuagood

出处:https://www.cnblogs.com/yanjinhua

版权:本作品采用「署名-非商业性使用-相同方式共享 4.0 国际」许可协议进行许可。

转载请著名作者 出处 https://github.com/yanjinhuagood

以上是关于WPF显示富文本emoji表情+文本(类似微信)的主要内容,如果未能解决你的问题,请参考以下文章

iOS开发emoji表情的输入思路

AndroidRichText 让Textview轻松的支持富文本(图像ImageSpan点击效果等等类似QQ微信聊天)

微信符号表情怎么打?

node.js中微信表情符号怎么用

iOS开发进阶 - 富文本正则替换表情

富文本编辑器 quill.js 开发: 自定义格式扩展