使用 WPF 进行诊断跟踪显示






4.93/5 (19投票s)
一个 WPF 控件和一个 FlowDocument,用于在运行的应用程序中显示跟踪输出。
引言
在应用程序中,通常需要向用户显示持续且详细的状态消息。通过继承 TraceListener
并将跟踪输出重定向到 UI 中的某个位置,可以非常简单地实现此目的。本文介绍了一种捕获跟踪输出并在 WPF 用户界面中显示它的机制。
背景
我有一个 WinForms 版本的程序,已经使用了很长时间,非常方便。在 WPF 中复制这个程序比我想象的要复杂一些,这主要是因为 WPF 的学习曲线比较陡峭。最终,WPF FlowDocument
使应用格式化跟踪输出变得轻而易举,尤其与旧的 Win32 RichTextBox
相比。
Using the Code
附带的代码包含两种将跟踪输出重定向到 UI 的机制。
TraceTextBoxControl
这是一个简单的 UserControl,可以像任何其他控件一样放置在 UI 中。当它在视觉树中加载时,它会创建一个 TraceListener
并立即开始以简单的黑白文本显示跟踪输出。
<local:TraceTextBoxControl>
这是上面截图中的最顶部的文本框。
TraceDocument
如果您需要超过黑白文本的格式化,可以使用 TraceDocument
。这个类继承自 FlowDocument
,可以在任何可以显示 FlowDocument
的地方使用。
在 RichTextBox
中(中间窗口)
<RichTextBox IsReadOnly="True" AllowDrop="False"
IsUndoEnabled="False"> <local:TraceDocument/>
</RichTextBox>
或者在 FlowDocumentReader
中(底部窗口)
<FlowDocumentReader ViewingMode="Scroll">
<local:TraceDocument/>
</FlowDocumentReader>
FlowDocumentReader
包含内置的搜索和缩放功能。
使用 FlowDocument
的好处在于,不同类型的跟踪消息可以在 XAML 中设置样式,并且 TraceDocument
将 TraceEventType
绑定到名称相似的样式。
public enum TraceEventType
{
Critical = 1,
Error = 2,
Warning = 4,
Information = 8,
Verbose = 16,
Start = 256,
Stop = 512,
Suspend = 1024,
Resume = 2048,
Transfer = 4096,
}
<FlowDocument x:Class="TraceTextBox.TraceDocument"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
FontSize="12" FontFamily="Courier New" PageWidth="10000" >
<FlowDocument.Resources>
<Style TargetType="{x:Type Paragraph}" x:Key="Information">
<Setter Property="Margin" Value="0"/>
<Setter Property="KeepTogether" Value="True"/>
</Style>
<Style TargetType="{x:Type Paragraph}" x:Key="Error">
<Setter Property="Margin" Value="0"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="KeepTogether" Value="True"/>
</Style>
<Style TargetType="{x:Type Paragraph}" x:Key="Warning">
<Setter Property="Margin" Value="0"/>
<Setter Property="Foreground" Value="Red"/>
<Setter Property="KeepTogether" Value="True"/>
</Style>
<Style TargetType="{x:Type Paragraph}" x:Key="Fail">
<Setter Property="Margin" Value="0"/>
<Setter Property="Foreground" Value="Fuchsia"/>
<Setter Property="FontWeight" Value="Bold"/>
<Setter Property="KeepTogether" Value="True"/>
</Style>
</FlowDocument.Resources>
</FlowDocument>
理解代码
TraceTextSource
是从 TraceListener
派生的类,用于捕获跟踪输出并将其代理到 ITraceTextSink
。
interface ITraceTextSink
{
void Fail(string msg);
void Event(string msg, TraceEventType eventType);
}
class TraceTextSource : TraceListener
{
public ITraceTextSink Sink { get; private set; }
private bool _fail;
private TraceEventType _eventType = TraceEventType.Information;
public TraceTextSource(ITraceTextSink sink)
{
Debug.Assert(sink != null);
Sink = sink;
}
public override void Fail(string message)
{
_fail = true;
base.Fail(message);
}
public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message)
{
_eventType = eventType;
base.TraceEvent(eventCache, source, eventType, id, message);
}
public override void Write(string message)
{
if (IndentLevel > 0)
message = message.PadLeft(IndentLevel + message.Length, '\t');
if (_fail)
Sink.Fail(message);
else
Sink.Event(message, _eventType);
_fail = false;
_eventType = TraceEventType.Information;
}
public override void WriteLine(string message)
{
Write(message + "\n");
}
}
TraceTextSource
还会连接到 UI(例如,使用 TraceDocument
)。
TraceListener listener = new TraceTextSource(new TraceDocument());
Trace.Listeners.Add(listener);
ITraceTextSink
的实现者负责在 UI 中适当地显示跟踪输出流。
public partial class TraceDocument : FlowDocument, ITraceTextSink
{
private delegate void AppendTextDelegate(string msg, string style);
private TraceListener _listener;
public TraceDocument()
{
AutoAttach = true;
InitializeComponent();
}
public bool AutoAttach { get; set; }
public void Event(string msg, TraceEventType eventType)
{
Append(msg, eventType.ToString());
}
public void Fail(string msg)
{
Append(msg, "Fail");
}
private void Append(string msg, string style)
{
if (Dispatcher.CheckAccess())
{
Debug.Assert(Blocks.LastBlock != null);
Debug.Assert(Blocks.LastBlock is Paragraph);
var run = new Run(msg);
run.Style = (Style)(Resources[style]);
if (run.Style == null)
run.Style = (Style)(Resources["Information"]);
((Paragraph)Blocks.LastBlock).Inlines.Add(run);
ScrollParent(this);
}
else
{
Dispatcher.Invoke(new AppendTextDelegate(Append), msg, style);
}
}
private static void ScrollParent(FrameworkContentElement element)
{
if (element != null)
{
if (element.Parent is TextBoxBase)
((TextBoxBase)element.Parent).ScrollToEnd();
else if (element.Parent is ScrollViewer)
((ScrollViewer)element.Parent).ScrollToEnd();
else
ScrollParent(element.Parent as FrameworkContentElement);
}
}
private void Document_Loaded(object sender, RoutedEventArgs e)
{
if (AutoAttach && _listener == null)
{
_listener = new TraceTextSource(this);
Trace.Listeners.Add(_listener);
}
}
private void Document_Unloaded(object sender, RoutedEventArgs e)
{
if (_listener != null)
{
Trace.Listeners.Remove(_listener);
_listener.Dispose();
_listener = null;
}
}
}
历史
- 2010/2/14 - 初始上传。
- 2012/8/25 - 支持多线程