XAML中的高效滚动命令日志视图

时间:2018-01-30 16:27:03

标签: .net wpf vb.net xaml

我有一个大型项目,其中包含许多用XAML / VB.net编写的大型用户控件。

我的软件的一个主要功能是将消息传递到远程设备并显示/使用他们的响应。

我已经在文本框中创建了一个滚动文本面板,并进行了一些修改。在大多数情况下,这很有效。

我正在寻找一种提高性能的方法,我相信(但我不是肯定的。)像这样的组件应默认使用虚拟化而应该只渲染&# 39;屏幕上显示随着越来越多的消息被传入,我看到内存增加,并且在存在大量消息之后它们似乎变得越来越慢。

是否有一些正确的方法来实现诸如xaml之类的东西?是否有任何免费资源可以模拟作为WPF控件的命令提示符?

<Grid x:Name="grdRoot" >
    <ScrollViewer x:Name="Scroller"  Height="{Binding Path=ActualHeight, 
       RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}" Width="{Binding Path=ActualWidth, 
       RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=Grid}}" HorizontalScrollBarVisibility="Auto">
        <TextBox x:Name="txtLog" BorderThickness="0" IsReadOnly="True" Background="{Binding LogBackColor}" ContextMenu="{DynamicResource ctxMenu}" FontFamily="Consolas" AcceptsTab="True" AcceptsReturn="True" FontSize="13" TextOptions.TextFormattingMode="Display" MaxLines="20000000" TextWrapping="Wrap"/>


    </ScrollViewer>

 <ItemsControl>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel IsItemsHost="true" >
                </VirtualizingStackPanel>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
</Grid>

编辑:我想我确实使用了错误的术语。我试图复制命令行窗口,以有效的方式显示消息的来回传递。

1 个答案:

答案 0 :(得分:1)

TextBox解决方案

效果取决于您使用TextBox的方式。如果你正在做这样的事情:

textBox.Text += latestMessage;

...那么你的表现会很糟糕,因为这些字符串连接在一段时间后会变成非常昂贵。

但如果你正在这个

textBox.AppendText(latestMessage);

...那么你不应该看到减速,即使有数十万行。但请注意,您将受到一些限制,并且需要自定义TextBox实施。

第一个陷阱是您需要避免访问Text属性。您还需要避免任何可能触发行度量标准重新计算的方法或属性,例如各种行/字符偏移方法(例如GetLineIndexFromCharacterIndex)。

第二个缺陷是您需要覆盖自动化同行。原因很简单:如果Tablet PC输入服务恰好在用户的PC上运行,那么这个小宝石将在你追加文本的任何时候执行:

var peer = UIElementAutomationPeer.FromElement(this) as TextBoxAutomationPeer;
if (peer != null)
{
    if (e.Property == TextProperty)
        peer.RaiseValuePropertyChangedEvent((string)e.OldValue, (string)e.NewValue);
    /* ... */
}

正如您所看到的,它强制在任何更改时读取Text属性。 Text是一个懒惰的属性,假设只能按需计算(尽管上面的代码会抛出一个扳手)。在每次更改时将所有文本范围转换为巨大的字符串会使我们回到第一个示例,这会带来可怕的性能影响。要解决此问题,我们需要扩展TextBox

public class TextBoxEx : TextBox
{
    protected override AutomationPeer OnCreateAutomationPeer()
    {
        return new NoOpAutomationPeer(this);
    }

    private sealed class NoOpAutomationPeer : FrameworkElementAutomationPeer
    {
        private static readonly List<AutomationPeer> EmptyChildren =
            new List<AutomationPeer>();

        public NoOpAutomationPeer([NotNull] FrameworkElement owner)
            : base(owner) {}

        protected override List<AutomationPeer> GetChildrenCore() => EmptyChildren;

        protected override string GetHelpTextCore() => 
            AutomationProperties.GetHelpText(this.Owner);

        protected override string GetNameCore()
        {
            var result = base.GetNameCore();

            if (string.IsNullOrEmpty(result))
                result = GetLabeledByCore()?.GetName();

            if (string.IsNullOrEmpty(result))
                result = GetAutomationIdCore();

            return result;
        }
    }
}

这应解决上面提到的问题,并且还会阻止自动化对等方在其他情况下计算Text属性。请注意,如果您实际使用UI自动化子系统,它可能会给您的自动化测试带来麻烦。我实际上从未使用过它(cue gasps),所以我不完全理解其含义。

ItemsControl Solution

如果您不介意略微限制用户将邮件复制到剪贴板的能力,则可以将虚拟化的ListBox(或其他Selector绑定到多个选择支持)到ObservableCollection条消息。项容器生成器只会为实际可见的项目保留足够的容器,如果启用容器回收,则应该有助于进一步降低内存使用量。要启用回收,您需要定义新的ItemsPanel模板:

<ItemsControl.ItemsPanel>
  <ItemsPanelTemplate>
    <VirtualizingStackPanel VirtualizationMode="Recycling" />
  </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>

这里的一个折衷是用户只能选择整个消息,而TextBox则会选择消息中的任何范围。但是对于大多数日志类型的屏幕,我认为更粗略的选择只会让用户更容易,因为他们通常想要复制整个邮件而不会意外地留下一些字符。