Search code examples
c#winapi

Getting a win32 window and adding menu programmatically


I have a dll that provides cross-platform app UI. For windows the underlying platform uses Win 32 API. I am trying to create a Win UI 3 app wrapping this dll and adding additional functionality. Specifically, after I make the call to DLL which launches a window, I want to be able to get hold of that window (the dll call doesn't return anything, just launches the window) and programmatically add menu to it. Is it possible to do this? Looking for a C# solution.


Solution

  • Thanks to stackoverflow and Copilot, I managed to solve this. Here is an outline of the solution.

    1. Getting the window
                    Thread.Sleep(10); // Without sleep, EnumWindows didn't return the window
                    IntPtr hWnd = FindWindowEx(className, null);
                    if (hWnd == IntPtr.Zero) { // for some reason FindWindowEx didn't work so as a fallback I used EnumWindows
                        EnumWindows(EnumWindowsCallback, IntPtr.Zero);
                    } else
                    {
                        AppMenu.SetupMenu(hWnd);
                    }
            [DllImport("User32.dll")]
            static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, IntPtr lParam);
    
            delegate bool EnumWindowsProc(IntPtr hWnd, IntPtr lParam);
    
            static bool EnumWindowsCallback(IntPtr hWnd, IntPtr lParam)
            {
                uint processId;
                GetWindowThreadProcessId(hWnd, out processId);
                if (processId != currentProcessId) // I already have current process id using Process.GetCurrentProcess().Id;
                {
                    return true;
                }
                string windowTitle = GetWindowTitle(hWnd);
                if (windowTitle == appName)
                {
                    AppMenu.SetupMenu(hWnd);
                }
                return true;
            }
    
            static string GetWindowTitle(IntPtr hWnd)
            {
                const int nChars = 256;
                StringBuilder sb = new StringBuilder(nChars);
                GetWindowText(hWnd, sb, nChars);
                return sb.ToString();
            }
    
            [DllImport("User32.dll")]
            static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);
    
    1. To setup menu on the window, my AppMenu class has
    public static void SetupMenu(IntPtr hWnd)
    {
                IntPtr hMenu = CreateMenu();
    
                IntPtr fMenu = CreatePopupMenu();
                AppendMenu(fMenu, MF_STRING, IDM_OPEN_GIF, "&Open GIF File\tCtrl+N");
                AppendMenu(fMenu, MF_STRING, IDM_OPEN_GIF_URL, "&Open GIF URL\tCtrl+Shift+N");
                // more menu items
                AppendMenu(hMenu, MF_POPUP, (uint)fMenu, "File");
                // more menus
    }
    
    1. Register for menu item selection (some of the constants below are from winuser.h that I picked up on the net (don't know how to make use of them from standard libraries instead of defining them in the app)
            [DllImport("User32.dll")]
            static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);
    
                var result = SetWinEventHook(EVENT_OBJECT_INVOKED, EVENT_OBJECT_INVOKED, IntPtr.Zero, WinEventProc, (uint)App.currentProcessId, 0, WINEVENT_OUTOFCONTEXT | WINEVENT_SKIPOWNTHREAD);
                if (result == IntPtr.Zero)
                {
                    Debug.WriteLine("Error setting up win event hook");
                }
    
    1. Lastly (this was the missing piece for me for a couple of hours), SetWinEventHook will only work if there is a message loop.
                MSG msg;
                while (GetMessage(out msg, IntPtr.Zero, 0, 0))
                {
                    TranslateMessage(ref msg);
                    DispatchMessage(ref msg);
                    if (msg.message == WM_QUIT)
                        break;
                }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct MSG
            {
                public IntPtr hwnd;
                public uint message;
                public IntPtr wParam;
                public IntPtr lParam;
                public uint time;
                public POINT pt;
            }
    
            [StructLayout(LayoutKind.Sequential)]
            public struct POINT
            {
                public int x;
                public int y;
            }