WPF下聊天气泡的实现

零、版本履历

日期说明
2020.05.02初稿

一、效果

先看最终效果。

在这里插入图片描述

最终微聊烂尾了,更确切地说,还没开始就结束了。

二、由来

产品虽然没有最终做出来,但至少聊天气泡打磨的还挺像样的。

说说怎么实现的吧。打磨一个聊天气泡的想法由来已久。

WinForm

最开始用的WInForm,想重绘ListBox或者RichTextBox来做,可是借助万能的度娘也没找到思路,不熟悉GDI+,更不知道怎么去重绘。

Layui

后来看到Layui中的IM组件挺符合我的需求,想到可以借助CefSharp实现客户端和前端的联动,最终由于不熟悉前端,不知道怎么屏蔽边框和拖动,想法又泡汤了。

在这里插入图片描述

WPF

直到某天入了WPF的坑,由于它自带界面和逻辑分离的天然优势,完全可以重写LabelDataTemplate来实现,特别简单。

三、实现

思路很简单,重写ListView的模板,针对接收/发送两种行为分别定义两个模板,再定义一个模板选择器DataTemplateSelector判断要应用哪个模板。

定义ChatBubble

考虑到气泡中不仅仅显示文本,还可能会有图片和表情符号,我们可以重写Label的模板,分头像和内容两部分,内容的边框作成气泡的样子。

直接上代码,这是接收的气泡。

<Style TargetType="Label" x:Key="chatRecv">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Label}">
                <Grid Margin="0,5,0,5">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="40" />
                        <ColumnDefinition Width="*" />
                    </Grid.ColumnDefinitions>

                    <Image Width="35" Height="35" Source="userIcon.jpg" />

                    <Border x:Name="border1" 
                            Grid.Column="1" 
                            CornerRadius="1" 
                            BorderBrush="#EDEDED" 
                            BorderThickness="1" 
                            VerticalAlignment="Top" 
                            Margin="10,2,0,2" 
                            Background="#FFFFFF" 
                            HorizontalAlignment="Left" 
                            Padding="5" 
                            MinHeight="35"
                            MinWidth="50">
                        <ContentPresenter VerticalAlignment="Center" />
                    </Border>
                    <Canvas Grid.Column="1" 
                            Width="10" 
                            Height="16" 
                            HorizontalAlignment="Left" 
                            VerticalAlignment="Top" 
                            Margin="3,14,0,0" 
                            Background="Transparent">
                        <Path x:Name="path1" 
                              Stroke="#EDEDED" 
                              StrokeThickness="1" 
                              Fill="#FFFFFF">
                            <Path.Data>
                                <PathGeometry Figures="M 8,0 L 0,6,8,12"/>
                            </Path.Data>
                        </Path>
                    </Canvas>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="path1" 
                                Property="Fill" 
                                Value="#F6F6F6" />
                        <Setter TargetName="border1" 
                                Property="Background" 
                                Value="#F6F6F6" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

同样的,再定义一个发送的气泡。

<Style TargetType="Label" x:Key="chatSend">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type Label}">
                <Grid Margin="0,5,0,5">
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*" />
                        <ColumnDefinition Width="40" />
                    </Grid.ColumnDefinitions>

                    <Image Grid.Column="1" 
                           Width="35" Height="35" 
                           Source="userIcon.jpg" />

                    <Border x:Name="border1" 
                            CornerRadius="1"
                            BorderBrush="#9EEA6A"
                            BorderThickness="1" 
                            VerticalAlignment="Top"
                            HorizontalAlignment="Right"
                            Margin="0,2,10,2" 
                            Background="#9EEA6A" 
                            Padding="5" 
                            MinHeight="35" 
                            MinWidth="50">
                        <ContentPresenter VerticalAlignment="Center" />
                    </Border>
                    <Canvas Width="10" 
                            Height="16" 
                            HorizontalAlignment="Right" 
                            VerticalAlignment="Top"
                            Margin="5,14,0,0" 
                            Background="Transparent">
                        <Path x:Name="path1" 
                              Stroke="#9EEA6A" 
                              StrokeThickness="0.5"
                              Fill="#9EEA6A">
                            <Path.Data>
                                <PathGeometry Figures="M 0,12 L 0,0,8,6"/>
                            </Path.Data>
                        </Path>
                    </Canvas>
                </Grid>
                <ControlTemplate.Triggers>
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter TargetName="path1" 
                                Property="Fill" 
                                Value="#98E165" />
                        <Setter TargetName="border1"
                                Property="Background" 
                                Value="#98E165" />
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

定义TemplateSelector

接下来轮到TemplateSelector出场,最终气泡要通过ListView呈现,可以在ItemSource中指定一个标记位IsSend来告知UI是接收还是发送

class ChatBubbleSelector : DataTemplateSelector
{
    public override DataTemplate SelectTemplate(object item, DependencyObject container)
    {
        var u = container as FrameworkElement;

        MessageEntity message = item as MessageEntity;

        if (message.IsSend)
            return u.FindResource("chatSend") as DataTemplate;
        else
            return u.FindResource("chatRecv") as DataTemplate;
    }
}

声明资源

然后在主界窗口中声明这个选择器,以及背景颜色等资源,配色取自微信PC端。

<Color x:Key="background">#F5F5F5</Color>

<SolidColorBrush x:Key="background_brush" 
                 Color="{StaticResource background}" 
                 options:Freeze="True" />

<Color x:Key="icon_btn_normal">#F7F7F7</Color>

<SolidColorBrush x:Key="icon_btn_normal_brush" 
                 Color="{StaticResource icon_btn_normal}" 
                 options:Freeze="True" />

<Color x:Key="message_send_bg">#98E165</Color>

<SolidColorBrush x:Key="message_send_bg_brush" 
                 Color="{StaticResource message_send_bg}" 
                 options:Freeze="True" />

<Color x:Key="message_recv_bg">#F6F6F6</Color>

<SolidColorBrush x:Key="message_recv_bg_brush" 
                 Color="{StaticResource message_recv_bg}" 
                 options:Freeze="True" />

<Color x:Key="green_normal">#07C160</Color>

<SolidColorBrush x:Key="green_normal_brush" 
                 Color="{StaticResource green_normal}" 
                 options:Freeze="True" />

主界面实现

最后在主界面上放一个ListView,用来承载消息。

<ListBox ItemTemplateSelector="{StaticResource chatBubbleSelector}" 
         ItemsSource="{Binding Messages}" 
         Background="{DynamicResource background_brush}" 
         SelectedIndex="{Binding SelectedMessage}" 
         BorderThickness="0,1,0,1" 
         BorderBrush="#D3D3D3"
         ScrollViewer.HorizontalScrollBarVisibility="Disabled">
    <ListBox.ItemContainerStyle>
        <Style TargetType="{x:Type ListBoxItem}" x:Key="listViewItemStyle">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type ListBoxItem}">
                        <Border Name="Bd"
                                Background="{TemplateBinding Background}"
                                BorderBrush="{TemplateBinding BorderBrush}"
                                BorderThickness="{TemplateBinding BorderThickness}"
                                Padding="{TemplateBinding Padding}"
                                SnapsToDevicePixels="true">
                            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
                                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"/>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
            <Setter Property="HorizontalContentAlignment" Value="Stretch"/>
            <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        </Style>
    </ListBox.ItemContainerStyle>
</ListBox>

在ViewModel中绑定数据源,F5运行,就能看到文章一开始的效果了。

最后的最后,附上本文用到的Demo:https://gitee.com/dswfort/BlogDemos/tree/master/ChartBubbleSample

2020年5月2日星期六

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值