【问题标题】:Winforms: Is there a way to be informed whenever a form gets opened in my application?Winforms:有没有办法在我的应用程序中打开表单时得到通知?
【发布时间】:2009-10-21 21:01:00
【问题描述】:

我的主窗体上有一个“切换到窗口”按钮,我希望仅在其他窗口(在我的应用程序中)打开时启用该按钮。每当打开或关闭我的主表单可以挂钩的表单时,是否会引发某种事件? (例如,也许某种方式可以跟踪 Application.OpenForms 何时发生变化?)

(我知道如果此功能在菜单项中,我可以简单地在单击菜单时执行 Application.OpenForms 检查,但此按钮不在菜单中。)

【问题讨论】:

  • 主窗体和其他窗口有什么关系?其他窗口可以在加载/关闭时引发事件以便订阅主窗体吗?

标签: c# .net winforms events


【解决方案1】:

您可以定义自己的类型FooForm,它继承自Form。在FooForm的构造函数中,触发一个静态定义的FormOpened事件。

然后,您的所有表单都将具有FooForm 的基本类型,而不是Form。每次打开您的一个表单时都会触发该事件。

【讨论】:

  • 感谢您的建议。在这一点上,我必须看看这是否可行;我想确保我没有错过执行此操作的内置方法。
【解决方案2】:

我最终构建了一个窗口管理器类,当创建一个新表单时,它会将自己添加到窗口管理器的集合中。您可以将此功能封装在 Form 基类中,这样您就不必记住要这样做。然后你可以在窗口管理器类中创建事件来通知你这样的事情。您还可以获得能够在管理器类中查询窗口集合的好处。然后,我使用该类将构建打开窗口菜单的功能整合到实用程序类中。

【讨论】:

  • 谢谢。考虑到时间,我可能会走这条路,但不幸的是,该应用程序没有考虑到这一点,而且非常大,截止日期迫在眉睫。我想我会问一下,看看是否有不需要改变架构的方法:)
【解决方案3】:

您可以使用MessageFilter 并监视 WM_SHOWWINDOW 和 WM_CLOSE 消息。

更新:您还可以使用类似于 https://blogs.msdn.microsoft.com/calvin_hsia/2016/11/30/its-easy-to-use-windows-hooks-even-from-c/ 的 Windows 挂钩

以下是每当打开或关闭表单时将写入控制台的代码。


using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

namespace WindowsFormsApp1
{
    class Form1 : Form
    {
        public Form1()
        {
            var newWindowBtn = new Button { Text = "New Window" };
            newWindowBtn.Click += (s, e) => new Form { Text = Guid.NewGuid().ToString() }.Show(this);
            Controls.Add(newWindowBtn);
        }
    }

    static class NativeMethods
    {
        [StructLayout(LayoutKind.Sequential)]
        public struct CWPSTRUCT
        {
            public IntPtr lparam;
            public IntPtr wparam;
            public int message;
            public IntPtr hwnd;
        }

        public delegate IntPtr CBTProc(int code, IntPtr wParam, IntPtr lParam);

        public enum HookType
        {
            WH_CALLWNDPROC = 4,
        }

        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnhookWindowsHookEx(IntPtr hookPtr);

        [DllImport("kernel32.dll")]
        public static extern uint GetCurrentThreadId();

        [DllImport("user32.dll")]
        public static extern IntPtr CallNextHookEx(IntPtr hookPtr, int nCode, IntPtr wordParam, IntPtr longParam);

        [DllImport("user32.dll")]
        public static extern IntPtr SetWindowsHookEx(HookType hookType, CBTProc hookProc, IntPtr instancePtr, uint threadID);
    }

    static class Program
    {
        [STAThread]
        static void Main()
        {
            var hook = NativeMethods.SetWindowsHookEx(NativeMethods.HookType.WH_CALLWNDPROC, Callback, IntPtr.Zero, NativeMethods.GetCurrentThreadId());
            try
            {
                Application.Run(new Form1());
            }
            finally
            {
                NativeMethods.UnhookWindowsHookEx(hook);
            }
        }

        static IntPtr Callback(int code, IntPtr wParam, IntPtr lParam)
        {
            var msg = Marshal.PtrToStructure(lParam);
            if (msg.message == 0x0018)//WM_SHOWWINDOW
            {
                var form = Control.FromHandle(msg.hwnd) as Form;
                if (form != null)
                {
                    Console.WriteLine($"Opened form [{form.Handle}|{form.Text}]");
                }
            }
            if (msg.message == 0x0010)//WM_CLOSE
            {
                var form = Control.FromHandle(msg.hwnd) as Form;
                if (form != null)
                {
                    Console.WriteLine($"Closed form [{form.Handle}|{form.Text}]");
                }
            }
            return NativeMethods.CallNextHookEx(IntPtr.Zero, code, wParam, lParam);
        }
    }
}

【讨论】:

  • 我在 Windows 7 和 .Net 4.5.1 上进行了尝试,但没有看到任何 WM_SHOWWINDOW 消息。你能让这个工作吗?
  • @Loring 我更新了答案以提供使用 Windows 挂钩的替代解决方案。
【解决方案4】:

一个简单的全局表单计数器:

public static class AppForms
{
    public static int OpenForms { get; private set; }
    public static event EventHandler FormShown;
    public static event EventHandler FormClosed;
    public static void Watch(Form form)
    {
        form.Shown += (sender, e) => { OpenForms++; FormShown?.Invoke(sender, e); };
        form.Closed += (sender, e) => { OpenForms--; FormClosed?.Invoke(sender, e); };
    }
}

下一步是在项目宽度上搜索InitializeComponent,并在下面添加一行代码:

InitializeComponent();
AppForms.Watch(this);

最后在主窗体的构造函数中订阅全局事件FormShownFormClosed

AppForms.FormShown += (sender, e) =>
{
    this.Text = $"OpenForms: {AppForms.OpenForms}";
};
AppForms.FormClosed += (sender, e) =>
{
    this.Text = $"OpenForms: {AppForms.OpenForms}";
};

您不必担心您的表单不会因为事件订阅而被垃圾收集。 AppForms 类是订阅者,订阅者不保留发布者的引用。 Only the publishers keep references of subscribers.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-05
    • 1970-01-01
    • 2011-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-09-21
    相关资源
    最近更新 更多