|
| 1 | +# Optional WinUI 3 backend on Windows |
| 2 | + |
| 3 | +`NATIVEAPI_ENABLE_WINUI3=ON` selects WinUI 3 for new Windows menus and |
| 4 | +message dialogs. Menus use `Microsoft.UI.Xaml.Controls.MenuFlyout`; message |
| 5 | +dialogs use `Microsoft.UI.Xaml.Controls.ContentDialog`. Both share a lazy XAML |
| 6 | +runtime and use private XAML Islands, including in tray-only applications. |
| 7 | +Future WinUI components should use this same option and runtime. |
| 8 | + |
| 9 | +With the option OFF (the default), menus and dialogs retain their Win32 |
| 10 | +implementations. Public C++ and C interfaces stay platform independent. |
| 11 | + |
| 12 | +## Build |
| 13 | + |
| 14 | +The optional backend requires MSVC, a Windows 10/11 SDK, and Windows App SDK 1.6. |
| 15 | +The default build, including MinGW, has no new dependencies. This integration |
| 16 | +currently pins the 1.6 package layout; newer SDK versions require validation before |
| 17 | +changing the version guard. |
| 18 | + |
| 19 | +From the repository root, in PowerShell: |
| 20 | + |
| 21 | +```powershell |
| 22 | +./cmake/RestoreWinUI3.ps1 |
| 23 | +$packages = (Resolve-Path build/winui3-packages).Path |
| 24 | +cmake -S . -B build/menu-modern -G "Visual Studio 17 2022" -A x64 ` |
| 25 | + -DNATIVEAPI_ENABLE_WINUI3=ON ` |
| 26 | + "-DNATIVEAPI_WINAPPSDK_DIR=$packages/winappsdk" ` |
| 27 | + "-DNATIVEAPI_CPPWINRT_EXE=$packages/cppwinrt/bin/cppwinrt.exe" ` |
| 28 | + "-DNATIVEAPI_WEBVIEW2_DIR=$packages/webview2" |
| 29 | +cmake --build build/menu-modern --config Debug --target menu_example message_dialog_example menu_backend_test winui3_dialog_test |
| 30 | +./build/menu-modern/examples/menu_example/Debug/menu_example.exe |
| 31 | +``` |
| 32 | + |
| 33 | +The restore script verifies pinned package hashes. It downloads build dependencies |
| 34 | +only, without installing a runtime. WebView2 metadata is required to generate WinUI |
| 35 | +projections; the menu does not instantiate WebView2 or require its browser runtime. |
| 36 | + |
| 37 | +Install the matching [Windows App Runtime 1.6](https://learn.microsoft.com/en-us/windows/apps/windows-app-sdk/downloads-archive) |
| 38 | +on deployment machines. For each consuming executable, call |
| 39 | +`nativeapi_deploy_winui3(your_executable_target)` after `add_subdirectory(nativeapi)` |
| 40 | +and creating the target. This copies the bootstrap DLL beside the executable. |
| 41 | +For a DLL/plugin, deploy beside the **host executable**. The library dynamically |
| 42 | +loads the bootstrap DLL only on first WinUI component use; a missing runtime does not |
| 43 | +prevent native menus from working. An existing WinUI host's package graph and |
| 44 | +dispatcher are reused. |
| 45 | + |
| 46 | +The host executable must declare **PerMonitorV2** DPI awareness in its application |
| 47 | +manifest to render crisp text at high display scaling. The bundled examples and |
| 48 | +WinUI smoke test embed `cmake/winui3-example.manifest`. Add equivalent DPI settings |
| 49 | +to your own application's manifest (a DLL manifest cannot set the host's DPI mode). |
| 50 | +The library does not change process-wide DPI settings, since that would affect |
| 51 | +other windows owned by the host. Without DPI awareness Windows bitmap-scales the |
| 52 | +finished menu, which makes text and edges blurry. |
| 53 | + |
| 54 | +## C++ and C usage |
| 55 | + |
| 56 | +To try the tray integration, build and run the tray example instead: |
| 57 | + |
| 58 | +```powershell |
| 59 | +cmake --build build/menu-modern --config Debug --target tray_icon_example |
| 60 | +./build/menu-modern/examples/tray_icon_example/Debug/tray_icon_example.exe |
| 61 | +``` |
| 62 | + |
| 63 | +Right-click the tray icon whose tooltip is **nativeapi WinUI3 Tray Test - right-click** |
| 64 | +(it may be in the tray overflow). Select **Exit** to stop the example. |
| 65 | + |
| 66 | +```cpp |
| 67 | +Application::GetInstance(); // Initializes COM on the UI thread. |
| 68 | +auto menu = std::make_shared<Menu>(); |
| 69 | +menu->AddItem(std::make_shared<MenuItem>("Open")); |
| 70 | +menu->Open(PositioningStrategy::CursorPosition()); // WinUI3 when compiled ON. |
| 71 | + |
| 72 | +MessageDialog dialog("Information", "This is a real WinUI 3 ContentDialog."); |
| 73 | +dialog.SetModality(DialogModality::Application); |
| 74 | +dialog.Open(); |
| 75 | +``` |
| 76 | +
|
| 77 | +```c |
| 78 | +native_menu_t menu = native_menu_create(); |
| 79 | +/* The build option selects the default backend for the C API too. */ |
| 80 | +/* Add items, register listeners, and open using the existing C API. */ |
| 81 | +native_menu_free(menu); |
| 82 | +``` |
| 83 | + |
| 84 | +`IsBackendSupported()` reports compile-time capability. `SetBackend()` rejects |
| 85 | +unsupported backends, changes while open, and modern presentation for menus wrapping |
| 86 | +an external `HMENU`. Runtime initialization failure makes `Open()` return false |
| 87 | +and logs a diagnostic. Submenus use their root's backend. |
| 88 | + |
| 89 | +## Message dialogs |
| 90 | + |
| 91 | +Run `./build/menu-modern/examples/message_dialog_example/Debug/message_dialog_example.exe`. |
| 92 | +No runtime switch or new API is required. |
| 93 | + |
| 94 | +- `None` returns after presentation. Keep the dialog alive and dispatch the UI |
| 95 | + message loop (normally `Application::Run()`). `Close()` dismisses it. |
| 96 | +- `Application` pumps messages until dismissal and temporarily disables the |
| 97 | + application's currently visible, enabled top-level windows. |
| 98 | +- `Window` disables the calling thread's active visible window. The public API |
| 99 | + has no explicit parent parameter yet; if no active window exists, the dialog |
| 100 | + opens without an owner. It never blocks other applications. |
| 101 | +- `SetTitle` and `SetMessage` update displayed content. Long messages scroll. |
| 102 | + The OK button, title-bar close, and `Close()` dismiss the dialog. Previously |
| 103 | + enabled windows are restored on dismissal, initialization failure, or destruction. |
| 104 | +- Create, mutate, open, close, and destroy on the same STA UI thread. Reopening |
| 105 | + an already open instance is rejected. WinUI runtime errors return false from |
| 106 | + `Open()` and are logged, with no silent fallback to Win32. |
| 107 | +- Hosts and example executables use PerMonitorV2 sizing, including monitor changes. |
| 108 | + |
| 109 | +The implementation follows Microsoft's [ContentDialog hosting requirements](https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.controls.contentdialog?view=windows-app-sdk-1.6). |
| 110 | + |
| 111 | +## Menu behavior and limits |
| 112 | + |
| 113 | +- Call creation, opening, closing, and mutations on the same STA UI thread. Keep |
| 114 | + the root menu alive until `Open()` returns. One modern menu session can run per |
| 115 | + thread; nested root opens are rejected. |
| 116 | +- `Open()` continues pumping messages until dismissal. `Close()`, clicks outside, |
| 117 | + and normal WinUI dismissal end the session. `WM_QUIT` is preserved for the host. |
| 118 | +- Normal items, separators, checkbox/radio items, icons, tooltips, disabled items, |
| 119 | + and nested submenus use WinUI controls. Checked state remains application-owned, |
| 120 | + matching the existing example: update it in the click listener. |
| 121 | +- Labels, icons, enabled/checked state, tooltips, and shortcut text update while |
| 122 | + displayed. Structural edits (insert/remove/replace submenu) are reflected on the |
| 123 | + next root open. Root opening listeners run before constructing the item tree; |
| 124 | + submenu lifecycle events follow the child presenter's loading/unloading. |
| 125 | +- Keyboard accelerator labels are displayed. This backend does not register |
| 126 | + application/global shortcuts; use the existing shortcut API for those. |
| 127 | +- WinUI checkbox controls are two-state. Mixed checkbox state is currently shown |
| 128 | + as unchecked. Use the native backend if tri-state presentation is required. |
| 129 | +- `GetNativeObject()` still returns the library-owned `HMENU`, never a WinRT |
| 130 | + pointer. It represents the native mirror; external direct `HMENU` changes do not |
| 131 | + update the modern presentation. Do not destroy this borrowed handle. |
| 132 | +- Positioning uses the existing screen/DIP conversion followed by WinUI placement |
| 133 | + and edge avoidance. Themes and accessibility come from WinUI. Appearance can |
| 134 | + differ from File Explorer's shell menu. |
| 135 | + |
| 136 | +## Verification |
| 137 | + |
| 138 | +`ctest --test-dir build/menu-modern -C Debug -R menu_backend_test --output-on-failure` |
| 139 | +checks backend selection without requiring the runtime. The explicit interactive |
| 140 | +smoke test opens and closes the menu twice and checks lifecycle/reentry: |
| 141 | + |
| 142 | +```powershell |
| 143 | +./build/menu-modern/tests/Debug/menu_backend_test.exe --winui3 |
| 144 | +``` |
| 145 | + |
| 146 | +The test also exercises live property updates and cancellation from an opening |
| 147 | +listener. Add `--interactive` to keep each popup open for up to a minute in a |
| 148 | +preview window. |
| 149 | + |
| 150 | +The same selection test should pass in a default build with the feature disabled. |
| 151 | + |
| 152 | +The explicit desktop dialog integration test covers modal and modeless opening, |
| 153 | +reentry rejection, live updates, programmatic/title-bar close, owner restoration, |
| 154 | +destruction while open, and sharing the runtime with a menu: |
| 155 | + |
| 156 | +```powershell |
| 157 | +./build/menu-modern/tests/Debug/winui3_dialog_test.exe |
| 158 | +``` |
0 commit comments