动态聊天窗口

本文关键字:窗口 聊天 动态 | 更新日期: 2023-09-27 18:21:36

我对wpf-gui的性能有问题。

首先我会解释一下我做了什么。我从数据库中读取了不同的聊天数据,大部分是文本,但有时文本中间会有一个图标,比如一个微笑或类似的图标。或者,没有文本,只有图像。

我通过使用Flowdocument和带内联的Textblock来完成这一切。哦,我忘了,我用wpf,对不起。

这很好,但目前Flowdocument将被绘制到RichTextbox或FlowdocumentReader,这需要很长时间,并且gui会冻结。我已经考虑过虚拟化,但RichTextBox不使用这个。所以我的下一个想法是使用Listbox,并为每个Chatbubble设置Richtextbox作为项。一个聊天可以包含大约20000个聊天气泡。所以现在我想使用Databinding,但我找不到绑定Textblock内联的方法。

所以现在有一些代码。

<DataTemplate x:Key="MessageDataTemplate" DataType="{x:Type classes:Message}">
            <Grid>
                <RichTextBox x:Name="rtbChat"
                    SpellCheck.IsEnabled="False"
                    VerticalScrollBarVisibility="Auto"
                    VerticalContentAlignment="Stretch">
                    <FlowDocument
                        FontFamily="Century Gothic"
                        FontSize="12"
                        FontStretch="UltraExpanded">
                        <Paragraph>
                            <Figure>
                                <BlockUIContainer>
                                    <Border>
                                        <Border>
                                            <Grid>
                                                <Grid.ColumnDefinitions>
                                                    <ColumnDefinition Width="150"/>
                                                    <ColumnDefinition Width="80"/>
                                                </Grid.ColumnDefinitions>
                                                <Grid.RowDefinitions>
                                                    <RowDefinition Height="15"/>
                                                    <RowDefinition Height="Auto"/>
                                                </Grid.RowDefinitions>
                                                <TextBlock x:Name="tUser"
                                                    Foreground="Gray"
                                                    TextAlignment="Right"
                                                    FontSize="10"
                                                    Grid.Row="0"
                                                    Grid.Column="1"
                                                    Text="{Binding displayUserName}"/>
                                                <TextBlock x:Name="tTime"
                                                    Foreground="Gray"
                                                    TextAlignment="Left"
                                                    FontSize="10"
                                                    Grid.Row="0"
                                                    Grid.Column="0"
                                                    Text="{Binding sendTime}"/>
                                                <TextBlock x:Name="tMessage"
                                                    Foreground="Black"
                                                    TextAlignment="Justify"
                                                    FontSize="12"
                                                    Height="NaN"
                                                    TextWrapping="Wrap"
                                                    Grid.Row="1"
                                                    Grid.Column="0"
                                                    Grid.ColumnSpan="2"
                                                    Text="{Binding contentText}"   />
                                                <Image x:Name="tImage"
                                                    Grid.Row="1"
                                                    Grid.Column="0"
                                                    Grid.ColumnSpan="2"
                                                    Height="NaN"
                                                    Source="{Binding imageSend}"/>
                                            </Grid>
                                        </Border>
                                    </Border>
                                </BlockUIContainer>
                            </Figure>
                        </Paragraph>
                    </FlowDocument>
                </RichTextBox>
            </Grid>
        </DataTemplate>

所以这还不是最终版本,我正在将它从源代码移植到xaml,目前缺少一些setter。

我已经对时间进行了基准测试,一切都很好,sqlite为10ms,构建FlowDocument大约需要4秒,但在RichTextBox中绘制FlowDocument最多需要5分钟。我知道这就是为什么孔盒是油漆的,也是看不见的部分。

我希望这是可以理解的,如果不是问我:)

这里是移植到xaml之前的源代码。

        var rtBox = new RichTextBox
        {
            //IsEnabled = false,
            BorderThickness = new Thickness(0, 0, 0, 0)
        };
        var doc = new FlowDocument();
        Contact contact = null;
        contact = _mess.remote_resource != "" ? _contacts.Find(x => x._jid == _mess.remote_resource) : _contacts.Find(x => x._jid == _mess.key_remote_jid);
        var para = new Paragraph();
        //--- Style of the message -----
        para.Padding = new Thickness(0);
        BlockUIContainer blockUI = new BlockUIContainer();
        blockUI.Margin = new Thickness(0, 0, 0, 0);
        blockUI.Padding = new Thickness(0);
        blockUI.TextAlignment = _mess.key_from_me == 1 ? TextAlignment.Right : TextAlignment.Left;
        Border bShadow = new Border();
        bShadow.Width = 231;
        bShadow.BorderBrush = Brushes.LightGray;
        bShadow.BorderThickness = new Thickness(0, 0, 0, 1);
        Border b2 = new Border();
        b2.Width = 230;
        b2.BorderBrush = Brushes.Gray;
        b2.Background = Brushes.White;
        b2.BorderThickness = new Thickness(0.5);
        b2.Padding = new Thickness(2);
        Grid g = new Grid();
        g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(150,GridUnitType.Star) });
        g.ColumnDefinitions.Add(new ColumnDefinition() { Width = new GridLength(80) });
        g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(15) });
        g.RowDefinitions.Add(new RowDefinition() { Height = new GridLength(25,GridUnitType.Auto) });
        TextBlock tUser = new TextBlock()
        {
            Foreground = Brushes.Gray,
            TextAlignment = TextAlignment.Right,
            FontSize = 10,
        };
        tUser.SetValue(Grid.RowProperty, 0);
        tUser.SetValue(Grid.ColumnProperty, 1);
        if(contact != null)
            tUser.Text = _mess.key_from_me == 1 ? "ich" : (contact._displayName == "" ? Whatsapp.Contacs.convertJidToNumber(_mess.remote_resource) : contact._displayName);
        else
        {
            tUser.Text = Whatsapp.Contacs.convertJidToNumber(_mess.remote_resource);
        }
        TextBlock tTime = new TextBlock()
        {
            Foreground = Brushes.Gray,
            TextAlignment = TextAlignment.Left,
            FontSize = 10,
        };
        tTime.SetValue(Grid.RowProperty, 0);
        tTime.SetValue(Grid.ColumnProperty, 0);
        tTime.Text = UnixTime.TimeReturnUnix2DateUtc(_mess.timestamp, timeZone).ToString();
        TextBlock tMessage = new TextBlock()
        {
            Foreground = Brushes.Black,
            TextAlignment = TextAlignment.Justify,
            FontSize = 12,
            Height = Double.NaN,
            TextWrapping = TextWrapping.Wrap
        };
        tMessage.SetValue(Grid.RowProperty, 1);
        tMessage.SetValue(Grid.ColumnProperty, 0);
        tMessage.SetValue(Grid.ColumnSpanProperty, 2);

        for (var i = 0; i < _mess.data.Length; i += Char.IsSurrogatePair(_mess.data, i) ? 2 : 1)
        {
            var x = Char.ConvertToUtf32(_mess.data, i);
            if (EmojiConverter.EmojiDictionary.ContainsKey(x))
            {
                //Generate new Image from Emoji
                var emoticonImage = new Image
                {
                    Width = 20,
                    Height = 20,
                    Margin = new Thickness(0, -5, 0, -5),
                    Source = EmojiConverter.EmojiDictionary[x]
                };
                //add grafik to FlowDocument
                tMessage.Inlines.Add(emoticonImage);
            }
            else
            {
                tMessage.Inlines.Add(new Run("" + _mess.data[i]));
            }
        }
        g.Children.Add(tUser);
        g.Children.Add(tTime);
        g.Children.Add(tMessage);
        b2.Child = g;
        bShadow.Child = b2;
        blockUI.Child = bShadow;
        Figure fig = new Figure(blockUI);
        fig.Padding = new Thickness(0);
        fig.Margin = new Thickness(0);
        fig.Height = new FigureLength(0, FigureUnitType.Auto);
        para.Inlines.Add(fig);
        doc.Blocks.Add(para);
        rtBox.Document = doc;
        msgList.Add(rtBox);

问候和感谢你的帮助。

动态聊天窗口

当然,一种方法是使用ListBox进行虚拟化。可以说,更好的方法是动态加载所需的消息或进行自己的虚拟化控制(默认ListBox虚拟化的问题包括,您必须一次性滚动整个项目才能使虚拟化工作……在某些情况下,从用户体验的角度来看,这可能会有点困难。)

从加载仍然需要很长时间的声音来看,您设置的虚拟化工作不正常。。。

使虚拟化工作所需的主要内容是,您需要使ListBox模板中的ScrollViewer具有CanContentScroll=True。Ie-do:

<ListBox ScrollViewer.CanContentScroll="True" .... >

或者给ListBox一个类似于下面的模板:

<ControlTemplate>
    <Border BorderBrush="{TemplateBinding Border.BorderBrush}"
            BorderThickness="{TemplateBinding Border.BorderThickness}"
            Background="{TemplateBinding Panel.Background}"
            SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}">
        <ScrollViewer Focusable="False"
                      Padding="{TemplateBinding Control.Padding}"
                      MaxHeight="{TemplateBinding Control.MaxHeight}"
                      CanContentScroll="True">
            <ItemsPresenter SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
        </ScrollViewer>
    </Border>
</ControlTemplate>

此外,除非您想实际选择以前的消息,否则可能ListBox不是您想要的,而您实际上想要ItemsControl?请参阅虚拟化项目控件?了解更多信息。

附加1-平滑滚动+虚拟化:

请参阅下文-如果您还希望平滑滚动,可能值得一看TreeView-请参阅http://classpattern.com/smooth-scrolling-with-virtualization-wpf-list.html#.VBHWtfldXSg-尽管我现在不能保证这是否真的有效,但我自己刚刚发现了!

补充2-澄清RE所需要素

正如我在下面的评论中所说,如果你不编辑所有内容,你可以去掉所有标签:

<Grid><RichTextBox><FlowDocument><Paragraph><Figure>

在数据模板中。您可能无法将消息的Text绑定到DataTemplate中的contentText,并且必须有一些幕后代码来动态生成TextBlock的内联。

附加3-如何绑定TextBlock以包含XAML中的图像等

好的,所以总的来说(忽略一些造型),我建议如下:

<DataTemplate x:Key="MessageDataTemplate" DataType="{x:Type classes:Message}">
    <Border>
        <Border>
            <Grid>
                <Grid.ColumnDefinitions>
                    <ColumnDefinition Width="150"/>
                    <ColumnDefinition Width="80"/>
                </Grid.ColumnDefinitions>
                <Grid.RowDefinitions>
                    <RowDefinition Height="15"/>
                    <RowDefinition Height="Auto"/>
                </Grid.RowDefinitions>
                <TextBlock x:Name="tUser"
                    Foreground="Gray"
                    TextAlignment="Right"
                    FontSize="10"
                    Grid.Row="0"
                    Grid.Column="1"
                    Text="{Binding displayUserName}" />
                <TextBlock x:Name="tTime"
                    Foreground="Gray"
                    TextAlignment="Left"
                    FontSize="10"
                    Grid.Row="0"
                    Grid.Column="0"
                    Text="{Binding sendTime}" />
                <TextBlock x:Name="tMessage"
                    Foreground="Black"
                    TextAlignment="Justify"
                    FontSize="12"
                    Height="NaN"
                    TextWrapping="Wrap"
                    Grid.Row="1"
                    Grid.Column="0"
                    Grid.ColumnSpan="2"
                    classes:TextBlockInlineBinder.Inlines="{Binding contentInlines}" />
                <Image x:Name="tImage"
                    Grid.Row="1"
                    Grid.Column="0"
                    Grid.ColumnSpan="2"
                    Height="NaN"
                    Source="{Binding imageSend}" />
            </Grid>
        </Border>
    </Border>
</DataTemplate>

注意消息TextBlock上的行classes:TextBlockInlineBinder.Inlines="{Binding contentInlines}"。这是为了能够结合到Inlines。。。基本上,这不是一个依赖属性,所以不能直接绑定到!

相反,我们可以使用下面的自定义静态类TextBlockInlineBinder来创建一个静态依赖属性,以添加到我们的TextBlock中,当它更新时,它会运行InlinesChanged方法来更新Inline:

public static class TextBlockInlineBinder
{
    #region Static DependencyProperty Implementation
    public static readonly DependencyProperty InlinesProperty =
        DependencyProperty.RegisterAttached("Inlines",
        typeof(IEnumerable<Inline>),
        typeof(TextBlockInlineBinder),
        new UIPropertyMetadata(new Inline[0], InlinesChanged));
    public static string GetInlines(DependencyObject obj)
    {
        return (string)obj.GetValue(InlinesProperty);
    }
    public static void SetInlines(DependencyObject obj, string value)
    {
        obj.SetValue(InlinesProperty, value);
    }
    #endregion
    private static void InlinesChanged(DependencyObject sender, 
                                       DependencyPropertyChangedEventArgs e)
    {
        var value = e.NewValue as IEnumerable<Inline>;
        var textBlock = sender as TextBlock;
        textBlock.Inlines.Clear();
        textBlock.Inlines.AddRange(value);
    }
} 

最后,绑定(我已经将其绑定到Message类上的contentInlines属性)需要是IEnumerable<Inline>类型,即类似于:

public IEnumerable<Inline> contentInlines
{
    get {
        var inlines = new List<Inline>();
        for (var i = 0; i < _mess.data.Length; i += Char.IsSurrogatePair(_mess.data, i) ? 2 : 1)
        {
            var x = Char.ConvertToUtf32(_mess.data, i);
            if (EmojiConverter.EmojiDictionary.ContainsKey(x))
            {
                //Generate new Image from Emoji
                var emoticonImage = new Image
                {
                    Width = 20,
                    Height = 20,
                    Margin = new Thickness(0, -5, 0, -5),
                    Source = EmojiConverter.EmojiDictionary[x]
                };
                inlines.Add(emoticonImage);
            }
            else
            {
                inlines.Add(new Run("" + _mess.data[i]));
            }
        }
        return inlines;
    }
}