我对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);
问候并感谢您的帮助。
答案 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?。
请参阅下文 - 如果您还想要平滑滚动,可能值得查看TreeView
- 请参阅http://classpattern.com/smooth-scrolling-with-virtualization-wpf-list.html#.VBHWtfldXSg - 但如果此时此功能正常,我无法保证,只是自己发现了!
正如我在下面的评论中所说,如果您没有编辑所有内容,则可以删除所有标记:
<Grid><RichTextBox><FlowDocument><Paragraph><Figure>
在数据模板中。您可能无法将消息的Text
绑定到DataTemplate中的contentText
,并且必须有一些幕后代码来动态生成TextBlock的内联
好的,总的来说(忽略一些造型),我建议如下:
<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;
}
}