mirror of
https://github.com/microsoft/PowerToys.git
synced 2026-07-06 02:19:51 +02:00
Compare commits
12 Commits
yuleng/kbm
...
dev/crutka
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
49b3795f1a | ||
|
|
8dc4598786 | ||
|
|
fa8fbb60a1 | ||
|
|
ab4947579b | ||
|
|
d221f84d8f | ||
|
|
14152672c5 | ||
|
|
5688441127 | ||
|
|
d9216f0fc7 | ||
|
|
8c9bd8ba64 | ||
|
|
aaee51b90e | ||
|
|
ff3c1f9252 | ||
|
|
99ee955dfb |
1
.github/actions/spell-check/expect.txt
vendored
1
.github/actions/spell-check/expect.txt
vendored
@@ -677,6 +677,7 @@ HDEVNOTIFY
|
||||
hdr
|
||||
HDROP
|
||||
hdwwiz
|
||||
Headered
|
||||
Helpline
|
||||
helptext
|
||||
hgdiobj
|
||||
|
||||
229
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
229
.github/skills/wpf-to-winui3-migration/SKILL.md
vendored
@@ -31,7 +31,9 @@ Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.U
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Recommended Order
|
||||
### Phase-by-Phase Scope
|
||||
|
||||
Work on bounded problems, not the entire codebase at once. Each phase should compile before moving to the next.
|
||||
|
||||
1. **Project file** — Update TFM, NuGet packages, set `<UseWinUI>true</UseWinUI>`
|
||||
2. **Data models and business logic** — No UI dependencies, migrate first
|
||||
@@ -45,79 +47,213 @@ Migrate PowerToys modules from WPF (`System.Windows.*`) to WinUI 3 (`Microsoft.U
|
||||
10. **Installer & build pipeline** — Update WiX, signing, build events
|
||||
11. **Tests** — Adapt for WinUI 3 runtime, async patterns
|
||||
|
||||
### Key Principles
|
||||
### Migration Contract: Prohibited Patterns
|
||||
|
||||
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different application lifecycle boilerplate. Merge your resources and initialization code into the generated WinUI 3 App class.
|
||||
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. This causes phantom build artifacts.
|
||||
- **Use `Lazy<T>` for resource-dependent statics** — `ResourceLoader` is not available at class-load time in all contexts.
|
||||
These rules capture human judgment and must be applied consistently across every file. Do NOT deviate.
|
||||
|
||||
**Architecture prohibitions:**
|
||||
- **Do NOT overwrite `App.xaml` / `App.xaml.cs`** — WinUI 3 has different lifecycle boilerplate. Merge resources and init code into the generated WinUI 3 App class.
|
||||
- **Do NOT create Exe→WinExe `ProjectReference`** — Extract shared code to a Library project. Causes phantom build artifacts.
|
||||
- **Do NOT instantiate services directly** — Use DI and CommunityToolkit.Mvvm patterns.
|
||||
- **Do NOT create a `Window` subclass for every dialog or sub-page** — use `ContentDialog` for in-app dialogs and `Frame`/`Page` navigation for sub-views. Separate `Window` classes are reserved for distinct top-level surfaces (e.g., FancyZones editor, OOBE).
|
||||
- **Do NOT omit `WindowsPackageType=None` and `WindowsAppSDKSelfContained=true`** — Both are mandatory in the csproj for every WinUI 3 module in PowerToys. Without them the app crashes at startup with `COMException: ClassFactory cannot supply requested class` because the WinUI 3 runtime DLLs are not found.
|
||||
|
||||
**XAML prohibitions:**
|
||||
- **Do NOT use `{DynamicResource}`** — Replace with `{ThemeResource}` (theme-reactive) or `{StaticResource}`.
|
||||
- **Do NOT use `{Binding}` in `Setter.Value`** — Not supported in WinUI 3. Use `{StaticResource}`.
|
||||
- **Do NOT use `{x:Static}`** — Replace with `{x:Bind}`, `x:Uid`, or code-behind.
|
||||
- **Do NOT use `{x:Type}`** — Not supported. Use `x:DataType` for DataTemplate, or code-behind.
|
||||
- **Do NOT use `clr-namespace:`** — Replace with `using:` in all xmlns declarations.
|
||||
- **Do NOT use `Style.Triggers` / `DataTrigger` / `EventTrigger`** — Replace with `VisualStateManager`.
|
||||
- **Do NOT use `MultiBinding`** — Replace with `x:Bind` function binding or computed ViewModel property.
|
||||
- **Do NOT use `Visibility="Hidden"`** — WinUI only has `Visible` and `Collapsed`. Use `Opacity="0"` if layout must be preserved.
|
||||
- **Do NOT use `IsDefault` / `IsCancel`** — Use `AccentButtonStyle` for primary button; handle Enter/Escape in code-behind.
|
||||
- **Do NOT omit `BasedOn` when overriding default styles** — Without it, your style replaces the entire default. Always use `BasedOn="{StaticResource DefaultButtonStyle}"` etc.
|
||||
- **Do NOT omit `XamlControlsResources` as first merged dictionary** — It provides default Fluent styles. Without it, controls have no visual appearance.
|
||||
|
||||
**Code-behind prohibitions:**
|
||||
- **Do NOT use `Application.Current.Dispatcher`** — Store `DispatcherQueue` in a static field explicitly.
|
||||
- **Do NOT use `Window.Current`** — Not supported. Use a custom `App.Window` static property.
|
||||
- **Do NOT put `DataContext`, `Resources`, or `VisualStateManager` on `Window`** — WinUI 3 `Window` is NOT a `DependencyObject`. Use a root `Page`/`UserControl`/`Grid`.
|
||||
- **Do NOT use tunneling/preview events** (`PreviewMouseDown`, `PreviewKeyDown`) — WinUI has no tunneling. Use bubbling equivalents with `Handled` property or `AddHandler(handledEventsToo: true)`.
|
||||
|
||||
**Resource prohibitions:**
|
||||
- **Do NOT use `Properties.Resources.MyString`** — Replace with `ResourceLoaderInstance.ResourceLoader.GetString("MyString")`.
|
||||
- **Do NOT initialize `ResourceLoader`-dependent values as static fields** — Wrap in `Lazy<T>` or null-coalescing property.
|
||||
- **Do NOT use `pack://` URIs** — Replace with `ms-appx:///` scheme.
|
||||
|
||||
## Quick Reference Tables
|
||||
|
||||
### Namespace Mapping
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` | Root namespace |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` | Core controls |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` | Low-level primitives |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` | Brushes, transforms |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` | Storyboard, animations |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` (UI) / `Windows.Graphics.Imaging` (processing) | Split by purpose |
|
||||
| `System.Windows.Media.Media3D` | **No equivalent** | Use Win2D or Composition APIs |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` | Rectangle, Ellipse, Path |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` | Pointer, keyboard, focus |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` | Binding, IValueConverter |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` | Limited — RichTextBlock + Paragraph |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` | XAML parsing, markup extensions |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` | Accessibility / UI Automation |
|
||||
| `System.Windows.Navigation` | **No direct equivalent** | Use `Frame.Navigate()` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` | Dispatcher → DispatcherQueue |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` / `Microsoft.UI.Xaml.Hosting` | HWND interop |
|
||||
|
||||
### Control Replacements (No 1:1 Mapping)
|
||||
|
||||
These WPF controls have no direct counterpart and require a different control or third-party package:
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | Notes |
|
||||
|-------------|---------------------|-------|
|
||||
| `DataGrid` | [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) | Community library; the Toolkit `DataGrid` is no longer maintained. Legacy code may still pin v7 `CommunityToolkit.WinUI.UI.Controls.DataGrid` 7.1.2 |
|
||||
| `Ribbon` | `CommandBar` / `NavigationView`, or [Toolkit Labs Ribbon](https://github.com/CommunityToolkit/Labs-Windows/tree/main/components/Ribbon) | No first-party Ribbon in WinUI; Labs component is experimental/partial |
|
||||
| `Menu` / `MenuItem` | `MenuBar` / `MenuBarItem` / `MenuFlyout` | `MenuBar` for classic menu, `MenuFlyout` for context |
|
||||
| `ContextMenu` | `MenuFlyout` | Assign to `ContextFlyout` property |
|
||||
| `ToolBar` / `ToolBarTray` | `CommandBar` + `AppBarButton` | |
|
||||
| `StatusBar` | Custom `Grid`/`StackPanel` or `InfoBar` | No StatusBar control |
|
||||
| `TabControl` | `TabView` or `NavigationView` (top mode) | `TabView` for closeable tabs |
|
||||
| `DocumentViewer` | `WebView2` | Render PDFs/XPS inside WebView2 |
|
||||
| `FlowDocument` | `RichTextBlock` | Partial replacement only |
|
||||
| `RichTextBox` | `RichEditBox` | Rich text editing |
|
||||
| `GroupBox` | `Expander` (built-in) or `HeaderedContentControl` (Toolkit) | See [Layout & Header Controls from CommunityToolkit.WinUI](#layout--header-controls-from-communitytoolkitwinui) below |
|
||||
| `Label` | `TextBlock` | WPF `Label` is a `ContentControl`; use `TextBlock` + `AccessKey` |
|
||||
| `TreeView` | `TreeView` (native) | Available natively, but data binding model differs significantly |
|
||||
| `MessageBox` | `ContentDialog` | Must set `XamlRoot` before `ShowAsync()` |
|
||||
| `MediaElement` | `MediaPlayerElement` | Different API |
|
||||
| `AccessText` | Not available | Use `AccessKey` property on target control |
|
||||
|
||||
### Layout & Header Controls from CommunityToolkit.WinUI
|
||||
|
||||
These WPF controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. **The NuGet package id and the XAML namespace differ intentionally**: package names end in `.Primitives` / `.HeaderedControls`, but the registered XAML namespace is the shorter `CommunityToolkit.WinUI.Controls` (confirmed in the [official Microsoft Q&A](https://learn.microsoft.com/en-us/answers/questions/5746230/why-does-communitytoolkit-uwp-controls-primitive-u)).
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | NuGet Package | XAML Namespace |
|
||||
|-------------|---------------------|---------------|----------------|
|
||||
| `WrapPanel` | `WrapPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `UniformGrid` | `UniformGrid` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `DockPanel` | `DockPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `GroupBox` (alt.) | `HeaderedContentControl` | `CommunityToolkit.WinUI.Controls.HeaderedControls` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
|
||||
### No Equivalent — Requires Architectural Rework
|
||||
|
||||
These WPF features have no WinUI counterpart and require redesign, not find-and-replace:
|
||||
|
||||
| WPF Feature | WinUI 3 Replacement Strategy |
|
||||
|-------------|------------------------------|
|
||||
| `Style.Triggers` / `DataTrigger` | `VisualStateManager` with `StateTrigger` — see [XAML Migration](./references/xaml-migration.md) |
|
||||
| `MultiBinding` | `x:Bind` function binding: `{x:Bind local:Converters.Format(VM.A, VM.B), Mode=OneWay}` |
|
||||
| `RoutedUICommand` / `CommandBinding` | `ICommand` / `[RelayCommand]` from CommunityToolkit.Mvvm. WinUI also has `StandardUICommand` / `XamlUICommand` for platform commands. |
|
||||
| `AdornerLayer` / `Adorner` | Depends on use case: `TeachingTip`/`InfoBar` (validation), `Popup` (overlays), `PlaceholderText` (watermarks), Canvas overlay (decorations) |
|
||||
| `Visibility.Hidden` | `Opacity="0"` with `Visibility="Visible"` (preserves layout space) |
|
||||
| `Window.Resources` / `Window.DataContext` | Move to root `Grid.Resources` / root `Page`/`UserControl` — WinUI `Window` is NOT a DependencyObject |
|
||||
| Tunneling events (`Preview*`) | Use bubbling equivalents + `Handled` property or `AddHandler(handledEventsToo: true)` |
|
||||
|
||||
### Critical API Replacements
|
||||
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`) |
|
||||
| `Dispatcher.Invoke()` | `DispatcherQueue.TryEnqueue()` | Different return type (`bool`), async by default |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` | Property vs method |
|
||||
| `Application.Current.Dispatcher` | Store `DispatcherQueue` in static field | See [Threading](./references/threading-and-windowing.md) |
|
||||
| `Window.Current` | Custom `App.Window` static property | Not supported in Windows App SDK |
|
||||
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
|
||||
| `MessageBox.Show()` | `ContentDialog` | Must set `XamlRoot` |
|
||||
| `System.Windows.Clipboard` | `Windows.ApplicationModel.DataTransfer.Clipboard` | Different API surface |
|
||||
| `RoutedUICommand` / `CommandBinding` | `ICommand` / `[RelayCommand]` | Remove `CommandBinding`; bind `ICommand` directly |
|
||||
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
|
||||
| `DynamicResource` | `ThemeResource` | Theme-reactive only |
|
||||
| `clr-namespace:` | `using:` | XAML namespace prefix |
|
||||
| `{x:Static props:Resources.Key}` | `x:Uid` or `ResourceLoader.GetString()` | .resx → .resw |
|
||||
| `DataType="{x:Type m:Foo}"` | Remove or use code-behind | `x:Type` not supported |
|
||||
| `Properties.Resources.MyString` | `ResourceLoaderInstance.ResourceLoader.GetString("MyString")` | Lazy-init pattern |
|
||||
| `Application.Current.MainWindow` | Custom `App.Window` static property | Must track manually |
|
||||
| `DataType="{x:Type m:Foo}"` | `x:DataType="m:Foo"` | `x:Type` not supported |
|
||||
| `SizeToContent="Height"` | Custom `SizeToContent()` via `AppWindow.Resize()` | See [Windowing](./references/threading-and-windowing.md) |
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Mouse → Pointer events |
|
||||
| `Pack URI (pack://...)` | `ms-appx:///` | Resource URI scheme |
|
||||
| `Observable` (custom base) | `ObservableObject` + `[ObservableProperty]` | CommunityToolkit.Mvvm |
|
||||
| `RelayCommand` (custom) | `[RelayCommand]` source generator | CommunityToolkit.Mvvm |
|
||||
| `JpegBitmapEncoder` | `BitmapEncoder.CreateAsync(JpegEncoderId, stream)` | Async, unified API |
|
||||
| `encoder.QualityLevel = 85` | `BitmapPropertySet { "ImageQuality", 0.85f }` | int 1-100 → float 0-1 |
|
||||
|
||||
### Event Replacements (Mouse → Pointer)
|
||||
|
||||
| WPF Event | WinUI 3 Event | Notes |
|
||||
|-----------|--------------|-------|
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Check `IsLeftButtonPressed` on args |
|
||||
| `MouseLeftButtonUp` | `PointerReleased` | Check pointer properties |
|
||||
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` with right button check |
|
||||
| `MouseMove` | `PointerMoved` | `MouseEventArgs` → `PointerRoutedEventArgs` |
|
||||
| `MouseWheel` | `PointerWheelChanged` | Different event args |
|
||||
| `MouseEnter` / `MouseLeave` | `PointerEntered` / `PointerExited` | |
|
||||
| `MouseDoubleClick` | `DoubleTapped` | Different event args |
|
||||
| `PreviewMouseDown` | `PointerPressed` | No tunneling — use `Handled` or `AddHandler` |
|
||||
| `PreviewKeyDown` | `KeyDown` | `KeyEventArgs` → `KeyRoutedEventArgs` |
|
||||
|
||||
### Property Replacements
|
||||
|
||||
| WPF | WinUI 3 | Context |
|
||||
|-----|---------|---------|
|
||||
| `Visibility.Hidden` | `Visibility.Collapsed` or `Opacity="0"` | Use `Opacity="0"` to preserve layout |
|
||||
| `TextWrapping.WrapWithOverflow` | `TextWrapping.Wrap` | WinUI doesn't distinguish |
|
||||
| `Focusable="True"` | `IsTabStop="True"` | Different property name |
|
||||
| `ContextMenu=` | `ContextFlyout=` | On any `UIElement` |
|
||||
| `MediaElement` | `MediaPlayerElement` | Different API |
|
||||
| `SnapsToDevicePixels` | Not available | WinUI handles pixel snapping internally |
|
||||
|
||||
### NuGet Package Migration
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` |
|
||||
| `WPF-UI` (Lepo) | Remove — use native WinUI 3 controls |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` |
|
||||
| (none) | `Microsoft.WindowsAppSDK` |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` |
|
||||
| (none) | `WinUIEx` (optional, for window helpers) |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` |
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `Microsoft.Xaml.Behaviors.Wpf` | `Microsoft.Xaml.Behaviors.WinUI.Managed` | |
|
||||
| `WPF-UI` (Lepo) | **Remove** — use native WinUI 3 controls | |
|
||||
| `CommunityToolkit.Mvvm` | `CommunityToolkit.Mvvm` (same) | |
|
||||
| `Microsoft.Toolkit.Wpf.*` | `CommunityToolkit.WinUI.*` | |
|
||||
| (none) | `Microsoft.WindowsAppSDK` | Required |
|
||||
| (none) | `Microsoft.Windows.SDK.BuildTools` | Required |
|
||||
| (none) | `WinUIEx` | Optional, window helpers |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Primitives` | Optional — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.HeaderedControls` | Optional — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.SettingsControls` | Optional — `SettingsCard`, `SettingsExpander` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Sizers` | Optional — `GridSplitter` |
|
||||
| (none) | `CommunityToolkit.WinUI.UI.Controls.DataGrid` | Legacy v7 — only for migrating existing `DataGrid` code; prefer `WinUI.TableView` |
|
||||
|
||||
### XAML Syntax Changes
|
||||
|
||||
| WPF | WinUI 3 |
|
||||
|-----|---------|
|
||||
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` |
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind |
|
||||
| `{x:Type local:MyType}` | Not supported |
|
||||
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` |
|
||||
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` |
|
||||
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` |
|
||||
| `<ui:FluentWindow>` / `<ui:Button>` (WPF-UI) | Native `<Window>` / `<Button>` |
|
||||
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` |
|
||||
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / handle via KeyDown |
|
||||
| `<AccessText>` | Not available — use `AccessKey` property |
|
||||
| `<behaviors:Interaction.Triggers>` | Migrate to code-behind or WinUI behaviors |
|
||||
| WPF | WinUI 3 | Notes |
|
||||
|-----|---------|-------|
|
||||
| `xmlns:local="clr-namespace:MyApp"` | `xmlns:local="using:MyApp"` | CLR → using syntax |
|
||||
| `{DynamicResource Key}` | `{ThemeResource Key}` | Re-evaluates on theme change |
|
||||
| `{StaticResource Key}` | `{StaticResource Key}` | Same — resolved once at load |
|
||||
| `{x:Static Type.Member}` | `{x:Bind}` or code-behind | |
|
||||
| `{x:Type local:MyType}` | Not supported | Use `x:DataType` for DataTemplate |
|
||||
| `{x:Array}` | Not supported | Create collections in code-behind |
|
||||
| `<Style.Triggers>` / `<DataTrigger>` | `VisualStateManager` | See [XAML Migration](./references/xaml-migration.md) |
|
||||
| `{Binding}` in `Setter.Value` | Not supported — use `StaticResource` | |
|
||||
| `Content="{x:Static p:Resources.Cancel}"` | `x:Uid="Cancel"` with `.Content` in `.resw` | |
|
||||
| `sys:String` / `sys:Int32` / etc. | `x:String` / `x:Int32` / etc. | XAML intrinsic types |
|
||||
| `<ui:FluentWindow>` (WPF-UI) | `<Window>` | Native + `ExtendsContentIntoTitleBar` |
|
||||
| `<ui:NumberBox>` / `<ui:ProgressRing>` (WPF-UI) | Native `<NumberBox>` / `<ProgressRing>` | |
|
||||
| `BasedOn="{StaticResource {x:Type ui:Button}}"` | `BasedOn="{StaticResource DefaultButtonStyle}"` | Named style keys |
|
||||
| `IsDefault="True"` / `IsCancel="True"` | `Style="{StaticResource AccentButtonStyle}"` / KeyDown | |
|
||||
| `<AccessText>` | Not available — use `AccessKey` property | |
|
||||
| `<behaviors:Interaction.Triggers>` | Code-behind or WinUI behaviors | |
|
||||
| `Window.Resources` | Root container's `Resources` (e.g. `Grid.Resources`) | Window is not a DependencyObject |
|
||||
|
||||
### Binding: {Binding} vs {x:Bind}
|
||||
|
||||
Both work in WinUI 3. Prefer `{x:Bind}` for new/migrated code.
|
||||
|
||||
| Feature | `{Binding}` | `{x:Bind}` |
|
||||
|---------|------------|------------|
|
||||
| Default mode | `OneWay` | **`OneTime`** — add `Mode=OneWay` explicitly! |
|
||||
| Default source | `DataContext` | Page/UserControl code-behind |
|
||||
| Compile-time validation | No | Yes |
|
||||
| Function binding | No | Yes (replaces `MultiBinding`) |
|
||||
| Performance | Reflection-based | Compiled, no reflection |
|
||||
| `MultiBinding` support | No (not in WinUI) | Use function binding |
|
||||
|
||||
## Detailed Reference Docs
|
||||
|
||||
@@ -151,6 +287,8 @@ Read only the section relevant to your current task:
|
||||
| JPEG quality value wrong after migration | WPF: int 1-100; WinRT: float 0.0-1.0 |
|
||||
| MSIX packaging fails in PreBuildEvent | Move to PostBuildEvent; artifacts not ready at PreBuild time |
|
||||
| RC file icon path with forward slashes | Use double-backslash escaping: `..\\ui\\Assets\\icon.ico` |
|
||||
| `COMException: ClassFactory cannot supply requested class` at startup | Missing `<WindowsPackageType>None</WindowsPackageType>` and/or `<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>` in csproj. Without these, the app tries to locate the Windows App SDK framework package (not installed) instead of using bundled runtime DLLs. **Both properties are mandatory for every WinUI 3 module in PowerToys.** |
|
||||
| `CombinedGeometry` not available in WinUI 3 | WinUI 3 `UIElement.Clip` only accepts `RectangleGeometry`. For overlay hole effects (exclude region), use a `Path` element with `GeometryGroup FillRule="EvenOdd"` containing two `RectangleGeometry` children — the EvenOdd rule creates a transparent hole where geometries overlap. |
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
@@ -163,3 +301,4 @@ Read only the section relevant to your current task:
|
||||
| NuGet restore failures | Run `build-essentials.cmd` after adding `Microsoft.WindowsAppSDK` package |
|
||||
| `Parallel.ForEach` compilation error | Migrate to `Parallel.ForEachAsync` for async imaging operations |
|
||||
| Signing check fails on leaked artifacts | Run `generateAllFileComponents.ps1`; verify only `WinUI3Apps\\` paths in signing config |
|
||||
| `COMException` / `ClassFactory` error at app launch | Ensure csproj has `<WindowsPackageType>None</WindowsPackageType>` and `<WindowsAppSDKSelfContained>true</WindowsAppSDKSelfContained>`. These are required for all unpackaged WinUI 3 apps in PowerToys — without them the WinUI 3 COM runtime cannot be found. |
|
||||
|
||||
@@ -4,24 +4,26 @@ Complete reference for mapping WPF types to WinUI 3 equivalents, based on the Im
|
||||
|
||||
## Root Namespace Mapping
|
||||
|
||||
| WPF Namespace | WinUI 3 Namespace |
|
||||
|---------------|-------------------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` |
|
||||
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` |
|
||||
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` |
|
||||
| WPF Namespace | WinUI 3 Namespace | Notes |
|
||||
|---------------|-------------------|-------|
|
||||
| `System.Windows` | `Microsoft.UI.Xaml` | Root namespace |
|
||||
| `System.Windows.Automation` | `Microsoft.UI.Xaml.Automation` | Accessibility / UI Automation |
|
||||
| `System.Windows.Automation.Peers` | `Microsoft.UI.Xaml.Automation.Peers` | |
|
||||
| `System.Windows.Controls` | `Microsoft.UI.Xaml.Controls` | Core controls |
|
||||
| `System.Windows.Controls.Primitives` | `Microsoft.UI.Xaml.Controls.Primitives` | Low-level primitives |
|
||||
| `System.Windows.Data` | `Microsoft.UI.Xaml.Data` | Binding, IValueConverter |
|
||||
| `System.Windows.Documents` | `Microsoft.UI.Xaml.Documents` | Limited — RichTextBlock + Paragraph only |
|
||||
| `System.Windows.Input` | `Microsoft.UI.Xaml.Input` | Pointer, keyboard, focus |
|
||||
| `System.Windows.Markup` | `Microsoft.UI.Xaml.Markup` | XAML parsing, markup extensions |
|
||||
| `System.Windows.Media` | `Microsoft.UI.Xaml.Media` | Brushes, transforms |
|
||||
| `System.Windows.Media.Animation` | `Microsoft.UI.Xaml.Media.Animation` | Storyboard, animations |
|
||||
| `System.Windows.Media.Imaging` | `Microsoft.UI.Xaml.Media.Imaging` | UI display only — use `Windows.Graphics.Imaging` for processing |
|
||||
| `System.Windows.Media.Media3D` | **No equivalent** | Use Win2D or Composition APIs |
|
||||
| `System.Windows.Navigation` | `Microsoft.UI.Xaml.Navigation` | For Frame navigation events; no `NavigationService` |
|
||||
| `System.Windows.Shapes` | `Microsoft.UI.Xaml.Shapes` | Rectangle, Ellipse, Path |
|
||||
| `System.Windows.Threading` | `Microsoft.UI.Dispatching` | Dispatcher → DispatcherQueue |
|
||||
| `System.Windows.Interop` | `WinRT.Interop` | HWND interop |
|
||||
| `System.Windows.Interop` | `Microsoft.UI.Xaml.Hosting` | XAML Islands |
|
||||
|
||||
## Core Type Mapping
|
||||
|
||||
@@ -45,7 +47,7 @@ Complete reference for mapping WPF types to WinUI 3 equivalents, based on the Im
|
||||
|
||||
These controls exist in both frameworks with the same name — change `System.Windows.Controls` to `Microsoft.UI.Xaml.Controls`:
|
||||
|
||||
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
|
||||
`Button`, `TextBox`, `TextBlock`, `ComboBox`, `CheckBox`, `ListView`, `Image`, `StackPanel`, `Grid`, `Border`, `ScrollViewer`, `ContentControl`, `UserControl`, `Page`, `Frame`, `Slider`, `ProgressBar`, `ToolTip`, `RadioButton`, `ToggleButton`
|
||||
|
||||
### Controls With Different Names or Behavior
|
||||
|
||||
@@ -56,6 +58,7 @@ These controls exist in both frameworks with the same name — change `System.Wi
|
||||
| `TabControl` | `TabView` | Different API |
|
||||
| `Menu` | `MenuBar` | Different API |
|
||||
| `StatusBar` | Custom `StackPanel` layout | No built-in equivalent |
|
||||
| `ListBox` | `ListView` (or `ItemsView`) | **Deprecated in WinUI 3 — do not use.** Prefer `ListView`, or `ItemsView` (WinUI 1.5+) for modern collection scenarios |
|
||||
| `AccessText` | Not available | Use `AccessKey` property on target control |
|
||||
|
||||
### WPF-UI (Lepo) to Native WinUI 3
|
||||
@@ -109,6 +112,43 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
|
||||
| `System.Windows.Interop.WindowInteropHelper` | `WinRT.Interop.WindowNative.GetWindowHandle()` | |
|
||||
| `System.Windows.SystemColors` | Resource keys via `ThemeResource` | No direct static class |
|
||||
| `System.Windows.SystemParameters` | Win32 API or `DisplayInformation` | No direct equivalent |
|
||||
| `System.Windows.Clipboard` | `Windows.ApplicationModel.DataTransfer.Clipboard` | Different API surface |
|
||||
| `System.Windows.Input.RoutedUICommand` | `Microsoft.UI.Xaml.Input.StandardUICommand` / `XamlUICommand` | Or use `ICommand` / `[RelayCommand]` |
|
||||
| `System.Windows.Input.CommandBinding` | **Remove** | Bind `ICommand` directly in XAML |
|
||||
|
||||
## Controls That Need Translation (No 1:1 Mapping)
|
||||
|
||||
These controls exist in WPF but require a different control, third-party library, or Community Toolkit package in WinUI 3:
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | Package / Notes |
|
||||
|-------------|---------------------|-----------------|
|
||||
| `DataGrid` | [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) | Community library; the Toolkit `DataGrid` is no longer maintained. PowerToys legacy modules may still pin v7 `CommunityToolkit.WinUI.UI.Controls.DataGrid` 7.1.2 — prefer `WinUI.TableView` for new work. |
|
||||
| `Ribbon` | `CommandBar` / `NavigationView`, or [Toolkit Labs Ribbon](https://github.com/CommunityToolkit/Labs-Windows/tree/main/components/Ribbon) | No first-party Ribbon in WinUI; the Labs component is experimental and partial |
|
||||
| `Menu` / `MenuItem` | `MenuBar` / `MenuBarItem` / `MenuFlyoutItem` | `MenuBar` for classic menu, `MenuFlyout` for context |
|
||||
| `ContextMenu` | `MenuFlyout` | Assign to `ContextFlyout` property |
|
||||
| `ToolBar` / `ToolBarTray` | `CommandBar` + `AppBarButton` | |
|
||||
| `StatusBar` | Custom `Grid`/`StackPanel` or `InfoBar` | No StatusBar control |
|
||||
| `TabControl` | `TabView` or `NavigationView` (top mode) | `TabView` for closeable tabs |
|
||||
| `DocumentViewer` | `WebView2` | `Microsoft.Web.WebView2` — render PDFs/XPS |
|
||||
| `FlowDocument` | `RichTextBlock` | Partial replacement only |
|
||||
| `RichTextBox` | `RichEditBox` | Rich text editing |
|
||||
| `GroupBox` | `Expander` (built-in) or `HeaderedContentControl` (Toolkit) | See [Layout & Header Controls from CommunityToolkit.WinUI](#layout--header-controls-from-communitytoolkitwinui) below |
|
||||
| `Label` | `TextBlock` | WPF `Label` is a `ContentControl`; use `TextBlock` + `AccessKey` |
|
||||
| `TreeView` | `TreeView` (native) | Available natively, but data binding model differs significantly |
|
||||
| `MediaElement` | `MediaPlayerElement` | Different API |
|
||||
|
||||
## Layout & Header Controls from CommunityToolkit.WinUI
|
||||
|
||||
These WPF layout/header controls have no built-in WinUI 3 equivalent — install the corresponding CommunityToolkit package. **The NuGet package id and the XAML namespace differ intentionally**: package names end in `.Primitives` / `.HeaderedControls`, but both packages register their controls in the shorter `CommunityToolkit.WinUI.Controls` XAML namespace (confirmed in the [official Microsoft Q&A](https://learn.microsoft.com/en-us/answers/questions/5746230/why-does-communitytoolkit-uwp-controls-primitive-u): *"all controls live under `CommunityToolkit.WinUI.Controls` … This is intentional"*).
|
||||
|
||||
| WPF Control | WinUI 3 Replacement | NuGet Package | XAML Namespace |
|
||||
|-------------|---------------------|---------------|----------------|
|
||||
| `WrapPanel` | `WrapPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `UniformGrid` | `UniformGrid` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `DockPanel` | `DockPanel` | `CommunityToolkit.WinUI.Controls.Primitives` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
| `GroupBox` (alt.) | `HeaderedContentControl` | `CommunityToolkit.WinUI.Controls.HeaderedControls` | `using:CommunityToolkit.WinUI.Controls` |
|
||||
|
||||
Other primitives in `Primitives`: `ConstrainedBox`, `SwitchPresenter`, `WrapLayout`, `StaggeredPanel`. Other Headered controls in `HeaderedControls`: `HeaderedItemsControl`, `HeaderedTreeView`.
|
||||
|
||||
## NuGet Package Migration
|
||||
|
||||
@@ -124,6 +164,11 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
|
||||
| (none) | `WinUIEx` | Optional, window helpers |
|
||||
| (none) | `CommunityToolkit.WinUI.Converters` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Extensions` | Optional |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Primitives` | Optional — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox`, `SwitchPresenter` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.HeaderedControls` | Optional — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.SettingsControls` | Optional — `SettingsCard`, `SettingsExpander` |
|
||||
| (none) | `CommunityToolkit.WinUI.Controls.Sizers` | Optional — `GridSplitter`, `PropertySizer` |
|
||||
| (none) | `CommunityToolkit.WinUI.UI.Controls.DataGrid` | Legacy v7 — only if migrating existing `DataGrid` code; prefer [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) for new work |
|
||||
| (none) | `Microsoft.Web.WebView2` | If using WebView |
|
||||
|
||||
## Project File Changes
|
||||
@@ -167,8 +212,9 @@ Last parameter changes from `CultureInfo` to `string` (BCP-47 language tag). All
|
||||
Key changes:
|
||||
- `UseWPF` → `UseWinUI`
|
||||
- TFM: `net8.0-windows` → `net8.0-windows10.0.19041.0`
|
||||
- Add `WindowsPackageType=None` for unpackaged desktop apps
|
||||
- Add `SelfContained=true` + `WindowsAppSDKSelfContained=true`
|
||||
- **CRITICAL: Add `WindowsPackageType=None`** — marks the app as unpackaged (no MSIX). Without this, the build produces an MSIX-style package that won't run as a standalone PowerToys module.
|
||||
- **CRITICAL: Add `WindowsAppSDKSelfContained=true`** — bundles the Windows App SDK runtime DLLs (e.g. `Microsoft.UI.Xaml.dll`) into the output directory. Without this, the app throws `COMException: ClassFactory cannot supply requested class` at startup because the WinUI 3 COM classes cannot be found.
|
||||
- Add `SelfContained=true` (usually via `Common.SelfContained.props`)
|
||||
- Add `DISABLE_XAML_GENERATED_MAIN` if using custom `Program.cs` entry point
|
||||
- Set `ProjectPriFileName` to match your module's assembly name
|
||||
- Move icon from `Resources/` to `Assets/<Module>/`
|
||||
|
||||
@@ -127,6 +127,74 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
|
||||
</Window>
|
||||
```
|
||||
|
||||
#### Recommended: Use WindowEx from WinUIEx
|
||||
|
||||
> **Tip:** Prefer `WinUIEx.WindowEx` over bare `Window`. It restores many WPF-like window properties directly in XAML, avoiding boilerplate code-behind for common windowing tasks.
|
||||
|
||||
```xml
|
||||
<!-- WinUI 3 with WindowEx (preferred in PowerToys) -->
|
||||
<winuiex:WindowEx
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
x:Class="MyApp.MainWindow"
|
||||
MinWidth="480"
|
||||
MinHeight="320"
|
||||
IsShownInSwitchers="True"
|
||||
IsTitleBarVisible="True">
|
||||
<Window.SystemBackdrop>
|
||||
<MicaBackdrop />
|
||||
</Window.SystemBackdrop>
|
||||
<Grid>
|
||||
...
|
||||
</Grid>
|
||||
</winuiex:WindowEx>
|
||||
```
|
||||
|
||||
Properties available on `WindowEx` that mirror WPF `Window`:
|
||||
|
||||
| WPF Window Property | WindowEx Property | Notes |
|
||||
|---------------------|-------------------|-------|
|
||||
| `MinWidth` / `MinHeight` | `MinWidth` / `MinHeight` | Set directly in XAML |
|
||||
| `Width` / `Height` | `Width` / `Height` | Initial window size |
|
||||
| `WindowState` | `WindowState` | Minimized, Maximized, Normal |
|
||||
| `Title` | `Title` or `x:Uid` | Window title |
|
||||
| `Icon` | Use `TitleBar.IconSource` | Via WinUI TitleBar control |
|
||||
| `ShowInTaskbar` | `IsShownInSwitchers` | Alt-Tab visibility |
|
||||
| `TopMost` | `IsAlwaysOnTop` | Always-on-top window |
|
||||
|
||||
NuGet: `WinUIEx` — already referenced by most PowerToys modules.
|
||||
|
||||
#### Recommended: Page-in-Window Architecture
|
||||
|
||||
> **Tip:** WinUI 3 `Window` is NOT a `FrameworkElement` — it does not support `Resources`, `DataContext`, `x:Bind`, or `VisualStateManager` directly. Place a `Page` as the Window's root content to regain these WPF-like capabilities.
|
||||
|
||||
```xml
|
||||
<!-- WinUI 3 — Window contains a Page for full FrameworkElement support -->
|
||||
<winuiex:WindowEx x:Class="MyApp.MainWindow"
|
||||
xmlns:winuiex="using:WinUIEx"
|
||||
xmlns:views="using:MyApp.Views">
|
||||
<views:MainPage x:Name="mainPage" />
|
||||
</winuiex:WindowEx>
|
||||
```
|
||||
|
||||
```xml
|
||||
<!-- MainPage.xaml — has full FrameworkElement capabilities -->
|
||||
<Page x:Class="MyApp.Views.MainPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<Page.Resources>
|
||||
<!-- Resources work here (unlike on Window) -->
|
||||
<SolidColorBrush x:Key="MyBrush" Color="Red"/>
|
||||
</Page.Resources>
|
||||
<Grid>
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<!-- VisualStateManager works here (unlike on Window) -->
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
...
|
||||
</Grid>
|
||||
</Page>
|
||||
```
|
||||
|
||||
This is the standard pattern in PowerToys (e.g., FileLocksmith, EnvironmentVariables).
|
||||
|
||||
### App.xaml Resources
|
||||
|
||||
```xml
|
||||
@@ -150,6 +218,20 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### CommunityToolkit.WinUI — WPF Replacement Controls
|
||||
|
||||
> **Tip:** The `CommunityToolkit.WinUI` package provides many controls and helpers familiar to WPF developers that are missing from WinUI 3 out of the box. Before writing custom replacements, check whether CommunityToolkit already provides what you need.
|
||||
|
||||
Key packages (XAML namespace is `using:CommunityToolkit.WinUI.Controls` for the `Controls.*` family):
|
||||
- **`CommunityToolkit.WinUI.Controls.Primitives`** — `WrapPanel`, `UniformGrid`, `DockPanel`, `ConstrainedBox`, `SwitchPresenter`
|
||||
- **`CommunityToolkit.WinUI.Controls.HeaderedControls`** — `HeaderedContentControl`, `HeaderedItemsControl`, `HeaderedTreeView`
|
||||
- **`CommunityToolkit.WinUI.Controls.SettingsControls`** — `SettingsCard`, `SettingsExpander`
|
||||
- **`CommunityToolkit.WinUI.Controls.Sizers`** — `GridSplitter`, `PropertySizer`, `ContentSizer`
|
||||
- **`CommunityToolkit.WinUI.UI.Controls.DataGrid`** — legacy v7 `DataGrid` (no longer maintained); prefer [`WinUI.TableView`](https://github.com/w-ahmad/WinUI.TableView) for new work
|
||||
- **`CommunityToolkit.WinUI.Converters`** — Common value converters (`BoolToVisibilityConverter`, `StringFormatConverter`, etc.)
|
||||
- **`CommunityToolkit.WinUI.Behaviors`** — XAML behaviors for animations and interactions
|
||||
- **`CommunityToolkit.WinUI.Extensions`** — Extension methods for WinUI types
|
||||
|
||||
### Common Control Replacements
|
||||
|
||||
```xml
|
||||
@@ -187,11 +269,135 @@ If the module uses the `WPF-UI` library, replace all Lepo controls with native W
|
||||
<!-- Handle Enter/Escape keys in code-behind if needed -->
|
||||
```
|
||||
|
||||
## No-Equivalent Patterns (Requires Architectural Rework)
|
||||
|
||||
These WPF features demand design changes, not find-and-replace. Read this section BEFORE attempting to migrate any file that uses these patterns.
|
||||
|
||||
### MultiBinding → x:Bind Function Binding
|
||||
|
||||
WinUI does not support `MultiBinding`. Replace with `x:Bind` function binding (most direct replacement), a computed ViewModel property, or multiple simple bindings.
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<TextBlock>
|
||||
<TextBlock.Text>
|
||||
<MultiBinding StringFormat="{}{0} {1}">
|
||||
<Binding Path="FirstName" />
|
||||
<Binding Path="LastName" />
|
||||
</MultiBinding>
|
||||
</TextBlock.Text>
|
||||
</TextBlock>
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<TextBlock Text="{x:Bind local:Converters.FormatFullName(ViewModel.FirstName, ViewModel.LastName), Mode=OneWay}" />
|
||||
```
|
||||
|
||||
```csharp
|
||||
public static class Converters
|
||||
{
|
||||
public static string FormatFullName(string first, string last) => $"{first} {last}";
|
||||
}
|
||||
```
|
||||
|
||||
### Adorners → Context-Dependent Replacements
|
||||
|
||||
WPF's `AdornerLayer` has no WinUI equivalent. Choose replacement by use case:
|
||||
|
||||
| Adorner Use Case | WinUI 3 Replacement |
|
||||
|------------------|---------------------|
|
||||
| Validation indicators | `TeachingTip`, `InfoBar`, or InputValidation templates |
|
||||
| Resize handles | `Popup` positioned relative to target |
|
||||
| Drag preview | `DragItemsStarting` event with custom DragUI |
|
||||
| Overlay decorations | Canvas overlay or Popup layer |
|
||||
| Watermark / Placeholder | `TextBox.PlaceholderText` (built-in) |
|
||||
|
||||
### RoutedUICommand → ICommand / RelayCommand
|
||||
|
||||
WinUI does not support routed commands or `CommandBinding`. Replace with standard `ICommand` pattern:
|
||||
|
||||
```csharp
|
||||
// CommunityToolkit.Mvvm
|
||||
[RelayCommand(CanExecute = nameof(CanSave))]
|
||||
private void Save() { /* save logic */ }
|
||||
private bool CanSave() => IsDirty;
|
||||
```
|
||||
|
||||
WinUI 3 also provides `StandardUICommand` and `XamlUICommand` for pre-defined platform commands (Cut, Copy, Paste, Delete) with built-in icons and keyboard accelerators.
|
||||
|
||||
### Tunneling / Preview Events
|
||||
|
||||
WinUI has no tunneling event model. `PreviewMouseDown`, `PreviewKeyDown`, etc. do not exist.
|
||||
|
||||
- Replace with the bubbling equivalent (`PointerPressed`, `KeyDown`)
|
||||
- If you relied on tunneling to intercept events before children, restructure using the `Handled` property
|
||||
- For must-handle scenarios, use `AddHandler` with `handledEventsToo: true`:
|
||||
```csharp
|
||||
myElement.AddHandler(UIElement.PointerPressedEvent,
|
||||
new PointerEventHandler(OnPointerPressed), handledEventsToo: true);
|
||||
```
|
||||
|
||||
## Style and Template Changes
|
||||
|
||||
### Implicit Styles — Always Use BasedOn
|
||||
|
||||
> **Warning:** In WinUI 3, always use `BasedOn` when overriding default control styles. Without it, your style **replaces the entire default style** rather than extending it.
|
||||
|
||||
```xml
|
||||
<!-- WRONG — replaces entire default style, control may lose all visual appearance -->
|
||||
<Style TargetType="Button">
|
||||
<Setter Property="Background" Value="Red" />
|
||||
</Style>
|
||||
|
||||
<!-- CORRECT — extends the default style -->
|
||||
<Style TargetType="Button" BasedOn="{StaticResource DefaultButtonStyle}">
|
||||
<Setter Property="Background" Value="Red" />
|
||||
</Style>
|
||||
```
|
||||
|
||||
### Triggers → VisualStateManager
|
||||
|
||||
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported. Two replacement approaches:
|
||||
|
||||
#### Approach 1: StateTrigger (direct DataTrigger replacement — simpler)
|
||||
|
||||
Use this for data-driven state changes. This is the closest equivalent to WPF `DataTrigger`:
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
<Style TargetType="Border">
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsActive}" Value="True">
|
||||
<Setter Property="Background" Value="Green" />
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
```
|
||||
|
||||
**WinUI 3:**
|
||||
```xml
|
||||
<Border x:Name="MyBorder">
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup>
|
||||
<VisualState x:Name="Active">
|
||||
<VisualState.StateTriggers>
|
||||
<StateTrigger IsActive="{x:Bind ViewModel.IsActive, Mode=OneWay}" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="MyBorder.Background" Value="Green" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Border>
|
||||
```
|
||||
|
||||
Note: `VisualStateManager` must be placed on a control inside the Window, NOT on the Window itself.
|
||||
|
||||
#### Approach 2: ControlTemplate (for property triggers like IsMouseOver)
|
||||
|
||||
Use this when replacing `<Trigger Property="IsMouseOver">` or similar control-state triggers:
|
||||
|
||||
**WPF:**
|
||||
```xml
|
||||
@@ -200,9 +406,6 @@ WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
<Trigger Property="IsMouseOver" Value="True">
|
||||
<Setter Property="Background" Value="LightBlue"/>
|
||||
</Trigger>
|
||||
<DataTrigger Binding="{Binding IsEnabled}" Value="False">
|
||||
<Setter Property="Opacity" Value="0.5"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
```
|
||||
@@ -249,11 +452,42 @@ WPF `Triggers`, `DataTriggers`, and `EventTriggers` are not supported.
|
||||
| `Disabled` | `Disabled` |
|
||||
| `Pressed` | `Pressed` |
|
||||
|
||||
## Visibility.Hidden — No Equivalent
|
||||
|
||||
WinUI only has `Visible` and `Collapsed`. There is no `Hidden`.
|
||||
|
||||
| WPF | WinUI 3 | Behavior |
|
||||
|-----|---------|----------|
|
||||
| `Visibility.Visible` | `Visibility.Visible` | Rendered and occupies layout space |
|
||||
| `Visibility.Hidden` | **Not available** | Use `Opacity="0"` with `Visibility="Visible"` to hide but keep layout |
|
||||
| `Visibility.Collapsed` | `Visibility.Collapsed` | Not rendered, no layout space |
|
||||
|
||||
## Resource Dictionary Changes
|
||||
|
||||
### Window.Resources → Grid.Resources
|
||||
### XamlControlsResources Must Be First
|
||||
|
||||
WinUI 3 `Window` is NOT a `DependencyObject` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
|
||||
> **Warning:** `XamlControlsResources` must be the **first** merged dictionary in `App.xaml`. It provides the default Fluent styles. Omitting it gives you controls with no visual appearance. Resource paths use `ms-appx:///` instead of relative paths.
|
||||
|
||||
```xml
|
||||
<Application.Resources>
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- MUST be first -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
<!-- Then your custom dictionaries -->
|
||||
<ResourceDictionary Source="ms-appx:///Styles/Colors.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
```
|
||||
|
||||
### Window.Resources → Grid.Resources (or use Page)
|
||||
|
||||
WinUI 3 `Window` is NOT a `FrameworkElement` — no `Window.Resources`, `DataContext`, or `VisualStateManager`.
|
||||
|
||||
**Preferred approach:** Use the [Page-in-Window architecture](#recommended-page-in-window-architecture) described above. A `Page` inside the `Window` gives you full `FrameworkElement` capabilities (Resources, DataContext, x:Bind, VisualStateManager).
|
||||
|
||||
**Fallback** (for simple windows without a Page):
|
||||
|
||||
```xml
|
||||
<!-- WPF -->
|
||||
@@ -317,19 +551,25 @@ Both are available. Prefer `{x:Bind}` for compile-time safety and performance.
|
||||
| Performance | Reflection-based | Compiled |
|
||||
| Function binding | No | Yes |
|
||||
|
||||
### WPF-Specific Binding Features to Remove
|
||||
### Binding Differences from WPF
|
||||
|
||||
These WPF binding patterns behave differently in WinUI 3 — review on a case-by-case basis rather than mechanically removing.
|
||||
|
||||
```xml
|
||||
<!-- These WPF-only features must be removed or rewritten -->
|
||||
<!-- UpdateSourceTrigger: limited support in WinUI 3 -->
|
||||
<TextBox Text="{Binding Value, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<!-- WinUI 3: UpdateSourceTrigger not needed; TextBox uses PropertyChanged by default -->
|
||||
<!-- WinUI 3: UpdateSourceTrigger=LostFocus does NOT exist; PropertyChanged is the TextBox default.
|
||||
Prefer x:Bind, which binds with PropertyChanged semantics by default for TwoWay. -->
|
||||
<TextBox Text="{x:Bind ViewModel.Value, Mode=TwoWay}" />
|
||||
|
||||
{Binding RelativeSource={RelativeSource Self}, ...}
|
||||
<!-- WinUI 3: Use x:Bind which binds to the page itself, or use ElementName -->
|
||||
<!-- RelativeSource: Self and TemplatedParent ARE supported in WinUI 3 -->
|
||||
<TextBlock Text="{Binding Tag, RelativeSource={RelativeSource Self}}" />
|
||||
<!-- Works in WinUI 3. FindAncestor mode is NOT supported — use ElementName, x:Bind,
|
||||
or the CommunityToolkit FrameworkElementExtensions.Ancestor attached property to reach ancestors. -->
|
||||
|
||||
<!-- {Binding} empty path: works in WinUI 3 (binds to the current DataContext) -->
|
||||
<ItemsControl ItemsSource="{Binding}" />
|
||||
<!-- WinUI 3: Must specify explicit path -->
|
||||
<!-- This is valid. Note: x:Bind requires an explicit path — there is no empty-path x:Bind. -->
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.Items}" />
|
||||
```
|
||||
|
||||
@@ -355,6 +595,64 @@ ExtendsContentIntoTitleBar="True" <!-- Set in code-behind -->
|
||||
| `IsHitTestVisible` | `IsHitTestVisible` | Same |
|
||||
| `TextBox.VerticalScrollBarVisibility` | `ScrollViewer.VerticalScrollBarVisibility` (attached) | Attached property |
|
||||
|
||||
## Complete Find-and-Replace Reference
|
||||
|
||||
Use this table for mechanical batch translation. Apply these rules consistently to every file.
|
||||
|
||||
### XAML Attribute Replacements
|
||||
|
||||
| Find | Replace With | Context |
|
||||
|------|-------------|---------|
|
||||
| `ContextMenu=` | `ContextFlyout=` | On any UIElement |
|
||||
| `{DynamicResource ` | `{ThemeResource ` | Theme-responsive references |
|
||||
| `{x:Static prefix:Resources.Key}` | `x:Uid="Key"` (with `.resw`) | Resource string — most common WPF case; mechanical `{x:Bind}` will NOT compile here |
|
||||
| `{x:Static prefix:Type.Member}` | `{x:Bind prefix:Type.Member}` | Static field/property reference (function binding) |
|
||||
| `Visibility="Hidden"` | `Visibility="Collapsed"` | Or use `Opacity="0"` for layout |
|
||||
| `MouseLeftButtonDown` | `PointerPressed` | Event handlers |
|
||||
| `MouseLeftButtonUp` | `PointerReleased` | Event handlers |
|
||||
| `MouseRightButtonDown` | `RightTapped` | Or `PointerPressed` + check `IsRightButtonPressed` |
|
||||
| `MouseRightButtonUp` | `PointerReleased` + check `IsRightButtonPressed` | No direct WinUI event; use `RightTapped` only for context-menu-open semantics |
|
||||
| `MouseEnter` | `PointerEntered` | Event handlers |
|
||||
| `MouseLeave` | `PointerExited` | Event handlers |
|
||||
| `MouseMove` | `PointerMoved` | Event handlers |
|
||||
| `MouseWheel` | `PointerWheelChanged` | Event handlers |
|
||||
| `MouseDoubleClick` | `DoubleTapped` | Event handlers |
|
||||
| `PreviewMouseDown` | `PointerPressed` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewMouseUp` | `PointerReleased` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewKeyDown` | `KeyDown` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewKeyUp` | `KeyUp` | No tunneling in WinUI — remove Preview |
|
||||
| `PreviewMouseWheel` | `PointerWheelChanged` | No tunneling in WinUI — remove Preview |
|
||||
| `Focusable="True"` | `IsTabStop="True"` | Focus behavior |
|
||||
| `Focusable="False"` | `IsTabStop="False"` | Focus behavior |
|
||||
| `TextWrapping="WrapWithOverflow"` | `TextWrapping="Wrap"` | TextBlock, TextBox |
|
||||
| `MediaElement` | `MediaPlayerElement` | Media playback |
|
||||
| `clr-namespace:` | `using:` | xmlns declarations |
|
||||
| `;assembly=` | (remove) | Assembly qualification not needed |
|
||||
|
||||
### Code-Behind Replacements
|
||||
|
||||
| Find | Replace With |
|
||||
|------|-------------|
|
||||
| `using System.Windows;` | `using Microsoft.UI.Xaml;` |
|
||||
| `using System.Windows.Controls;` | `using Microsoft.UI.Xaml.Controls;` |
|
||||
| `using System.Windows.Media;` | `using Microsoft.UI.Xaml.Media;` |
|
||||
| `using System.Windows.Data;` | `using Microsoft.UI.Xaml.Data;` |
|
||||
| `using System.Windows.Input;` | `using Microsoft.UI.Xaml.Input;` |
|
||||
| `using System.Windows.Threading;` | `using Microsoft.UI.Dispatching;` |
|
||||
| `using System.Windows.Shapes;` | `using Microsoft.UI.Xaml.Shapes;` |
|
||||
| `using System.Windows.Markup;` | `using Microsoft.UI.Xaml.Markup;` |
|
||||
| `using System.Windows.Automation;` | `using Microsoft.UI.Xaml.Automation;` |
|
||||
| `using System.Windows.Media.Animation;` | `using Microsoft.UI.Xaml.Media.Animation;` |
|
||||
| `using System.Windows.Documents;` | `using Microsoft.UI.Xaml.Documents;` |
|
||||
| `using System.Windows.Navigation;` | `using Microsoft.UI.Xaml.Navigation;` |
|
||||
| `Dispatcher.Invoke(` | `DispatcherQueue.TryEnqueue(` |
|
||||
| `Dispatcher.BeginInvoke(` | `DispatcherQueue.TryEnqueue(` |
|
||||
| `Dispatcher.CheckAccess()` | `DispatcherQueue.HasThreadAccess` |
|
||||
| `MouseEventArgs` | `PointerRoutedEventArgs` |
|
||||
| `KeyEventArgs` | `KeyRoutedEventArgs` |
|
||||
| `RoutedUICommand` | `RelayCommand` (CommunityToolkit.Mvvm) |
|
||||
| `CommandBinding` | Remove; bind ICommand directly |
|
||||
|
||||
## XAML Formatting (XamlStyler)
|
||||
|
||||
After migration, run XamlStyler to normalize formatting:
|
||||
|
||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -378,3 +378,6 @@ installer/*/*.wxs.bk
|
||||
vcpkg_installed/
|
||||
|
||||
deps/vcpkg/
|
||||
|
||||
# Superpowers-generated docs (specs, design, plans) — local-only, not committed
|
||||
docs/superpowers/
|
||||
|
||||
@@ -78,10 +78,10 @@
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.ImplementationLibrary" Version="1.0.250325.1"/>
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.6901" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.0.20" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.0.185" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.0.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Foundation" Version="2.1.0" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.AI" Version="2.2.3" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK.Runtime" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageVersion Include="Microsoft.Xaml.Behaviors.Wpf" Version="1.1.39" />
|
||||
<PackageVersion Include="ModernWpfUI" Version="0.9.4" />
|
||||
|
||||
@@ -79,4 +79,4 @@ Below are community created plugins that target a website or software. They are
|
||||
| [Linear](https://github.com/vednig/powertoys-linear) | [vednig](https://github.com/vednig) | Create Linear Issues directly from Powertoys Run |
|
||||
| [PerplexitySearchShortcut](https://github.com/0x6f677548/PowerToys-Run-PerplexitySearchShortcut) | [0x6f677548](https://github.com/0x6f677548) | Search Perplexity |
|
||||
| [SpeedTest](https://github.com/ruslanlap/PowerToysRun-SpeedTest) | [ruslanlap](https://github.com/ruslanlap) | One-command internet speed tests with real-time results, modern UI, and shareable links. |
|
||||
| [DiskAnalyzer](https://github.com/thetsaw/PowerToys.Plugin) | [thetsaw](https://github.com/thetsaw) | Scan folders, find the largest files, and view drive space usage with visual progress bars. |
|
||||
| [DiskAnalyzer](https://github.com/valley-soft/powertoys-diskanalyzer) | [ValleySoft](https://github.com/valley-soft) | Scan folders, find the largest files, and view drive space usage. |
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
</ClCompile>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="dark_menu.h" />
|
||||
<ClInclude Include="icon_helpers.h" />
|
||||
<ClInclude Include="theme_listener.h" />
|
||||
<ClInclude Include="theme_helpers.h" />
|
||||
|
||||
97
src/common/Themes/dark_menu.h
Normal file
97
src/common/Themes/dark_menu.h
Normal file
@@ -0,0 +1,97 @@
|
||||
// Theme-aware rendering for classic Win32 popup menus (HMENU / TrackPopupMenu).
|
||||
//
|
||||
// Lets a Win32 tray/popup menu follow the OS light/dark theme. It puts the
|
||||
// process into the matching app theme via uxtheme's preferred-app-mode entry
|
||||
// points -- the same mechanism the OS uses to render dark menus, as already
|
||||
// used by ZoomIt and File Explorer -- and then the system draws the real
|
||||
// themed menu. Native keyboard, accessibility, checkmarks, separators and DPI
|
||||
// are all preserved; only the colors change.
|
||||
//
|
||||
// Theme detection reads the documented AppsUseLightTheme value, fresh on each
|
||||
// call, so a live light<->dark switch is reflected without restarting.
|
||||
//
|
||||
// Drop-in for any PowerToys system-tray utility:
|
||||
//
|
||||
// theme::dark_menu::SetAppMode(theme::dark_menu::IsSystemDarkMode());
|
||||
// TrackPopupMenu(menu, ...);
|
||||
#pragma once
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
namespace theme::dark_menu
|
||||
{
|
||||
namespace details
|
||||
{
|
||||
// uxtheme preferred-app-mode values (order matters).
|
||||
enum class PreferredAppMode
|
||||
{
|
||||
Default = 0,
|
||||
AllowDark,
|
||||
ForceDark,
|
||||
ForceLight,
|
||||
Max
|
||||
};
|
||||
|
||||
using SetPreferredAppModeFn = PreferredAppMode(WINAPI*)(PreferredAppMode);
|
||||
using FlushMenuThemesFn = void(WINAPI*)();
|
||||
|
||||
struct Ordinals
|
||||
{
|
||||
SetPreferredAppModeFn setPreferredAppMode = nullptr;
|
||||
FlushMenuThemesFn flushMenuThemes = nullptr;
|
||||
};
|
||||
|
||||
// Resolved once per process: an inline function's local static is a single
|
||||
// shared instance across all translation units that include this header.
|
||||
inline const Ordinals& GetOrdinals() noexcept
|
||||
{
|
||||
static const Ordinals ordinals = []() noexcept {
|
||||
Ordinals result{};
|
||||
HMODULE uxtheme = GetModuleHandleW(L"uxtheme.dll");
|
||||
if (uxtheme == nullptr)
|
||||
{
|
||||
uxtheme = LoadLibraryExW(L"uxtheme.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
|
||||
}
|
||||
if (uxtheme != nullptr)
|
||||
{
|
||||
// Ordinal 135 = SetPreferredAppMode, ordinal 136 = FlushMenuThemes.
|
||||
result.setPreferredAppMode = reinterpret_cast<SetPreferredAppModeFn>(
|
||||
GetProcAddress(uxtheme, MAKEINTRESOURCEA(135)));
|
||||
result.flushMenuThemes = reinterpret_cast<FlushMenuThemesFn>(
|
||||
GetProcAddress(uxtheme, MAKEINTRESOURCEA(136)));
|
||||
}
|
||||
return result;
|
||||
}();
|
||||
return ordinals;
|
||||
}
|
||||
}
|
||||
|
||||
// True if the user's app theme is dark, via the documented AppsUseLightTheme value.
|
||||
inline bool IsSystemDarkMode() noexcept
|
||||
{
|
||||
DWORD value = 1; // default to light if the value is missing
|
||||
DWORD size = sizeof(value);
|
||||
RegGetValueW(HKEY_CURRENT_USER,
|
||||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize",
|
||||
L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr, &value, &size);
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
// Make this process's classic popup menus render dark or light. Cheap and
|
||||
// idempotent -- call right before TrackPopupMenu so the menu always matches
|
||||
// the current theme, including after a live theme switch. No-op if those
|
||||
// entry points are unavailable.
|
||||
inline void SetAppMode(bool dark) noexcept
|
||||
{
|
||||
const details::Ordinals& ordinals = details::GetOrdinals();
|
||||
if (ordinals.setPreferredAppMode != nullptr)
|
||||
{
|
||||
ordinals.setPreferredAppMode(dark ? details::PreferredAppMode::ForceDark
|
||||
: details::PreferredAppMode::ForceLight);
|
||||
}
|
||||
if (ordinals.flushMenuThemes != nullptr)
|
||||
{
|
||||
ordinals.flushMenuThemes();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1149,13 +1149,10 @@ VideoRecordingSession::VideoRecordingSession(
|
||||
// Store frame interval for timeout-based frame production when webcam is active.
|
||||
m_frameIntervalTicks = ( frameRate > 0 ) ? ( 10'000'000LL / frameRate ) : 333'333LL;
|
||||
|
||||
if (m_audioGenerator)
|
||||
{
|
||||
// Always set up audio profile for loopback capture (stereo AAC)
|
||||
auto audioProps = m_audioGenerator->GetEncodingProperties();
|
||||
m_encodingProfile.Audio(winrt::AudioEncodingProperties::CreateAac(
|
||||
audioProps.SampleRate(), audioProps.ChannelCount(), 192000));
|
||||
}
|
||||
// NOTE: Audio encoding profile (m_encodingProfile.Audio) is set in
|
||||
// StartAsync() after the audio graph is fully initialized, not here.
|
||||
// Calling GetEncodingProperties() before InitializeAsync completes
|
||||
// would crash because m_audioOutputNode is still null.
|
||||
|
||||
// Describe our input: uncompressed BGRA8 buffers
|
||||
auto properties = winrt::VideoEncodingProperties::CreateUncompressed(
|
||||
@@ -1233,7 +1230,16 @@ winrt::IAsyncAction VideoRecordingSession::StartAsync()
|
||||
co_await m_audioGenerator->InitializeAsync();
|
||||
}
|
||||
RecDiag( L"StartAsync: audio initialized\n" );
|
||||
m_streamSource = winrt::MediaStreamSource(m_videoDescriptor, winrt::AudioStreamDescriptor(m_audioGenerator->GetEncodingProperties()));
|
||||
|
||||
// Set up the audio encoding profile now that the audio graph is
|
||||
// fully initialized. GetEncodingProperties() requires
|
||||
// m_audioOutputNode to be valid, which is only guaranteed after
|
||||
// InitializeAsync completes.
|
||||
auto audioProps = m_audioGenerator->GetEncodingProperties();
|
||||
m_encodingProfile.Audio(winrt::AudioEncodingProperties::CreateAac(
|
||||
audioProps.SampleRate(), audioProps.ChannelCount(), 192000));
|
||||
|
||||
m_streamSource = winrt::MediaStreamSource(m_videoDescriptor, winrt::AudioStreamDescriptor(audioProps));
|
||||
}
|
||||
else {
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
#include <common/utils/logger_helper.h>
|
||||
#include <common/utils/winapi_error.h>
|
||||
#include <common/utils/gpo.h>
|
||||
#include <common/Themes/dark_menu.h>
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#endif // __ZOOMIT_POWERTOYS__
|
||||
@@ -10163,8 +10164,23 @@ LRESULT APIENTRY MainWndProc(
|
||||
InsertMenu( hPopupMenu, 0, MF_BYPOSITION|MF_SEPARATOR, 0, NULL );
|
||||
InsertMenu( hPopupMenu, 0, MF_BYPOSITION, IDC_OPTIONS, L"&Options" );
|
||||
}
|
||||
// Apply dark mode theme to the menu
|
||||
// Make the popup theme-aware (dark/light).
|
||||
#ifdef __ZOOMIT_POWERTOYS__
|
||||
// Shared common helper: the OS renders the dark/light menu (native
|
||||
// chrome, keyboard, accessibility). Reusable by other Win32 modules
|
||||
// such as the runner tray menu.
|
||||
//
|
||||
// Detect the theme fresh at open time (respecting ZoomIt's theme
|
||||
// override) so a live light<->dark switch is reflected without a
|
||||
// restart. IsDarkModeEnabled() uses uxtheme's cached state, which a
|
||||
// runtime theme switch may not refresh, whereas the AppsUseLightTheme
|
||||
// registry value (read by IsSystemDarkMode) is always current.
|
||||
const bool useDarkMenu = ( g_ThemeOverride == 1 ) ||
|
||||
( g_ThemeOverride != 0 && theme::dark_menu::IsSystemDarkMode() );
|
||||
theme::dark_menu::SetAppMode( useDarkMenu );
|
||||
#else
|
||||
ApplyDarkModeToMenu( hPopupMenu );
|
||||
#endif
|
||||
TrackPopupMenu( hPopupMenu, 0, pt.x , pt.y, 0, hWnd, NULL );
|
||||
DestroyMenu( hPopupMenu );
|
||||
break;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
<PackageVersion Include="Microsoft.Windows.CsWinRT" Version="2.2.0" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
|
||||
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools.MSIX" Version="1.7.20250829.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.0.1" />
|
||||
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="2.2.0" />
|
||||
<PackageVersion Include="Shmuelie.WinRTServer" Version="2.1.1" />
|
||||
<PackageVersion Include="StyleCop.Analyzers" Version="1.2.0-beta.556" />
|
||||
<PackageVersion Include="System.Text.Json" Version="9.0.8" />
|
||||
|
||||
@@ -453,10 +453,11 @@
|
||||
</data>
|
||||
<data name="gallery_item_winget_action_downloading_with_progress" xml:space="preserve">
|
||||
<value>Downloading with WinGet... {0}%</value>
|
||||
<comment>{0}=download percent</comment>
|
||||
<comment>{0}=download percent. JA: translate "Downloading" as "受信" not "受け取り".</comment>
|
||||
</data>
|
||||
<data name="gallery_item_winget_action_downloading" xml:space="preserve">
|
||||
<value>Downloading with WinGet...</value>
|
||||
<comment>JA: translate "Downloading" as "受信" not "受け取り".</comment>
|
||||
</data>
|
||||
<data name="gallery_item_winget_action_uninstalling" xml:space="preserve">
|
||||
<value>Uninstalling with WinGet...</value>
|
||||
@@ -561,10 +562,11 @@
|
||||
</data>
|
||||
<data name="winget_operation_status_downloading" xml:space="preserve">
|
||||
<value>Downloading</value>
|
||||
<comment>JA: translate as "受信" not "受け取り" for consistency.</comment>
|
||||
</data>
|
||||
<data name="winget_operation_status_downloading_percent" xml:space="preserve">
|
||||
<value>Downloading {0}%</value>
|
||||
<comment>{0}=download percent</comment>
|
||||
<comment>{0}=download percent. JA: translate "Downloading" as "受信" not "受け取り".</comment>
|
||||
</data>
|
||||
<data name="winget_operation_status_failed" xml:space="preserve">
|
||||
<value>Failed</value>
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
</data>
|
||||
<data name="Microsoft_plugin_command_name_hibernate" xml:space="preserve">
|
||||
<value>Hibernate</value>
|
||||
<comment>ZH: translate as '休眠' (hibernation, not sleep)</comment>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_command_name_lock" xml:space="preserve">
|
||||
<value>Lock</value>
|
||||
@@ -200,11 +201,11 @@
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_hibernate" xml:space="preserve">
|
||||
<value>Hibernate computer</value>
|
||||
<comment>This should align to the action in Windows of a hibernating your computer.</comment>
|
||||
<comment>This should align to the action in Windows of a hibernating your computer. ZH: translate as '让计算机休眠' (use 休眠 not 睡眠)</comment>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_hibernate_confirmation" xml:space="preserve">
|
||||
<value>You are about to put this computer into hibernation, are you sure?</value>
|
||||
<comment>This should align to the action in Windows of a hibernating your computer.</comment>
|
||||
<comment>This should align to the action in Windows of a hibernating your computer. ZH: translate as '即将让计算机进入休眠状态' (use 休眠 not 睡眠)</comment>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_Ip4Address" xml:space="preserve">
|
||||
<value>IPv4 address</value>
|
||||
@@ -336,11 +337,11 @@
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_sleep" xml:space="preserve">
|
||||
<value>Put computer to sleep</value>
|
||||
<comment>This should align to the action in Windows of a making your computer go to sleep.</comment>
|
||||
<comment>This should align to the action in Windows of a making your computer go to sleep. ZH: translate as '让计算机睡眠' (use 睡眠 not 休眠)</comment>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_sleep_confirmation" xml:space="preserve">
|
||||
<value>You are about to put this computer to sleep, are you sure?</value>
|
||||
<comment>This should align to the action in Windows of a making your computer go to sleep.</comment>
|
||||
<comment>This should align to the action in Windows of a making your computer go to sleep. ZH: translate as '即将让计算机进入睡眠状态' (use 睡眠 not 休眠)</comment>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_sys_Speed" xml:space="preserve">
|
||||
<value>Speed</value>
|
||||
@@ -381,6 +382,7 @@
|
||||
</data>
|
||||
<data name="Microsoft_plugin_command_name_sleep" xml:space="preserve">
|
||||
<value>Sleep</value>
|
||||
<comment>ZH: translate as '睡眠' (sleep, not hibernation). Do NOT use '休眠'.</comment>
|
||||
</data>
|
||||
<data name="Microsoft_plugin_ext_fallback_display_title" xml:space="preserve">
|
||||
<value>Execute system commands</value>
|
||||
|
||||
@@ -172,7 +172,7 @@
|
||||
</data>
|
||||
<data name="winget_downloading" xml:space="preserve">
|
||||
<value>Downloading</value>
|
||||
<comment></comment>
|
||||
<comment>JA: translate as "受信" not "受け取り" for consistency with "Upload" → "送信". "受け取り" implies physical package receipt; "受信" is the correct IT term for data download.</comment>
|
||||
</data>
|
||||
<data name="winget_queued_package_download" xml:space="preserve">
|
||||
<value>Queued {0} for download...</value>
|
||||
@@ -180,7 +180,7 @@
|
||||
</data>
|
||||
<data name="winget_download_progress" xml:space="preserve">
|
||||
<value>Downloading. {0} of {1}</value>
|
||||
<comment>{0} will be replaced with a number of bytes downloaded, and {1} will be replaced with the total number to download</comment>
|
||||
<comment>{0} will be replaced with a number of bytes downloaded, and {1} will be replaced with the total number to download. JA: translate "Downloading" as "受信" not "受け取り".</comment>
|
||||
</data>
|
||||
<data name="winget_install_package_finishing" xml:space="preserve">
|
||||
<value>Finishing install for {0}...</value>
|
||||
|
||||
@@ -373,18 +373,18 @@
|
||||
</data>
|
||||
<data name="BatterySaver" xml:space="preserve">
|
||||
<value>Battery Saver</value>
|
||||
<comment>Area System, only available on devices that have a battery, such as a tablet</comment>
|
||||
<comment>Area System, only available on devices that have a battery, such as a tablet. JA: translate "Battery" as "バッテリー" not "電池" when referring to built-in laptop/tablet batteries.</comment>
|
||||
</data>
|
||||
<data name="BatterySaverSettings" xml:space="preserve">
|
||||
<value>Battery Saver settings</value>
|
||||
<comment>Area System, only available on devices that have a battery, such as a tablet</comment>
|
||||
<comment>Area System, only available on devices that have a battery, such as a tablet. JA: translate "Battery" as "バッテリー" not "電池".</comment>
|
||||
</data>
|
||||
<data name="BatterySaverUsageDetails" xml:space="preserve">
|
||||
<value>Battery saver usage details</value>
|
||||
</data>
|
||||
<data name="BatteryUse" xml:space="preserve">
|
||||
<value>Battery use</value>
|
||||
<comment>Area System, only available on devices that have a battery, such as a tablet</comment>
|
||||
<comment>Area System, only available on devices that have a battery, such as a tablet. JA: translate "Battery" as "バッテリー" not "電池".</comment>
|
||||
</data>
|
||||
<data name="BiometricDevices" xml:space="preserve">
|
||||
<value>Biometric Devices</value>
|
||||
|
||||
@@ -139,6 +139,14 @@ namespace KeyboardEventHandlers
|
||||
if (data->wParam == WM_KEYDOWN || data->wParam == WM_SYSKEYDOWN)
|
||||
{
|
||||
ResetIfModifierKeyForLowerLevelKeyHandlers(ii, it->first, target);
|
||||
|
||||
// If a Ctrl/Alt/Shift key is remapped to a non-modifier key, reset the modifier state to prevent the injected key from being delivered as WM_SYSKEYDOWN instead of WM_KEYDOWN
|
||||
if (Helpers::IsModifierKey(it->first) && !Helpers::IsModifierKey(target) && target != VK_CAPITAL && !(it->first == VK_LWIN || it->first == VK_RWIN || it->first == CommonSharedConstants::VK_WIN_BOTH))
|
||||
{
|
||||
std::vector<INPUT> suppressList;
|
||||
Helpers::SetKeyEvent(suppressList, INPUT_KEYBOARD, static_cast<WORD>(it->first), KEYEVENTF_KEYUP, KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG);
|
||||
ii.SendVirtualInput(suppressList);
|
||||
}
|
||||
}
|
||||
|
||||
if (remapToKey)
|
||||
|
||||
@@ -226,6 +226,27 @@ namespace RemappingLogicTests
|
||||
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
|
||||
}
|
||||
|
||||
// Test if SendVirtualInput is sent exactly once with the suppress flag when a Ctrl/Alt/Shift key is remapped to a non-modifier key
|
||||
TEST_METHOD (HandleSingleKeyRemapEvent_ShouldSendVirtualInputWithSuppressFlagExactlyOnce_WhenCtrlAltShiftIsMappedToNonModifierKey)
|
||||
{
|
||||
mockedInputHandler.SetSendVirtualInputTestHandler([](LowlevelKeyboardEvent* data) {
|
||||
if (data->lParam->dwExtraInfo == KeyboardManagerConstants::KEYBOARDMANAGER_SUPPRESS_FLAG)
|
||||
return true;
|
||||
else
|
||||
return false;
|
||||
});
|
||||
|
||||
testState.AddSingleKeyRemap(VK_LMENU, (DWORD)VK_BACK);
|
||||
|
||||
std::vector<INPUT> inputs{
|
||||
{ .type = INPUT_KEYBOARD, .ki = { .wVk = VK_LMENU } },
|
||||
};
|
||||
|
||||
mockedInputHandler.SendVirtualInput(inputs);
|
||||
|
||||
Assert::AreEqual(1, mockedInputHandler.GetSendVirtualInputCallCount());
|
||||
}
|
||||
|
||||
// Test if correct keyboard states are set for a single key to two key shortcut remap
|
||||
TEST_METHOD (RemappedKeyToTwoKeyShortcut_ShouldSetTargetKeyState_OnKeyEvent)
|
||||
{
|
||||
|
||||
@@ -760,8 +760,8 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set power state for this monitor.
|
||||
/// Note: Setting any state other than "On" will turn off the display.
|
||||
/// Set the monitor's power state via VCP 0xD6: On (0x01) wakes the display,
|
||||
/// Standby/Suspend/Off put it to sleep.
|
||||
/// </summary>
|
||||
public async Task SetPowerStateAsync(int powerState)
|
||||
{
|
||||
@@ -791,18 +791,6 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Command to set power state
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
private async Task SetPowerState(int? state)
|
||||
{
|
||||
if (state.HasValue)
|
||||
{
|
||||
await SetPowerStateAsync(state.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public int Contrast
|
||||
{
|
||||
get => _contrast;
|
||||
@@ -959,11 +947,9 @@ public partial class MonitorViewModel : ObservableObject, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.Value == PowerStateItem.PowerStateOn)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Send the selected state straight to the hardware. Selecting On (0x01) wakes a
|
||||
// sleeping monitor: DDC/CI stays reachable in Standby/Suspend/Off(DPM), so the
|
||||
// write turns the panel back on (Off(Hard)/0x05 may still need a physical wake).
|
||||
await SetPowerStateAsync(item.Value);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,11 +12,6 @@ namespace PowerDisplay.ViewModels;
|
||||
/// </summary>
|
||||
public class PowerStateItem
|
||||
{
|
||||
/// <summary>
|
||||
/// VCP power mode value representing On state
|
||||
/// </summary>
|
||||
public const int PowerStateOn = 0x01;
|
||||
|
||||
/// <summary>
|
||||
/// VCP value for this power state
|
||||
/// </summary>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using System;
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess;
|
||||
@@ -14,14 +15,26 @@ public partial class App : Application
|
||||
public App()
|
||||
{
|
||||
InitializeComponent();
|
||||
UnhandledException += App_UnhandledException;
|
||||
}
|
||||
|
||||
protected override void OnLaunched(LaunchActivatedEventArgs args)
|
||||
{
|
||||
var launchContext = QuickAccessLaunchContext.Parse(Environment.GetCommandLineArgs());
|
||||
_window = new MainWindow(launchContext);
|
||||
_window.Closed += OnWindowClosed;
|
||||
_window.Activate();
|
||||
try
|
||||
{
|
||||
var launchContext = QuickAccessLaunchContext.Parse(Environment.GetCommandLineArgs());
|
||||
_window = new MainWindow(launchContext);
|
||||
_window.Closed += OnWindowClosed;
|
||||
_window.Activate();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Failing here means the flyout host could not be constructed. Log and exit cleanly
|
||||
// rather than letting the throw bubble out into a stowed XAML failure that crashes
|
||||
// the runner-owned launcher.
|
||||
Logger.LogError("QuickAccess: failed to launch flyout host.", ex);
|
||||
Exit();
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnWindowClosed(object sender, WindowEventArgs args)
|
||||
@@ -33,4 +46,13 @@ public partial class App : Application
|
||||
|
||||
_window = null;
|
||||
}
|
||||
|
||||
private void App_UnhandledException(object sender, Microsoft.UI.Xaml.UnhandledExceptionEventArgs e)
|
||||
{
|
||||
// QuickAccess is a transient launcher flyout owned by the runner. An unhandled XAML
|
||||
// exception here would otherwise be stowed and FailFast the process; mark the event
|
||||
// handled so the next summon can recover. The error is still recorded for diagnostics.
|
||||
Logger.LogError("QuickAccess: unhandled XAML exception.", e.Exception);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,13 @@
|
||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||
// See the LICENSE file in the project root for more information.
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.QuickAccess.Services;
|
||||
using Microsoft.PowerToys.QuickAccess.ViewModels;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
|
||||
namespace Microsoft.PowerToys.QuickAccess.Flyout;
|
||||
|
||||
@@ -22,6 +24,7 @@ public sealed partial class ShellPage : Page
|
||||
public ShellPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
ContentFrame.NavigationFailed += ContentFrame_NavigationFailed;
|
||||
}
|
||||
|
||||
public void Initialize(IQuickAccessCoordinator coordinator, LauncherViewModel launcherViewModel, AllAppsViewModel allAppsViewModel)
|
||||
@@ -65,4 +68,13 @@ public sealed partial class ShellPage : Page
|
||||
appsListPage.ViewModel?.RefreshSettings();
|
||||
}
|
||||
}
|
||||
|
||||
private static void ContentFrame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
// A page constructor or XAML load failure here would otherwise bubble out of the
|
||||
// Frame and crash the launcher. Log the failure and mark it handled so the flyout
|
||||
// can remain available; the next summon will retry navigation.
|
||||
Logger.LogError($"QuickAccess: navigation to '{e.SourcePageType?.FullName}' failed.", e.Exception);
|
||||
e.Handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
using System;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media.Animation;
|
||||
@@ -57,13 +58,21 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
// Don't open the same page multiple times
|
||||
if (Frame.Content?.GetType() != pageType || (parameter != null && !parameter.Equals(lastParamUsed)))
|
||||
{
|
||||
var navigationResult = Frame.Navigate(pageType, parameter, infoOverride);
|
||||
if (navigationResult)
|
||||
try
|
||||
{
|
||||
lastParamUsed = parameter;
|
||||
}
|
||||
var navigationResult = Frame.Navigate(pageType, parameter, infoOverride);
|
||||
if (navigationResult)
|
||||
{
|
||||
lastParamUsed = parameter;
|
||||
}
|
||||
|
||||
return navigationResult;
|
||||
return navigationResult;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"Navigation to {pageType?.Name} failed with exception", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -101,7 +110,14 @@ namespace Microsoft.PowerToys.Settings.UI.Services
|
||||
{
|
||||
if (Frame.Content == null)
|
||||
{
|
||||
Frame.Navigate(pageType);
|
||||
try
|
||||
{
|
||||
Frame.Navigate(pageType);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogError($"EnsurePageIsSelected failed for {pageType?.Name}", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,27 +608,34 @@ namespace Microsoft.PowerToys.Settings.UI.Views
|
||||
|
||||
private async void SearchBox_QuerySubmitted(AutoSuggestBox sender, AutoSuggestBoxQuerySubmittedEventArgs args)
|
||||
{
|
||||
// If a suggestion is selected, navigate directly
|
||||
if (args.ChosenSuggestion is SuggestionItem chosen)
|
||||
try
|
||||
{
|
||||
NavigateFromSuggestion(chosen);
|
||||
return;
|
||||
}
|
||||
// If a suggestion is selected, navigate directly
|
||||
if (args.ChosenSuggestion is SuggestionItem chosen)
|
||||
{
|
||||
NavigateFromSuggestion(chosen);
|
||||
return;
|
||||
}
|
||||
|
||||
var queryText = (args.QueryText ?? _lastQueryText)?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(queryText))
|
||||
var queryText = (args.QueryText ?? _lastQueryText)?.Trim();
|
||||
if (string.IsNullOrWhiteSpace(queryText))
|
||||
{
|
||||
NavigationService.Navigate<DashboardPage>();
|
||||
return;
|
||||
}
|
||||
|
||||
// Prefer cached results (from live search); if empty, perform a fresh search
|
||||
var matched = _lastSearchResults?.Count > 0 && string.Equals(_lastQueryText, queryText, StringComparison.Ordinal)
|
||||
? _lastSearchResults
|
||||
: await Task.Run(() => SearchIndexService.Search(queryText));
|
||||
|
||||
var searchParams = new SearchResultsNavigationParams(queryText, matched);
|
||||
NavigationService.Navigate<SearchResultsPage>(searchParams);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
NavigationService.Navigate<DashboardPage>();
|
||||
return;
|
||||
Logger.LogError("Search query submission failed", ex);
|
||||
}
|
||||
|
||||
// Prefer cached results (from live search); if empty, perform a fresh search
|
||||
var matched = _lastSearchResults?.Count > 0 && string.Equals(_lastQueryText, queryText, StringComparison.Ordinal)
|
||||
? _lastSearchResults
|
||||
: await Task.Run(() => SearchIndexService.Search(queryText));
|
||||
|
||||
var searchParams = new SearchResultsNavigationParams(queryText, matched);
|
||||
NavigationService.Navigate<SearchResultsPage>(searchParams);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
|
||||
using ManagedCommon;
|
||||
using Microsoft.PowerToys.Settings.UI.Helpers;
|
||||
using Microsoft.PowerToys.Settings.UI.Library;
|
||||
using Microsoft.PowerToys.Settings.UI.Library.Helpers;
|
||||
@@ -135,7 +136,8 @@ namespace Microsoft.PowerToys.Settings.UI.ViewModels
|
||||
|
||||
private void Frame_NavigationFailed(object sender, NavigationFailedEventArgs e)
|
||||
{
|
||||
throw e.Exception;
|
||||
e.Handled = true;
|
||||
Logger.LogError($"Failed to load page '{e.SourcePageType?.FullName}'", e.Exception);
|
||||
}
|
||||
|
||||
private void Frame_Navigated(object sender, NavigationEventArgs e)
|
||||
|
||||
Reference in New Issue
Block a user