none
floating popup menu on a smartphone

    Question

  • I want to display a ContextMenu associated with a textbox on a WM6 smartphone. As there is no touchscreen on a smartphone and therefore no such thing as "tap and hold", the .NET compact framework for WM6 standard doesn't support the ContextMenu property for textbox controls. I want to simulate this behaviour by displaying a popup menu whenever the user presses ENTER when the textbox is having focus. I know this is possible in C++ by using TrackPopupMenu. I am thinking of declaring a MainMenu object in my program, obtain a handle to it (IntPtr) and p/invoke TrackPopupMenu to display the popup menu. However, so far I have no idea how to get the IntPtr handle to MainMenu - the MainMenu class does not expose this property.

     

    Any ideas how I can achieve this? Thanks.

     

    P.S. Don't reply and tell me this is not a supported scenario (I know many moderators like to say this). I have seen a lot of smartphone applications showing pop-up context menus and some of them are written in .NET code (which, unfortunately, have been obfuscated Sad)

    Tuesday, July 01, 2008 4:08 AM

Answers

  • I have solved the problem by using the following code:

     

    Code Snippet

    IntPtr parentWindow = GetActiveWindow()
    IntPtr hMenuWnd = SHFindMenuBar(parentWindow);
    IntPtr hMainMenu = (IntPtr)SendMessage(hMenuWnd, SHCMBM_GETMENU, 0, 0);
    IntPtr hMenu = GetSubMenu(hMainMenu, 0);
    TrackPopupMenu(hMenu, TPM_LEFTALIGN, 0, 0, parentWindow, IntPtr.Zero);

     

    [DllImport("coredll.dll", EntryPoint = "TrackPopupMenuEx", SetLastError = true)]
    private static extern int TrackPopupMenu(IntPtr hMenu, int wFlags, int x, int y, IntPtr hwnd, IntPtr lprc);

     

    [DllImport("coredll.dll", SetLastError = true)]
    public static extern IntPtr GetActiveWindow();

     

    [DllImport("aygshell.dll", SetLastError=true)]
    public static extern IntPtr SHFindMenuBar(IntPtr hWindow);

     

    [DllImport("coredll.dll", SetLastError = true)]
    public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

     

    [DllImport("coredll.dll", SetLastError = true)]
    public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);

     

     

    This code will retrieve all the sub-menus under the left soft key menu of the application and display as pop-up menu on the top left corner of the form. Fortunately, the OnClick event of the menu items can be declared the usual way:

     

    Code Snippet

    this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click);

     

    private void menuItem3_Click(object sender, EventArgs e)

    {

    }

     

    and menuItem3_Click will be called when the associated menu item is clicked, regardless of whether it comes from the pop-up menu or from the main menu. However, there are some limitations:

     

    (1) I can only display a sub-menu (via GetSubMenu) but cannot display the entire menu. Removing the GetSubMenu line and passing hMainMenu to TrackPopupMenu immediately causes TrackPopupMenu to return 0 and GetLastError returns ERROR_INVALID_HANDLE. How can I display the entire menu?

     

    (2) The menu can only be displayed as pop-up if it is associated with the MainMenu property of the form (otherwise SHFindMenuBar would not work). How can I display the pop-up menu without first associating it with the form? As in your reply, I can use reflection to retrieve the private properties of the MainMenu data type, which hopefully contains the IntPtr handle. Can you give a detailed example?

     

    (3) Alternatively, I have tried creating my own menu and pass the handle to TrackPopupMenu

     

    Code Snippet

    IntPtr hMenu = CreatePopupMenu();

    int index = 100;

    AppendMenu(hMenu, MF_STRING, index, "Item1");index++;

    AppendMenu(hMenu, MF_STRING, index, "Item2");index++;

    AppendMenu(hMenu, MF_STRING, index, "Item3");index++;

     

    It works this way (the popup menu is displayed) but handling the click event is difficult. To trace which item is clicked I would have to use

     

    Code Snippet
    TrackPopupMenu
    (hMenu, TPM_LEFTALIGN | TPM_RETURNCMD, 0, 0, parentWindow, IntPtr.Zero);

     

     

    which make the call to TrackPopupMenu a blocking call and will return the index of the selected menu item.

    Wednesday, July 02, 2008 3:15 AM

All replies

  • Provided that the TrackPopupMenu does actually work on a WM Standard platform - the best (IMO) option would be to implement your own popup menu (ContextMenu) based on the existing Win32 APIs (CreateMenu...).

     

    Other approaches using Reflection to read private fields are not to recommend.

    Tuesday, July 01, 2008 4:38 PM
  • I have solved the problem by using the following code:

     

    Code Snippet

    IntPtr parentWindow = GetActiveWindow()
    IntPtr hMenuWnd = SHFindMenuBar(parentWindow);
    IntPtr hMainMenu = (IntPtr)SendMessage(hMenuWnd, SHCMBM_GETMENU, 0, 0);
    IntPtr hMenu = GetSubMenu(hMainMenu, 0);
    TrackPopupMenu(hMenu, TPM_LEFTALIGN, 0, 0, parentWindow, IntPtr.Zero);

     

    [DllImport("coredll.dll", EntryPoint = "TrackPopupMenuEx", SetLastError = true)]
    private static extern int TrackPopupMenu(IntPtr hMenu, int wFlags, int x, int y, IntPtr hwnd, IntPtr lprc);

     

    [DllImport("coredll.dll", SetLastError = true)]
    public static extern IntPtr GetActiveWindow();

     

    [DllImport("aygshell.dll", SetLastError=true)]
    public static extern IntPtr SHFindMenuBar(IntPtr hWindow);

     

    [DllImport("coredll.dll", SetLastError = true)]
    public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

     

    [DllImport("coredll.dll", SetLastError = true)]
    public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);

     

     

    This code will retrieve all the sub-menus under the left soft key menu of the application and display as pop-up menu on the top left corner of the form. Fortunately, the OnClick event of the menu items can be declared the usual way:

     

    Code Snippet

    this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click);

     

    private void menuItem3_Click(object sender, EventArgs e)

    {

    }

     

    and menuItem3_Click will be called when the associated menu item is clicked, regardless of whether it comes from the pop-up menu or from the main menu. However, there are some limitations:

     

    (1) I can only display a sub-menu (via GetSubMenu) but cannot display the entire menu. Removing the GetSubMenu line and passing hMainMenu to TrackPopupMenu immediately causes TrackPopupMenu to return 0 and GetLastError returns ERROR_INVALID_HANDLE. How can I display the entire menu?

     

    (2) The menu can only be displayed as pop-up if it is associated with the MainMenu property of the form (otherwise SHFindMenuBar would not work). How can I display the pop-up menu without first associating it with the form? As in your reply, I can use reflection to retrieve the private properties of the MainMenu data type, which hopefully contains the IntPtr handle. Can you give a detailed example?

     

    (3) Alternatively, I have tried creating my own menu and pass the handle to TrackPopupMenu

     

    Code Snippet

    IntPtr hMenu = CreatePopupMenu();

    int index = 100;

    AppendMenu(hMenu, MF_STRING, index, "Item1");index++;

    AppendMenu(hMenu, MF_STRING, index, "Item2");index++;

    AppendMenu(hMenu, MF_STRING, index, "Item3");index++;

     

    It works this way (the popup menu is displayed) but handling the click event is difficult. To trace which item is clicked I would have to use

     

    Code Snippet
    TrackPopupMenu
    (hMenu, TPM_LEFTALIGN | TPM_RETURNCMD, 0, 0, parentWindow, IntPtr.Zero);

     

     

    which make the call to TrackPopupMenu a blocking call and will return the index of the selected menu item.

    Wednesday, July 02, 2008 3:15 AM