简体   繁体   中英

Winforms: Is there a way to be informed whenever a form gets opened in my application?

I have a "Switch to window" button on my main form that I'd like to be enabled only when other windows (in my app) are open. Is there some sort of event that is raised whenever a form is opened or closed that my main form can hook? (For example, perhaps some sort of way to track when Application.OpenForms has changed?)

(I understand that if this functionality were in a menu item, I could simply do the Application.OpenForms check when the menu was clicked, but this button is not in a menu.)

You could define your own type FooForm which inherits from Form . In the constructor of FooForm , fire a statically defined FormOpened event.

Then, all your forms would have a base type of FooForm instead of Form . The event will be fired each time one of your forms is opened.

I ended up building a window manager class and when a new form is created, it adds itself to the Window Manager's collection. You could encapsulate this functionality in a base Form class so you don't have to remember to do it. Then you can create events in the window manager class to notify you of things like this. You also get the benefit of being able to query the collection of windows in the manager class. I then used this class to be able to consolidate the functionality that builds a menu of open windows into a utility class.

You could use a MessageFilter and monitor for WM_SHOWWINDOW and WM_CLOSE messages.

UPDATE: You can also use a windows hook similar to what's at https://blogs.msdn.microsoft.com/calvin_hsia/2016/11/30/its-easy-to-use-windows-hooks-even-from-c/

Below is code that will write to the Console whenever a Form is opened or closed.


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);
        }
    }
}

A simple global form counter:

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); };
    }
}

The next step is to make a project-width search for InitializeComponent and add one line of code bellow it:

InitializeComponent();
AppForms.Watch(this);

Finally subscribe to the global events FormShown and FormClosed in your main form's constructor.

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

You don't need to worry about your forms not being garbage collected because of the event subscriptions. The class AppForms is a subscriber, and subscribers don't keep references of publishers. Only the publishers keep references of subscribers .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM