2017-02-24 4 views
0

私はチームのビルドツールとして使用する古いWPFアプリケーションを変更しています。 1つのコンポーネントは、MSBuild ILoggerを介してビルドログテキストを渡すFlowDocumentを含むLoggingWindowクラスです。ビルドが完了する頃には、典型的には1000ページを超えることになり、ビルドが進むにつれて100k行以上のログテキストを受け取ることになります。非常に大量のテキストが渡されるため、うまく機能しません。これは、私たちが今、これを処理する方法である:文字列のコレクションを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のバッチでメッセージを取得し、メッセージがある場合は10 msの間スリープし、そうでない場合は100 msの間スリープしますメインスレッドをあまりにもブロックしないようにします。それは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メソッドは、メインスレッド上で実行する必要がありますので、私たちは、段落を追加する前に、ディスパッチャをチェックする必要があります。

私はのObservableCollectionにLogMessageにを追加して、WPFに実際のUIの更新を延期、おそらくより良いそれを扱うなることができるように、バインディングWPFデータを利用した溶液に、これをリファクタリングしたいと思います
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の初心者であり、バインディングに新しい方でもあるので、どうやってこれをやっていくのかは分かりません。

また、誰かが私がやろうとしていることを能動的に行う方法についてより良い提案があれば、それは素晴らしいことです。私の目標は、メインスレッドをブロックするのではなく、可能な限り多くのログメッセージのペースに追いつくことができるようにすることです。

答えて

0

WPF RichTextBoxBindingからIEnumerableをサポートしません。ただし、プレーンテキストで作業している場合は、必要ないかもしれません。代わりに、あなたはそのItemTemplateとしてTextBlock、及びそのItemsPanelなどVirtualizingStackPanelで、ItemsControlを使用することができます。あなたは簡単に文字列(メッセージ)ののObservableCollectionにこれをバインドでき

<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> 

。あなたが容易に観察コレクションを列挙し、全体のテキストを取得することができますが、テキストの選択に関するそれは限られた機能は、&貼り付け等、このから離れて

based on this answerをコピーし、あなたが(添付DocumentXamlを作成することができるようですまたはDocumentRTF)プロパティを使用して、RichTextBoxのドキュメントをバインドできます。

関連する問題