3

我有一个 WPF 应用程序,它通过套接字连接到设备并获取流式文本数据(每秒大约 1 条消息)。然后,此数据将显示在 UI 上。用户可以创建诸如“如果数据包含'abc'突出显示该行”“...使其变为粗体”这样的规则,所以纯文本输出是不行的,它需要是“富”文本。

我目前对此的解决方案是在我的 ViewModel 中有一个包含格式化输出的FlowDocument 。View 有一个FlowDocumentScrollViewer,它绑定到 ViewModel 中的 FlowDocument。

这可行,但是当 FlowDocument 变大(约 6,000 行)时,性能开始下降。当前算法将行数限制在 10,000 行,但随后情况变得更糟,以至于应用程序无法使用。一旦达到 10,000 行,我就会为添加的每一行删除一行,从而导致 FlowDocumentScrollViewer 为每个新行获取 2 个更新通知。

我试图找到一种批量删除的方法(当我们达到 10,000 行时删除最旧的 1,000 行),但 FlowDocument 上没有批量删除。循环 1,000 次并执行删除会导致 1,000 条更新通知并锁定 UI。

这是我的问题,这是我的问题:

使用 WPF 显示流式富文本内容的最佳方式是什么? 我每秒收到约 1 条消息,每条消息约为 150 个字符,我想保留最后 10000 条消息。我会以错误的方式解决这个问题吗?是否还有其他性能更好的控件/对象?

编辑:这里有一些更多的要求

  • 需要能够打印输出文本
  • 需要能够选择和复制输出文本,以便将其粘贴到另一个文档中
4

3 回答 3

5

性能下降似乎是由 FlowDocument 中的大量块引起的。对于收到的每条消息,我都在创建一个运行,将运行添加到段落并将段落添加到文档中。

我更改了算法,所以现在它创建了一个段落,然后向该段落添加 250 次运行,然后创建一个新的段落......添加 250 次运行......等等。这基本上将块的数量减少了一半。

当我达到最大行数(10,000)时,这还有一个额外的好处。我可以删除最旧的段落并立即删除最旧的 250 行,而不是为添加的每个新行删除一行(并与 CPU 挂钩)。

这种相对简单的改变使性能很好地处于可接受的范围内。现在 CPU 保持在相对较低的水平,峰值约为 15%,而不是固定 CPU 并锁定 UI。

于 2009-04-14T15:33:06.313 回答
0

由于能够在列中显示内容等,FlowDocumentScrollViewers 可能会产生开销。是否有正常 WPF RichTextBox 不起作用的原因?另外,您有 .NET 3.5 SP1 吗?以下链接表明 SP1 中 FlowDocuments 的性能有了很大改进:http: //social.msdn.microsoft.com/Forums/en-US/wpf/thread/a116da54-ce36-446a-8545-3f34e9b9038d

于 2009-04-07T18:18:31.650 回答
0

这个想法使事情变得非常复杂,但我的想法是为每条消息创建一个查看器,并且只创建显示当前可见消息所需的数量的查看器。我认为 VirtualizingStackPanel 控件将是一个很好的工具来管理它。我在这里找到了一个描述 VirtualizingStackPanel 实现的系列。

显然,这意味着在单独的数据结构中维护消息缓冲区。

编辑:我刚刚意识到标准 ListBox 控件在其实现中使用了 VirtualizingStackPanel。考虑到这一点,我修改后的建议是:

  1. 创建一个数据结构来包含每条消息的来源。
  2. 在数据结构上创建一个属性,该属性“动态”从消息源创建 FlowDocument
  3. 将 ListBox 绑定到所述数据结构的集合。
  4. 使用 FlowDocumentScrollViewer 为 ListBox 定义 ItemTemplate,其中 Document 属性绑定到上述数据结构的属性。

编辑 2:关于打印/缩放:我无法帮助您在 WPF 中打印(可能涉及 VisualBrush 的东西?),但缩放应该很容易做到。我创建了一个 ZoomListBox 来测试这个想法。XAML 如下所示:

<ListBox
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d"
    x:Class="Test.ZoomListBox"
    d:DesignWidth="640" d:DesignHeight="480"
    x:Name="ThisControl">
    <ListBox.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel IsItemsHost="True">
                <VirtualizingStackPanel.LayoutTransform>
                    <ScaleTransform ScaleX="{Binding ElementName=ThisControl, Path=Zoom}" ScaleY="{Binding ElementName=ThisControl, Path=Zoom}" />
                </VirtualizingStackPanel.LayoutTransform>
            </VirtualizingStackPanel>
        </ItemsPanelTemplate>
    </ListBox.ItemsPanel>
</ListBox>

后面的代码是这样的:

public partial class ZoomListBox
{
    public ZoomListBox()
    {
        this.InitializeComponent();
    }

    public double Zoom
    {
        get { return (double)GetValue(ZoomProperty); }
        set { SetValue(ZoomProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Zoom.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ZoomProperty =
        DependencyProperty.Register("Zoom", typeof(double), typeof(ZoomListBox), new UIPropertyMetadata(1.0));
}

以及一个使用它的例子:

<Grid x:Name="LayoutRoot">
    <Grid.RowDefinitions>
        <RowDefinition />
        <RowDefinition Height="Auto" />
    </Grid.RowDefinitions>
    <l:ZoomListBox x:Name="ZoomList">
        <Button>Foo</Button>
        <Button>Foo</Button>
        <Button>Foo</Button>
        <Button>Foo</Button>
        <Button>Foo</Button>
        <Button>Foo</Button>
        <Button>Foo</Button>
    </l:ZoomListBox>
    <Slider Grid.Row="1" Value="{Binding ElementName=ZoomList, Path=Zoom}" Minimum="0.5" Maximum="5" /> 
</Grid>
于 2009-04-07T19:36:19.777 回答