Restore legacy menu message parity#21
Conversation
Restore WM_MENUSELECT and WM_EXITMENULOOP dispatch in Control, keep Form and Menu parity aligned, add regression coverage in the new unit test tree, and document the validation findings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
b855d59 to
3d70e95
Compare
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…py menus Tests prove the end-to-end message path: - WM_MENUSELECT -> Control.WmMenuSelect -> ContextMenu.ProcessMenuSelect -> MenuItem.PerformSelect -> Select event fires (Grid Colors pattern) - WM_INITMENUPOPUP -> MenuItem.Popup fires (Universal Copy pattern) - Idempotency: repeat hover on Grid Colors replaces items, no duplicates Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous ProcessMenuSelect used managed MenuItems[index] to find the popup item, but hidden items (Visible = false) are skipped in the native menu. This caused a native-to-managed index mismatch: Windows reports the native index (which excludes hidden items), but the managed collection includes hidden items at earlier indices, so the wrong MenuItem was found. CargoWise's ZGrid context menu uses hidden items extensively (DeactivateMenuItem, ActivateMenuItem, CustomiseMenuItem, CopyToNewRow, etc.), which made every popup lookup return the wrong item. The fix replaces the index-based lookup with GetMenuItemFromNativeIndex(), which uses Win32 GetMenuItemID/GetSubMenu to navigate the actual native menu structure. This matches the approach used by the .NET Framework reference implementation (WinFormsLegacyControls) and is immune to index mismatches from hidden items. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| public static extern bool SetMenuDefaultItem(HandleRef hMenu, int uItem, bool fByPos); | ||
|
|
||
| [DllImport(Libraries.User32, ExactSpelling = true)] | ||
| public static extern int GetMenuItemID(IntPtr hMenu, int nPos); |
There was a problem hiding this comment.
The legacy has implementation for this not just the contract. Why you did not have this one here?https://github.com/WiseTechGlobal/winforms/blob/net8.0_legacy/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetMenuItemID.cs#L15-L20
| public static extern int GetMenuItemID(IntPtr hMenu, int nPos); | ||
|
|
||
| [DllImport(Libraries.User32, ExactSpelling = true)] | ||
| public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos); |
There was a problem hiding this comment.
Same goes to the GetSubMenu, the implementation is there too: https://github.com/WiseTechGlobal/winforms/blob/net8.0_legacy/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.GetSubMenu.cs#L15-L20
| /// popup item we need. This approach is immune to index mismatches caused by | ||
| /// hidden (<c>Visible = false</c>) menu items. | ||
| /// </summary> | ||
| private static MenuItem? GetMenuItemFromNativeIndex(IntPtr hmenu, int index) |
There was a problem hiding this comment.
Can you point me or add link to where this implementation came from in the legacycode? It would be easier to reference later, so far i cant find it anywhere.
| /// <summary> | ||
| /// Handles the WM_MENUSELECT message. | ||
| /// </summary> | ||
| private void WmMenuSelect(ref Message m) |
There was a problem hiding this comment.
Any reasons why the implementation differ from the original one, rather than doing lift and shift?
https://github.com/WiseTechGlobal/winforms/blob/net8.0_legacy/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs#L221-L255
| /// Handles the WM_EXITMENULOOP message. If this control has a context menu, its | ||
| /// Collapse event is raised. | ||
| /// </summary> | ||
| private void WmExitMenuLoop(ref Message m) |
There was a problem hiding this comment.
Similarly here too, why change from the original one? https://github.com/WiseTechGlobal/winforms/blob/net8.0_legacy/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs#L12230-L12244
| /// <summary> | ||
| /// Handles the WM_MENUSELECT message. | ||
| /// </summary> | ||
| private void WmMenuSelect(ref Message m) |
There was a problem hiding this comment.
Alot different than original too here: https://github.com/WiseTechGlobal/winforms/blob/net8.0_legacy/src/System.Windows.Forms/src/System/Windows/Forms/Control.cs#L221-L255
| /// <c>ZFilterGridModule.AddUniversalCopyMenuItems</c> (Popup-based). | ||
| /// </para> | ||
| /// </remarks> | ||
| public class LazyMenuPopulationRegressionTests |
There was a problem hiding this comment.
Where this test is coming from? Are you adding a new one? if yes, does the existing from legacy does not cover for this?
|
|
||
| namespace System.Windows.Forms.Tests; | ||
|
|
||
| public class MenuSelectTests |
There was a problem hiding this comment.
Similarly here, is this a new test? I cant find from the legacy branch,
This pull request improves the handling of legacy context menu messages in Windows Forms controls, ensuring correct event dispatch and fixing issues with menu item selection and popup events. The changes also include test updates and some code cleanup.
Context Menu Message Handling Improvements:
WM_MENUSELECTandWM_EXITMENULOOPmessages inControlandFormclasses, ensuring that legacy context menu events (likeMenuItem.Popupand selection) are properly dispatched. [1] [2] [3] [4]ProcessMenuSelectlogic inMenuto reliably resolve the correctMenuItemfor selection, including complex cases with hidden items, by using Win32 interop (GetMenuItemID,GetSubMenu). [1] [2]Code Simplification:
Disposedevent handlers inContextMenuandContextMenuStripproperties. [1] [2]Testing and Documentation:
ContextMenuSubMenuPopupTeststo a new location, clarifying test comments and improving assertion style. [1] [2] [3] [4]## SummaryMenuItem.Selectbehavior is preserved for both command and popup items.Popuphandlers fire and dynamic menu items can be populated.LazyMenuPopulationRegressionTestsproving the full end-to-end message path for both the Grid Colors (Select-based) and Universal Copy (Popup-based) lazy-loading patterns.src/test/unit/System.Windows.Forms/System/Windows/Forms/Controls/Unsupported/ContextMenu/.Rationale
GridColourSchemeManagerandUniversalCopyManagerare not needed once this framework fix is in place.Validation
dotnet build src\test\unit\System.Windows.Forms\System.Windows.Forms.Tests.csproj --no-restore✅LazyMenuPopulationRegressionTestscommitted and covers: Grid Colors WM_MENUSELECT path, Universal Copy WM_INITMENUPOPUP path, idempotency (no duplicate items on repeat hover).ProcessMenuSelectinMenu,WmMenuSelectinControlandForm.C:\Users\Rick.Xu\OneDrive - WiseTech Global Pty Ltd\Desktop\wm-menu-select-root-fix-report.md.