Show system menu from another process (using WinForms, c#)

天涯浪子 提交于 2020-05-13 11:52:25

问题


I'm trying to show the system menu (containing minimize, restore, etc.) from a different process in my WinForms UI. I understand that I need interop calls like GetSystemMenu and TrackPopupMenuEx but I failed to make it work. Can someone provide a sample code how to do it?

I've found this code snippet (for WPF): Open another application's System Menu

I modified it to something like this:

    const uint TPM_LEFTBUTTON = 0x0000;
    const uint TPM_RETURNCMD = 0x0100;
    const uint WM_SYSCOMMAND = 0x0112;

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

    [DllImport("user32.dll")]
    static extern uint TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);

    [return: MarshalAs(UnmanagedType.Bool)]
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

    public void ShowContextMenu()
    {
        IntPtr wMenu = GetSystemMenu(ExternalWindowHandle, false);
        // Display the menu
        uint command = TrackPopupMenuEx(wMenu, TPM_LEFTBUTTON | TPM_RETURNCMD, 10, 10, ExternalWindowHandle, IntPtr.Zero);
        if (command == 0)
            return;
        PostMessage(ExternalWindowHandle, WM_SYSCOMMAND, new IntPtr(command), IntPtr.Zero);
    }

As mentioned in the question title, I do not want to minimize a window to the systray, I want to display a system menu from another process (window) at a location I choose. Pretty much the same way as the windows taskbar. The taskbar (explorer) seems to be able to display the system menu when you right-click it on the taskbar.

Thanks, Stefan


回答1:


I have a working version of the code also I have checked the MSDN library and I found out that in order for the TrackPopupMenuEx method to work the "ExternalWindowHandle" variable you passed i.e. the window that the handle represents needs to be in the foreground of the desktop.

The MSDN Library says the following:

"To display a context menu for a notification icon, the current window must be the foreground window before the application calls TrackPopupMenu or TrackPopupMenuEx. Otherwise, the menu will not disappear when the user clicks outside of the menu or the window that created the menu (if it is visible). If the current window is a child window, you must set the (top-level) parent window as the foreground window.", http://msdn.microsoft.com/en-us/library/windows/desktop/ms648003(v=vs.85).aspx

So that means it will only ever work when your window is the active one and in the foreground if you are for example debugging in visual studio it will not work because the window is not the one in the foreground i.e. visual studio is not your app.

Please see enclosed the working code example, bear in mind that it will only work when the app window is the one in focus/in the foreground. i.e. the TrackPopupMenuEx will always return 0 when you are debugging or using another window.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication2
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        const uint TPM_LEFTBUTTON = 0x0000;
        const uint TPM_RETURNCMD = 0x0100;
        const uint WM_SYSCOMMAND = 0x0112;

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

        [DllImport("user32.dll")]
        static extern uint TrackPopupMenuEx(IntPtr hmenu, uint fuFlags, int x, int y, IntPtr hwnd, IntPtr lptpm);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        public static void ShowContextMenu(IntPtr appWindow, IntPtr myWindow, Point point)
        {
            IntPtr wMenu = GetSystemMenu(appWindow, false);
            // Display the menu
            uint command = TrackPopupMenuEx(wMenu,
                TPM_LEFTBUTTON | TPM_RETURNCMD, (int)point.X, (int)point.Y, myWindow, IntPtr.Zero);
            if (command == 0)
                return;

            PostMessage(appWindow, WM_SYSCOMMAND, new IntPtr(command), IntPtr.Zero);
        }

        private void button1_Click(object sender, EventArgs e)
        {
            ShowContextMenu(new IntPtr(<<put your target window handle here>>), this.Handle, new Point(0, 0));
        }
    }
}


来源:https://stackoverflow.com/questions/15129218/show-system-menu-from-another-process-using-winforms-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!