覆盖默认窗口/系统菜单

时间:2015-08-19 21:18:31

标签: windows winforms winapi

我想在所有窗口的系统菜单中添加“Always-On-Top”-menuentry(当您右键单击标题栏或单击图标时会打开的菜单)。我更喜欢C#或C ++,但如果最坏的情况发生,我也会使用VB ......

我知道像Dexpot这样的应用程序可以做到这一点,但我无法找到有用的源代码或免费应用程序,它们可以为所有窗口而不仅仅是自己的窗口执行此操作。 我也知道还有其他方法可以实现这个功能(AutoHotkeys或小程序,它们存在于系统托盘中,让你选择应该保持在顶部的窗口),但我正在寻找一种更流畅和直观的方式。理想情况下,我会在标题栏上添加一个小针脚按钮,但我的猜测是更多涉及,所以我现在会坚持使用菜单变体。

想法?谢谢!

2 个答案:

答案 0 :(得分:1)

使用AddMenuItems()方法并使用MS-Paint进行测试。我注意到的一件事是程序关闭后,修改后的系统菜单变得不稳定。可能这是因为事件不是来自过程' UI线程。可以在ApplicationExit事件中调用GetMenu(hMainWindowHandle, true),其中true表示还原菜单。

public static class AlwaysOnTop {

    static AlwaysOnTop() {
        Application.ApplicationExit += delegate {
            try {
                foreach (DictionaryEntry de in htThreads) {
                    Hook h = (Hook) de.Value;
                    RemoveMenu(h.hMenu, h.uniqueId, 0);
                    //DeleteMenu(h.hMenu, h.uniqueId, 0);
                    UnhookWinEvent(h.hWinEventHook);
                }
            } catch {
            }
        };
    }

    private const int EVENT_OBJECT_INVOKED = 0x8013;
    private const int OBJID_SYSMENU = -1;
    private const int WINEVENT_OUTOFCONTEXT = 0;
    private const int MF_STRING = 0x00000000;
    private const int HWND_TOPMOST = -1;
    private const int HWND_NOTOPMOST = -2;
    private const int SWP_NOMOVE = 0x0002;
    private const int SWP_NOSIZE = 0x0001;
    private const uint MF_UNCHECKED = 0x00000000;
    private const uint MF_CHECKED = 0x00000008;

    [DllImport("user32.dll")]
    private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);

    [DllImport("user32.dll")]
    private static extern bool AppendMenu(IntPtr hMenu, uint uFlags, uint uIDNewItem, String lpNewItem);

    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

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

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern int UnhookWinEvent(IntPtr hWinEventHook);

    [DllImport("user32.dll", SetLastError=true)]
    private static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);

    [DllImport("user32.dll")]
    private static extern bool CheckMenuItem(IntPtr hMenu, uint uIDCheckItem, uint uCheck);

    [DllImport("user32.dll")]
    private static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);

    //[DllImport("user32.dll")]
    //private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);

    private static Hashtable htThreads = new Hashtable();
    private static WinEventProc CallWinEventProc = new WinEventProc(EventCallback);
    private delegate void WinEventProc(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime);
    private static void EventCallback(IntPtr hWinEventHook, int iEvent, IntPtr hWnd, int idObject, int idChild, int dwEventThread, int dwmsEventTime) {
        //callback function, called when message is intercepted
        if (iEvent == EVENT_OBJECT_INVOKED) {
            if (idObject == OBJID_SYSMENU) {
                Hook h = (Hook) htThreads[(uint) dwEventThread];
                if (h != null && h.uniqueId == idChild) {
                    bool b = !h.Checked;
                    if (b)
                        SetWindowPos(h.hMainWindowHandle, (IntPtr) HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);
                    else
                        SetWindowPos(h.hMainWindowHandle, (IntPtr) HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE);

                    CheckMenuItem(h.hMenu, h.uniqueId, (b ? MF_CHECKED : MF_UNCHECKED));
                    h.Checked = b;
                }
            }
        }
    }

    private class Hook {
        public uint uniqueId = 1001;
        public IntPtr hWinEventHook;
        public IntPtr hMenu;
        public IntPtr hMainWindowHandle;
        public bool Checked;
    }

    public static void AddMenuItems() {
        Process[] arr = Process.GetProcesses();
        foreach (Process p in arr) {
            if (p.MainWindowHandle == IntPtr.Zero)
                continue;

            if (p.ProcessName != "mspaint") // <-- remove or change this line
                continue;

            IntPtr hMenu = GetSystemMenu(p.MainWindowHandle, false);
            if (hMenu == IntPtr.Zero)
                continue;

            bool b = AppendMenu(hMenu, MF_STRING, 1001, "Always On Top");
            uint pid = 0;
            uint tid = GetWindowThreadProcessId(p.MainWindowHandle, out pid);

            Hook h = (Hook) htThreads[tid];
            if (h == null) {
                h = new Hook();
                h.hMenu = hMenu;
                h.hWinEventHook = SetWinEventHook(EVENT_OBJECT_INVOKED, EVENT_OBJECT_INVOKED, IntPtr.Zero, CallWinEventProc, pid, tid, WINEVENT_OUTOFCONTEXT);
                h.hMainWindowHandle = p.MainWindowHandle;
                htThreads[tid] = h;
            }
        }
    }
}

答案 1 :(得分:0)

以下是使用ToolStripDropDown代替默认窗口菜单的示例。通过设置背景颜色,字体并在需要时添加一些图标,可以使其看起来更像默认窗口菜单。

隐藏默认窗口菜单显然更加困难。也许有更好的方法。隐藏菜单的失败尝试留在下面的代码中。右键单击标题栏时仍会显示默认菜单。

public class FormCustomMenu : Form {

    WindowMenu WindowMenu = new WindowMenu();

    public FormCustomMenu() {
        //this.ShowIcon = false;
    }

    private const int WM_INITMENU = 0x116;
    private const int WM_INITMENUPOPUP = 0x117;
    private const int WM_SYSCOMMAND = 0x112;
    protected override void WndProc(ref Message m) {
        if (m.Msg == WM_SYSCOMMAND) { //WM_INITMENU || m.Msg == WM_INITMENUPOPUP) {}
            Point pt = Cursor.Position;
            int h = SystemInformation.CaptionHeight;
            Rectangle r = new Rectangle(this.Location, new Size(h, h));
            if (!r.Contains(pt) || Cursor.Current != Cursors.Default)
                base.WndProc(ref m);
            else {
                Rectangle r2 = RectangleToScreen(this.ClientRectangle);
                WindowMenu.Show(r2.Location);
            }
        }
        else {
            base.WndProc(ref m);
        }
    }

/*
    Failed attempts at hiding the default window menu.

    protected override void OnHandleCreated(EventArgs e) {
        base.OnHandleCreated(e);
        //IntPtr hMenu = GetSystemMenu(Handle, false);
        //SendMessage(hMenu, WM_SETREDRAW, (IntPtr) 0, (IntPtr) 0);
        //int count = GetMenuItemCount(hMenu);
        //for (int i = count - 1; i >= 3; i--)
        //  RemoveMenu(hMenu, (uint) i, MF_BYPOSITION);
    }

    [DllImport("user32.dll")]
    public static extern int GetMenuItemCount(IntPtr hMenu);
    [DllImport("user32.dll")]
    private static extern bool RemoveMenu(IntPtr hMenu, uint uPosition, uint uFlags);

    //[DllImport("user32.dll")]
    //public static extern IntPtr DestroyMenu(IntPtr hMenu);
    [DllImport("user32.dll")]
    public static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
    [DllImport("user32.dll")]
    private static extern bool DeleteMenu(IntPtr hMenu, uint uPosition, uint uFlags);

    private const int MF_BYPOSITION = 0x00000400; 
    private const int WM_SETREDRAW = 11;

    [DllImport("user32.dll")]
    public static extern int SendMessage(IntPtr hWnd, Int32 wMsg, IntPtr wParam, IntPtr lParam);


    // this also removes the min/max/close buttons:
    //private const int WS_SYSMENU = 0x80000;
    //protected override CreateParams CreateParams {
    //  get {
    //      var p = base.CreateParams;
    //      p.Style = p.Style & ~WS_SYSMENU;
    //      return p;
    //  }
    //}
    */
}

public class WindowMenu : ToolStripDropDown {
    public WindowMenu() {
        Items.Add(new ToolStripMenuItem("Custom1"));
    }
}