动态聊天窗口

时间:2014-09-11 15:02:00

标签: c# wpf xaml

我对wpf gui的性能有疑问。

首先,我将解释我所做的事情。 我从数据库中读取了不同的聊天数据,主要是文本,但有时在文本中间有一个图标,如笑脸或类似的。或者,没有文字只是图像。

我通过使用Flowdocument并使用带有内联的Textblock来完成所有这些操作。哦,我忘了,我用wpf,对不起。

这很好用,但是当Flowdocument将被绘制到RichTextbox或FlowdocumentReader时,它需要很长时间并且gui冻结。我考虑过虚拟化,但RichTextBox并没有使用它。所以我的下一个想法是使用一个Listbox并为每个Chatbubble设置一个Richtextbox项。聊天可以包含约20,000个Chatbubbles。 所以现在我想使用数据绑定,但我没有找到一种方法来绑定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为10 ms,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);

问候并感谢您的帮助。

1 个答案:

答案 0 :(得分:1)

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

从它仍然需要永远加载的声音来看,你设置的虚拟化并没有正常工作......

使虚拟化工作所需的主要事情是您需要让ListBox模板中的ScrollViewer具有CanContentScroll=True。即:

<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?有关详情,请参阅Virtualizing an 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>

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

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

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);
    }
} 

最后,绑定(我已经绑定到contentInlines类的Message属性)需要是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;
    }
}