动态聊天窗口
本文关键字:窗口 聊天 动态 | 更新日期: 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;
}
}