LogViewer:一个显示日志信息的快速 WPF 控件





5.00/5 (6投票s)
并发线程可以使用 LogViewer 高效地向用户显示可滚动信息
LogViewer 用例
在后台执行一个可能运行数分钟并执行数千个步骤的任务是很常见的。如果在此期间 GUI 没有变化,用户可能会认为应用程序已冻结。另一方面,他可能不需要查看所有数千个步骤,而只需查看少数几个主要步骤和当前步骤。
最简单的方法是只显示主要步骤,但这样用户就不知道许多细节步骤中的哪一个花费了太多时间。但是,如果他看到当前步骤显示了几秒钟,他就会知道出了问题。
由于后台任务可能不知道哪个步骤会花费很长时间并被显示,所以最简单的方法是它可以将所有消息写入 LogViewer
,然后 LogViewer
执行以下功能:
- 接受线程安全(非 WPF 线程)的日志消息
- 区分临时消息和永久消息。临时消息会被任何后续消息覆盖,而永久消息在后台任务完成后仍然会显示给用户。
- 收集在 0.1 秒内收到的所有消息,并将它们一起传输到 WPF 线程。WPF 线程无法每秒更新数百次 GUI,而且用户无论如何也无法读取这些信息。
- 将消息转换为 WPF
Flow
元素。控制字体、大小、边距等可能相当复杂,后台任务的编写者不应该需要了解 WPF。为了指定所需的格式,他只需在消息中传递一个专门为该应用程序在 StyledString.cs 中定义的Document StringStyleEnum
值。
LogViewer 设计
本章介绍了一些设计考虑因素。如果您只对使用 Logviewer 感兴趣,请跳到“在您的应用程序中使用 LogViewer”一章。
多线程的实现
我曾考虑使用一些花哨的线程安全集合来存储消息,但最终我决定只使用简单的列表,并通过后台任务锁定每个 Write()
和通过 WPF 线程锁定每个 Read()
。正如很快解释的那样,Read()
速度极快,每秒只执行 10 次,这意味着几乎不会出现同时锁定。
LogViewer.Write()
获取锁,然后使用“活动”消息缓冲区执行三项活动:
- 如果缓冲区中的最后一条消息是临时消息,则将其删除。
- 将消息和
StringStyleEnum
值添加到活动缓冲区。 - 如果计时器未启动:以 0.1 秒延迟启动计时器,如果调用者实际上已经在 WPF 线程上运行,则立即将消息写入 GUI。
在 wpfTimer
的 Tick
事件中,首先锁定缓冲区,然后切换缓冲区。如果 Write()
在 Tick 发生之前使用 Buffer1
,那么在 Tick 发生之后它将使用 Buffer2
。锁现在被释放,WPF 线程有足够的时间来处理“非活动”缓冲区。完成后,计时器将在另一个 0.1 秒后重新启动。一旦它获得一个空缓冲区,它就会停止重新启动。
支持格式化文本
后台任务通常属于业务层代码,不应依赖 WPF。因此,LogViewer
定义了自己的 StringStyleEnum
。
public enum StringStyleEnum {
none = 0,
normal,
label,
header1,
errorHeader,
errorText,
stats
}
这些值可以根据应用程序的需求而有所不同。文件 StyledString.cs 还包含 static
方法 StyledString.ToInline()
,它将消息及其 StringStyleEnum
值转换为 WPF flowdocument Run
。
case StringStyleEnum.header1:
styledParagraph.Margin = new Thickness(0, 24, 0, 4);
inline = new Bold(new Run(message)) {
FontSize = styledParagraph.FontSize * 1.2
};
break;
向纯字符串添加格式
后台任务使用以下 LogViewer
方法进行写入:
public void WriteLine()
public void WriteLine(string line)
public void WriteTempLine(string line)
public void WriteLine(string line, StringStyleEnum stringStyle)
public void WriteTempLine(string line, StringStyleEnum stringStyle)
public void Write(string text)
public void Write(string text, StringStyleEnum stringStyle)
public void Write(StyledString styledString)
public void Write(params StyledString[] styledStrings)
Write()
包含消息,可能还有一些关于格式和是否需要换行的信息。LogViewer
将其转换为 StyledString
,然后转发到 WPF。
public class StyledString {
public string String { get; private set; }
public StringStyleEnum StringStyle { get; private set; }
public LineHandlingEnum LineHandling { get; private set; }
public DateTime Created { get; private set; }
}
StringStyleEnum
的值对于每个应用程序都不同。LineHandling
可以有三个值:none
、endOfLine
和 temporaryEOL
。基本上,可以使用 Write()
只写入行的一部分,这在日志行的一部分应该是粗体而另一部分不是粗体时很有用,在这种情况下,后台任务将调用 Write()
两次。
标记为 temporaryEOL
的消息只会在下次调用 Write()
之前显示给用户。
在您的应用程序中使用 LogViewer
您可以从 Github 获取最新版本的 LogViewer
。
https://github.com/PeterHuberSg/LogViewer
我将其放在自己的 DLL LogViewerLib
中。我建议您不要将该库链接到您的应用程序中,而是直接复制 LogViewer.cs 和 StyledString.cs 这两个文件。您需要根据应用程序的需求更改 StyledString.cs 的内容,因此最好不要将您的代码与 GitHub 上的 LogViewer
保持同步。
LogViewerTestApp
是我用来测试 LogViewer
的 WPF 应用程序。
测试 LogViewer
看看 LogViewerTestApp
的代码以了解如何将 LogViewer
添加到您的应用程序中可能会很有趣。运行 LogViewerTestApp
可以让您了解 LogViewer
的行为。
Test1
写入一些不同格式的文本。请注意,最后一行“tempLine3
”是临时行。一旦再次按下任何 TestX
按钮,该行将被覆盖。
Test2
尽可能快地写入临时行。令人惊讶的是,LogViewer
每秒可以处理一百万条临时消息!毕竟,它只是将它们收集在 RAM 中,但每 0.1 秒只向 WPF 发送 1 条。
Test3
继续写入新行。这有助于测试用户是否可以停止和继续自动滚动。如果用户想要检查永久行,他可以滚动到该行,这会停止自动滚动。一旦他想再次激活自动滚动,他只需滚动到末尾。
延伸阅读
如果您已经阅读到这里,您可能真的对 WPF 感兴趣,在这种情况下,我强烈推荐我的其他一些 WPF 文章。我感觉这篇文章读起来不是那么有趣,但我的有些文章确实很有帮助,让您深入了解 WPF,这是您在其他地方找不到的。
- 深入 WPF 布局和渲染
- WPF 开发人员必读:WPF 控件测试平台
- WPF 颜色、颜色空间、颜色选择器和为普通人创建自己颜色的权威指南
- 使用绑定进行 WPF DataGrid 格式化的指南
- WPF DataGrid:解决排序、滚动到视图、刷新和焦点问题
- 避免 WPF DataGrid 的限制,用几行您自己的代码替换它
- WPF LinearGradientBrush 参数详解
- WPF 滚动条高级指南或如何显示数百万种颜色组合
- 提高 WPF DispatcherTimer 的精度
我在 Github 上的项目
我还编写了几个开源项目,可以在 Github 上找到。
StorageLib | WPF 单用户应用程序数据库的真正惊人替代品。程序员只需定义他想要使用的 C# 类,代码生成器就会编写所有代码,用于在本地磁盘上永久创建、更新和删除数据。这极大地加快了开发人员的工作,因为他不再需要使用任何 SQL、Entity Framework 等。查询以 Linq 完成,运行速度超快。代码执行速度比任何数据库都快得多。支持事务、备份等等。在多个项目中无错误运行 5 年以上。 |
TracerLib | 高效实时收集多线程数据,这些数据可以存储在文件中,也可以只存储在 RAM 中并在一段时间后丢弃。这对于异常处理很有用,因为它允许告诉用户在异常发生之前发生了什么! |
WpfWindowsLib | 用于数据输入的 WPF 控件,检测是否缺少所需数据或数据是否已更改。 |
最后,但同样重要,一个很棒的游戏
我六年前编写了 MasterGrab,从那时起我几乎每天在开始编程之前都会玩它。在随机地图上击败试图夺取所有 200 个国家的 3 个机器人大约需要 10 分钟。一旦一个玩家拥有所有国家,游戏就结束了。游戏很有趣,每天都充满新鲜感,因为地图每次看起来都完全不同。机器人为游戏带来了一些活力,它们相互竞争,也与人类玩家竞争。如果您愿意,您甚至可以编写自己的机器人,游戏是开源的。我花了大约 2 周时间编写我的机器人,但我惊讶于击败它们有多难。在与它们对战时,必须制定一个策略,让机器人相互攻击而不是攻击您。我迟早会写一篇关于它的 CodeProject 文章,但您现在就可以下载并玩它。应用程序中有很好的帮助解释如何玩。
历史
- 2022 年 9 月 3 日:初始版本
- 2023 年 5 月 23 日:将 WPF 处理从 WpfTimer_Tick 中的锁中移除。详情请参见评论。