如何捕获 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
参数中)以及它们的 wParam
和 lParam
值。要找出您的应用程序的哪一部分发送了特定消息,您可以使用 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; }
}
简介
我有一个代码用 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
参数中)以及它们的 wParam
和 lParam
值。要找出您的应用程序的哪一部分发送了特定消息,您可以使用 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; }
}