2017-02-24 160 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的文檔。