如何捕获 WPF 应用程序中的所有用户操作以将它们存储在日志文件中?

How to capture all user actions in a WPF application to store them in a log file?

简介

我有一个代码用 C# 编写的 WPF 应用程序,开发于 Visual Studio 2013 年。这是一个 GUI 解决方案,有许多相互依赖的项目,包括按钮、字段、菜单、复选框和文本。

记录用户操作

我的目标是创建一个日志文件(一个 txt 文件),记录最终用户在使用此应用程序时执行的所有操作。示例操作可以是单击按钮、在空白文本字段中写入、(de)select 复选框、关闭 activity/tab 等。我想捕获 all 应用程序中的此类用户交互 UI,并将这些操作存储为文本日志文件中操作的总结一行描述。

可能的解决方案

蛮力法

执行此操作的一种方法是使用代码更新所有上述元素(按钮、文本字段...)的事件处理程序,以编写要附加到 txt 日志文件的操作摘要。例如,在接受按钮单击处理程序中:

 private void buttonAccept_Click(object sender, EventArgs e)
 {
     // TODO: default button click handler code...

     // TODO: ...and the code to write the event to a txt log file         
 }

但是这个策略实现起来会非常痛苦,必须更新每个和每个事件处理程序(甚至为当前没有的元素编写事件处理程序)。

更好的方法

我正在寻找一种更简单的方法来实现这一点。我的一些想法是:

一种方法是更改​​ 事件处理程序基础 class(所有其他事件处理程序都继承自?)以包含日志文件编写代码。这样日志文件的代码就只需要写一次,每当调用any事件处理程序时都会调用它,这就是我们想要的(需要区分一个用户生成的事件和一个应用程序生成的事件,这在 事件链接 的情况下可能会很困难。因此这可能是一个挑战。

另一种方法是使用一些“嗅探器”,它像调试器一样工作,并在用户移动时存储应用程序方法的堆栈跟踪。这只会让我们对用户的工作流程有部分了解。

    private void buttonAccept_Click(object sender, EventArgs e)
    private bool IsValidName(string userName)
    private void buttonCancel_Click(object sender, EventArgs e)
    private void Close()
    ...

在消息中添加时间戳会很有用...

    3/1/2015 18:19 private void buttonAccept_Click(object sender, EventArgs e)
    3/1/2015 18:19 private bool IsValidName(string userName)
    3/1/2015 18:21 private void buttonCancel_Click(object sender, EventArgs e)
    3/1/2015 18:21 private void Close()
    ...

... 因为,删除与前一条消息的时间戳差异低于合适阈值的消息,将只留下用户操作直接调用的方法。假设方法签名具有足够的描述性,则此消息列表可以用作所需的日志文件。

    3/1/2015 18:19 private void buttonAccept_Click(object sender, EventArgs e)
    3/1/2015 18:21 private void buttonCancel_Click(object sender, EventArgs e)
    ...

但是,这仅适用于具有与之关联的事件处理程序的元素。尽管如此,它还是比蛮力法好。

建议?

我在想出实现这些想法的方法时遇到了一些困难。谁能详细说明如何实施?或者,如果您能想出更好的方法来制作日志文件,请分享您的想法以及如何开发它。谢谢!

查看 Microsoft UI Automation to see if it meets your needs. It includes the ability to be notified of events,它看起来像您想要的。

最简单直接的方法是拦截应用程序的所有 windows 消息并存储您需要的消息所需的信息。

实现此目的最直接的方法是制作 "message filter" (Application.AddMessageFilter)。它看起来像这样:

public class GlobalMessageLogger : IMessageFilter{

    private const int WM_LBUTTONDOWN = 0x201;

    public bool PreFilterMessage(ref Message message){
        if (message.Msg == WM_LBUTTONDOWN) {
            //Log message
        }
        return false;
    }
}

那么你可以这样做:

Application.AddMessageFilter(new GlobalMessageLogger());

您可以使用 Message struct 成员(message 参数)获取有关特定消息的更多信息。您必须阅读特定的 Windows 消息、它们的代码(存储在 Msg 参数中)以及它们的 wParamlParam 值。要找出您的应用程序的哪一部分发送了特定消息,您可以使用 HWnd 属性.

这是一种非常低级的方法,但它为您提供了极大的灵活性(您不限于 UI 实际上具有事件处理程序的元素),并且针对日志记录目的进行了高度本地化的更改。

所以,它是 WPF;如果写得正确,它也是 MVVM。然后您可以 装饰 您的视图模型并捕获所有更改的绑定。下面的例子将证明这一点。对于按钮点击,您可以使用类似的方法,除了您将拦截 ICommand 实例并监听它们的事件。

例子

例如,假设您定义了常规视图模型(伪代码),它具有双向 属性 调用 SimpleProperty:

class SimpleViewModel : IViewModel
{
    public string SimpleProperty { get; set; }
}

要添加日志记录,请定义一个装饰器,如下所示(同样是伪代码):

class LoggerViewModel : IViewModel
{
    private readonly IViewModel _originalVM;
    private readonly static Logger _logger = new Logger(); 

    public LoggerViewModel(IViewModel originalVM) 
    {
        _originalVM = originalVM;
    }

    public string SimpleProperty 
    {
        get 
        {
            _logger.Log("Page requested SimplePriperty and value is " + _originalVM.SimpleProperty);
            return _originalVM.SimpleProperty;
        }
        set
        {
            _logger.Log("Page changed SimpleProperty and value is " + _originalVM.SimpleProperty);
            _originalVM.SimpleProperty = value;
        }
    }
}

很明显,IViewModel 界面是这样的:

interface IViewModel
{
    string SimpleProperty { get; set; }
}