65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 WPF 进行诊断跟踪显示

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (19投票s)

2010年2月14日

CPOL

2分钟阅读

viewsIcon

62898

downloadIcon

1170

一个 WPF 控件和一个 FlowDocument,用于在运行的应用程序中显示跟踪输出。

TraceTextBox_src

引言

在应用程序中,通常需要向用户显示持续且详细的状态消息。通过继承 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 中设置样式,并且 TraceDocumentTraceEventType 绑定到名称相似的样式。

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 - 支持多线程
© . All rights reserved.