【问题标题】:Winforms Static HandleCreated or OnLoad EventWinforms 静态 HandleCreated 或 OnLoad 事件
【发布时间】:2018-11-06 07:37:25
【问题描述】:

在创建 Winforms 控件的句柄或首次加载它时是否会触发任何 静态 事件?

这是一个人为的例子:

using (Form f1 = new Form())
{
    f1.HandleCreated += (sender, args) => { MessageBox.Show("hi"); };
    f1.ShowDialog();
}
using (Form f2 = new Form())
{
    f2.HandleCreated += (sender, args) => { MessageBox.Show("hi"); };
    f2.ShowDialog();
}

这就是我想要的:

Form.StaticHandleCreated += (sender, args) => { MessageBox.Show("hi"); };
using (Form f1 = new Form())
{
    f1.ShowDialog();
}
using (Form f2 = new Form())
{
    f2.ShowDialog();
}

(我想要这个是因为我有几百个控件,并且需要自定义第三方控件的默认行为,并且供应商没有提供更好的方法。他们专门说要处理OnLoad事件来进行自定义,但这是一项巨大的工作量。)


感谢 xtu,这是一个工作版本,它不会在必要时保留表单,以避免内存泄漏。

public class WinFormMonitor : IDisposable
{
    private readonly IntPtr _eventHook;
    private List<Form> _detectedForms = new List<Form>();

    public event Action<Form> NewFormDetected;

    private WinEventDelegate _winEventDelegate;

    public WinFormMonitor()
    {
        _winEventDelegate = WinEventProc;

        _eventHook = SetWinEventHook(
            EVENT_OBJECT_CREATE,
            EVENT_OBJECT_CREATE,
            IntPtr.Zero,
            _winEventDelegate,
            0,
            0,
            WINEVENT_OUTOFCONTEXT);
    }

    public void Dispose()
    {
        _detectedForms.Clear();
        UnhookWinEvent(_eventHook);
    }

    private void WinEventProc(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        if (idObject != 0 || idChild != 0) return;
        var currentForms = Application.OpenForms.OfType<Form>().ToList();
        var newForms = currentForms.Except(_detectedForms);
        foreach (var f in newForms)
        {
            NewFormDetected?.Invoke(f);
        }
        _detectedForms = currentForms;
    }


    private const uint EVENT_OBJECT_CREATE = 0x8000;
    private const uint WINEVENT_OUTOFCONTEXT = 0;

    private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
            hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
        uint idThread, uint dwFlags);

    [DllImport("user32.dll")]
    private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
}

【问题讨论】:

  • 在Form基类中实现这个怎么样?那么任何继承 Form 基类的 Form 都会以这种方式运行。
  • 这是一个选项 - 在解决方案中查找和替换会起作用,但我希望有一个更易于维护的选项,因为当我们制作新的 UserControl 时,我们需要记住使用另一个班级。
  • 不,没有静态事件,但是这个问题有两种不同的解决方案 - 一种是创建一个基本表单,其中每个具有这些控件的表单都继承自它,另一种是将所有自定义代码封装在不同的类中,并在包含这些控件的所有表单中引用它。没有更多细节,我更喜欢第二种选择。
  • @ZoharPeled,是的,我们经常使用后者,但是当我们添加新控件时,我们必须始终记住添加装饰器。我估计在过去的一年中我花了 8 个小时来寻找缺少装饰器的控件并为它们创建票证。我希望有一些“有效”的东西。
  • 一年中的 8 小时似乎并不多....

标签: c# winforms


【解决方案1】:

我受到this answer 的启发,使用SetWinEventHook API 创建了一个新的WinForm 监视器。基本上,它监视EVENT_OBJECT_CREATE 事件并在找到新窗口时触发一个事件。

用法很简单。您创建一个新实例,然后将处理程序附加到NewFormCreated 事件。这是一个例子:

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    using (var monitor = new WinFormMonitor())
    {
        monitor.NewFormCreated += (sender, form) => { MessageBox.Show($"hi {form.Text}"); };

        Application.Run(new Form1());
    }
}

这里是WinFormMonitor的来源:

public class WinFormMonitor : IDisposable
{
    private readonly IntPtr _eventHook;
    private readonly IList<int> _detectedFormHashes = new List<int>();

    public event EventHandler<Form> NewFormCreated = (sender, form) => { };

    public WinFormMonitor()
    {
        _eventHook = SetWinEventHook(
            EVENT_OBJECT_CREATE,
            EVENT_OBJECT_CREATE,
            IntPtr.Zero,
            WinEventProc,
            0,
            0,
            WINEVENT_OUTOFCONTEXT);
    }

    public void Dispose()
    {
        _detectedFormHashes.Clear();
        UnhookWinEvent(_eventHook);
    }

    private void WinEventProc(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        // filter out non-HWND namechanges... (eg. items within a listbox)
        if (idObject != 0 || idChild != 0) return;
        if (!TryFindForm(hwnd, out var foundForm)) return;

        RaiseIfNewFormFound(foundForm);
    }

    private void RaiseIfNewFormFound(Form foundForm)
    {
        var formHash = foundForm.GetHashCode();
        if (_detectedFormHashes.Contains(formHash)) return;

        NewFormCreated(this, foundForm);
        _detectedFormHashes.Add(formHash);
    }

    private static bool TryFindForm(IntPtr handle, out Form foundForm)
    {
        foreach (Form openForm in Application.OpenForms)
        {
            if (openForm.Handle != handle) continue;
            foundForm = openForm;
            return true;
        }

        foundForm = null;
        return false;
    }

    private const uint EVENT_OBJECT_CREATE = 0x8000;
    private const uint WINEVENT_OUTOFCONTEXT = 0;

    private delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType,
        IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    private static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr
            hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess,
        uint idThread, uint dwFlags);

    [DllImport("user32.dll")]
    private static extern bool UnhookWinEvent(IntPtr hWinEventHook);
}

【讨论】:

  • 谢谢,这很好 - 你为我节省了很多时间。我只是做了一些调整,不保留所有表单以便它们可以被 GC,并为 WinEventProc 回调创建了一个成员变量,以避免调试器抛出异常。
  • @MineR 您将不胜感激分享您的修改!
  • @BillW,我已经添加了我在问题中使用的版本。
  • @MineR 感谢您的提示!我已经更新了答案中的代码,因此它现在不会缓存表单列表,而是缓存以前看到的表单的哈希码列表。
猜你喜欢
  • 2011-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-08
  • 2010-12-27
  • 2016-09-13
  • 2021-09-14
相关资源
最近更新 更多