2017-02-24 102 views
0

我正在修改我们在团队中用作构建工具的较早的WPF应用程序。一个组件是一个LoggingWindow类,它包含一个FlowDocument,我们将构建日志文本传递给MSBuild ILogger。在构建完成时,文档通常最终会超过1000页,随着构建的进行接收超过10万行日志文本。由于文本传递量很大,因此表现不佳。这是现在我们如何处理这个问题:将字符串集合绑定到FlowDocument

这是LoggingWindow类的XAML:

<FlowDocumentPageViewer Name="LogPageViewer" Width="Auto" Height="Auto"> 
    <FlowDocument Name="LogDocument" ColumnWidth="800" Foreground="LightGray" Background="Black" FontSize="12" FontFamily="Consolas" TextAlignment="Left"> 
     <FlowDocument.Resources> 
      <Style TargetType="{x:Type Paragraph}"> 
       <Setter Property="Margin" Value="0"></Setter> 
      </Style> 
     </FlowDocument.Resources> 
    </FlowDocument> 
</FlowDocumentPageViewer> 

我们旋转了一个任务轮询消息ConcurrentQueue。正如你所看到的,我不知道如何向文档中添加消息,所以如果有消息,我会分批抓取消息并休眠10毫秒,如果没有消息,则休眠100毫秒不要太多地阻塞主线程。因为这是拥有该FlowDocument的线程

public void Start() 
{ 
    _cts = new CancellationTokenSource(); 
    _uiProcessor = Task.Factory.StartNew(() => UpdateUi(_cts.Token)); 
} 

private void UpdateUi(CancellationToken context) 
{ 
    while (!context.IsCancellationRequested) 
    { 
     if (_messageQueue.Count > 0) 
     { 
      var count = Math.Min(_messageQueue.Count, 10); 
      for (var i = 0; i < count; i++) 
      { 
       LogMessage message; 
       _messageQueue.TryDequeue(out message); 
       AddText(message); 
      } 
      // there are likely to be more messages, so only sleep for 10 ms 
      Thread.Sleep(10); 
     } 
     else 
     { 
      // there aren't likely to be more messages yet, so we can sleep for 100 ms 
      Thread.Sleep(100); 
     } 
    } 
} 

的AddText方法必须在主线程上运行,因此我们需要增加一款之前检查调度。

private void AddText(LogMessage message) 
{ 
    if (Dispatcher.CheckAccess()) 
    { 
     try 
     { 
      var timestampText = $"{message.Timestamp.ToString("MM/dd/yyyy HH:mm:ss.fff")}:{new string(' ', message.Indent * 2)}"; 
      var span = new Span 
      { 
       FontFamily = new FontFamily("Consolas"), 
       FontStyle = FontStyles.Normal, 
       FontWeight = FontWeights.Normal, 
       FontStretch = FontStretches.Normal, 
       FontSize = 12, 
       Foreground = new SolidColorBrush(Color.FromArgb(0xff, 0xd3, 0xd3, 0xd3)) 
      }; 
      span.Inlines.Add(new Run(timestampText) { Foreground = new SolidColorBrush(Colors.White) }); 
      span.Inlines.Add(new Run(message.Message) { Foreground = new SolidColorBrush(message.Color), FontWeight = message.Weight }); 
      var paragraph = new Paragraph(span); 

      LogDocument.Blocks.Add(paragraph); 

      if (AutoScrollMenuItem.IsChecked) 
      { 
       LogPageViewer.LastPage(); 
      } 
     } 
     catch (Exception ex) 
     { 
      _errorIndex++; 
      using (var fs = File.OpenWrite($"FormatError-{_errorIndex:00}.txt")) 
      { 
       var sw = new StreamWriter(fs) 
       { 
        AutoFlush = true 
       }; 

       sw.WriteLine("Error: "); 
       sw.WriteLine(ex); 
       sw.WriteLine(); 

       sw.Write(message.Message); 
       fs.Close(); 
      } 
     } 
    } 
    else 
    { 
     Dispatcher.Invoke(new Action<LogMessage>(AddText), message); 
    } 
} 

我想重构这个的解决方案,使利用WPF数据绑定,这样我可以添加的LogMessage到一个ObservableCollection并推迟实际UI更新WPF,这可能会处理它优于我可以手动。不过,我是WPF新手,甚至更新绑定,所以我不太确定我会如何去做这件事。另外,如果任何人有什么更好的建议,以如何执行我想要做的事情,这将是伟大的。我的目标是能够尽可能地跟上日志消息的添加速度,同时不会阻塞主线程。

回答

0

WPF RichTextBox不支持BindingIEnumerable;但是,如果您使用纯文本,则可能不需要其中一个。相反,你可以使用ItemsControl,具有TextBlockItemTemplateVirtualizingStackPanelItemsPanel

<ItemsControl ItemsSource="{Binding Messages}" > 
    <ItemsControl.ItemTemplate> 
     <DataTemplate> 
      <TextBlock Text="{Binding}" TextWrapping="Wrap" /> 
     </DataTemplate> 
    </ItemsControl.ItemTemplate> 
    <ItemsControl.ItemsPanel> 
     <ItemsPanelTemplate> 
      <VirtualizingStackPanel IsItemsHost="True"/> 
     </ItemsPanelTemplate> 
    </ItemsControl.ItemsPanel> 
</ItemsControl> 

您可以轻松地绑定这个字符串(消息)的一个ObservableCollection。虽然你可以很容易地枚举观察集合,并得到整个文本,它限制了关于文本选择功能,复制粘贴&等

从这个

除此之外,based on this answer,似乎你可以创建一个连接DocumentXaml(或DocumentRTF)属性,这将允许您绑定RichTextBox的文档。