*在*触发设计器生成的事件之前记录 WinForm 按钮单击

Met*_*450 1 .net c# events delegates winforms

我正在尝试找出一种方法,让我的 WinForms 应用程序将每次按钮单击记录到文件中。到目前为止,我找到的最佳解决方案(来自Log all button clicks in Win Forms app)如下所示:

public class ButtonLogger
{
    public static void AttachButtonLogging(ControlCollection controls)
    {
        foreach (var control in controls.Cast<Control>())
        {
            if (control is Button)
            {
                Button button = (Button)control;
                button.Click += LogButtonClick;
            }
            else
            {
                AttachButtonLogging(control.Controls);
            }
        }
    }

    private static void LogButtonClick(object sender, EventArgs eventArgs)
    {
        Button button = sender as Button;
        WriteLog("Click: " + button.Parent.Name.ToString() + "." + button.Name.ToString() + " (\"" + button.Text + "\")");
    }

    private static void WriteLog(string message)
    {
        //...
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在每个表单的构造函数的末尾:

ButtonLogger.AttachButtonLogging(this.Controls);
Run Code Online (Sandbox Code Playgroud)

虽然这是一个很好的方法,但它有 3 个主要缺点,所有这些缺点都是由于日志事件总是在“真实”事件(由设计者创建)之后发生:

1) 如果单击按钮打开一个模式对话框,则该对话框中的单击将在父级单击之前被记录(也称为日志显示无序)。这是因为只有在模式对话框关闭后(即在该对话框内单击之后)才会记录父级的单击。

2) 同样,如果该模式对话框中的某些操作导致应用程序崩溃,则打开它的单击将永远不会被记录,从而使其看起来好像崩溃发生在父级中。

3)如果单击按钮关闭当前对话框,则button.Parent将为空(因为记录它的事件将在对话框已被“第一个”事件关闭后被触发)。因此,我们无法记录“关闭”点击来自的表单的名称。

我一直在绞尽脑汁寻找一种颠倒顺序的方法(因此日志事件在“真实”事件之前被触发),但无法想出任何不妨碍使用 Designer 的方法。问题是设计器在 InitializeComponents() 中创建并分配事件,而我们无法编辑这些事件。

对可能(不成功)解决方案的思考:

  • 从设计器在按钮创建和添加事件之间放置的内容调用 AttachButtonLogging()。然而,唯一的事情是调用 SuspendLayout(),它不是虚拟的,因此不能被覆盖(此时按钮尚未添加到 ControlCollection 中)。

  • 让 AttachButtonLogging() 删除每个按钮的现有事件,添加日志事件,然后重新添加原始事件。但是,您无法从类外部枚举该类的事件(即 AttachbuttonLogging 无法枚举按钮的事件)。

似乎这样的事情应该可以通过一些创造性的思维来实现,但我还没有什么想法。注意:目标是在构建新对话框时以一种不麻烦的方式记录点击- 类似于上面的解决方案,但没有提到的三个陷阱。

Idl*_*ind 5

只需将IMessageFilter()Application.AddMessageFilter()结合使用即可在消息发送到您的应用程序之前获取消息。这适用于应用程序所有形式的所有按钮(无论它们嵌套多深)。它甚至适用于在运行时动态添加的按钮。 最好的部分是,您根本不需要对现有代码和控件进行任何更改。 您所要做的就是从启动表单的 Load() 事件中添加一次消息过滤器:

public partial class Form1 : Form
{

    public Form1()
    {
        InitializeComponent();
    }

    private void Form1_Load(object sender, EventArgs e)
    {
        Application.AddMessageFilter(new ButtonLogger());
    }

}

public class ButtonLogger : IMessageFilter
{

    private const int WM_KEYUP = 0x101;
    private const int WM_LBUTTONUP = 0x202;

    public bool PreFilterMessage(ref Message m)
    {
        if (m.Msg == WM_LBUTTONUP || (m.Msg == WM_KEYUP && ((int)m.WParam == 32 || (int)m.WParam == 13)))
        {
            Control ctl = Control.FromHandle(m.HWnd);
            if (ctl is Button)
            {
                LogButtonClick((Button)ctl);
            }
        }
        return false; // allow normal processing of all messages
    }

    private void LogButtonClick(Button btn)
    {
        WriteLog("Click: " + btn.Parent.Name.ToString() + "." + btn.Name.ToString() + " (\"" + btn.Text + "\")");
    }

    private void WriteLog(string message)
    {
        Console.WriteLine(message);
    }

}
Run Code Online (Sandbox Code Playgroud)