diff --git a/.nuget/README.md b/.nuget/README.md
index 6c4806a..3fb2961 100644
--- a/.nuget/README.md
+++ b/.nuget/README.md
@@ -1,5 +1,12 @@
DesktopFlyouts is a WinUI library for showing desktop flyouts from tray icons or programmatically.
+## UWP desktop host
+
+The UWP package uses XamlHostingKit for System XAML initialization and message dispatch. Start the
+application with `XamlIslandApplication.Start`, and create every additional flyout on its own XAML
+thread with `XamlIslandApplication.CreateWindow`. See the getting-started guide below for the full
+entry-point example.
+
## Usage
### DesktopFlyout
diff --git a/AGENTS.md b/AGENTS.md
index 32983cb..b39a500 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -19,12 +19,8 @@ DesktopFlyouts is a WinUI library for showing lightweight desktop flyouts, menu
└───samples
├───DesktopFlyouts.Wasdk.Sample.App
│ └───DesktopFlyouts.Wasdk.Sample.App.csproj
- ├───DesktopFlyouts.Uwp.Sample.App
+ └───DesktopFlyouts.Uwp.Sample.App
│ └───DesktopFlyouts.Uwp.Sample.App.csproj
- ├───DesktopFlyouts.Uwp.Sample.Packaging
- │ └───DesktopFlyouts.Uwp.Sample.Packaging.wapproj
- └───DesktopFlyouts.Uwp.Sample.TrayHost
- └───DesktopFlyouts.Uwp.Sample.TrayHost.csproj
```
Most runtime behavior lives in `src/DesktopFlyouts.Shared` behind `#if WASDK`, `#if UWP`, and `#if HAS_UNO`. Keep all branches building when changing shared files.
@@ -55,10 +51,10 @@ dotnet msbuild /restore:false src/DesktopFlyouts.Uno/DesktopFlyouts.Uno.csproj /
dotnet msbuild /restore:false samples\DesktopFlyouts.Wasdk.Sample.App\DesktopFlyouts.Wasdk.Sample.App.csproj /p:Configuration=Debug /p:Platform=x64 /p:AppxBundle=Never
```
-3. Build the UWP library (Windows only):
+3. Build the UWP sample and library (Windows only):
```powershell
-dotnet msbuild /restore:false src\DesktopFlyouts.Uwp\DesktopFlyouts.Uwp.csproj /p:Configuration=Debug /p:Platform=x64 /p:AppxBundle=Never
+dotnet msbuild /restore:false samples\DesktopFlyouts.Uwp.Sample.App\DesktopFlyouts.Uwp.Sample.App.csproj /p:Configuration=Debug /p:Platform=x64 /p:AppxBundle=Never
```
4. Check whitespace and line endings:
@@ -118,7 +114,7 @@ Validate one item at a time:
1. `git status --short --branch` to understand the current branch and dirty files.
2. Build the Uno library.
3. Build the WASDK sample (Windows only).
-4. Build the UWP library when shared code changed (Windows only).
+4. Build the UWP sample and library (Windows only).
5. Launch the WASDK sample as packaged, never by direct `.exe` (Windows only).
6. Confirm the app has a responsive process and real window handle (Windows only).
7. Run `git diff --check`.
diff --git a/DesktopFlyouts.slnx b/DesktopFlyouts.slnx
index d04044e..51d2006 100644
--- a/DesktopFlyouts.slnx
+++ b/DesktopFlyouts.slnx
@@ -24,15 +24,6 @@
-
-
-
-
-
-
-
-
-
diff --git a/Directory.Packages.props b/Directory.Packages.props
index 4824bf8..1bdccb9 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -18,8 +18,9 @@
-
+
+
diff --git a/README.md b/README.md
index c2b7e1b..211a70b 100644
--- a/README.md
+++ b/README.md
@@ -17,6 +17,10 @@ The UWP version of sample app is currently under development. Recommend to use W
> dotnet add package DesktopFlyouts.Uwp
```
+UWP desktop hosts initialize System XAML and its message loop through XamlHostingKit. See the
+[UWP startup instructions](docs/getting-started.md#start-a-uwp-desktop-host); each additional
+flyout is created on its own XAML thread with `XamlIslandApplication.CreateWindow`.
+
### WinUI (WinAppSDK/WinUI3)
diff --git a/docs/focus-and-activation.md b/docs/focus-and-activation.md
index ab41e0e..563800d 100644
--- a/docs/focus-and-activation.md
+++ b/docs/focus-and-activation.md
@@ -48,4 +48,7 @@ Set it to `False` for sticky flyouts.
## UWP message loop integration
-UWP desktop-host scenarios expose `TryPreTranslateMessage` on `DesktopFlyout` and `DesktopMenuFlyout`. Call it from the native message loop so keyboard navigation and accelerators can reach the hosted XAML island.
+Start UWP desktop hosts with `XamlIslandApplication.Start`. XamlHostingKit owns the CoreWindow
+message loop and performs XAML message translation, so application code must not run a second
+native message loop. `TryPreTranslateMessage` remains available for source compatibility and
+returns `False` with the XamlHostingKit host.
diff --git a/docs/getting-started.md b/docs/getting-started.md
index b0a8600..6e09e74 100644
--- a/docs/getting-started.md
+++ b/docs/getting-started.md
@@ -10,6 +10,46 @@ dotnet add package 0x5BFA.DesktopFlyouts.WinUI --prerelease
dotnet add package 0x5BFA.DesktopFlyouts.Uwp --prerelease
```
+### Start a UWP desktop host
+
+The UWP package uses XamlHostingKit to own System XAML initialization and the desktop message loop.
+Disable the generated XAML entry point and start the application through `XamlIslandApplication`:
+
+```xml
+$(DefineConstants);DISABLE_XAML_GENERATED_MAIN
+```
+
+```csharp
+using DesktopFlyouts.Shared;
+
+[STAThread]
+static void Main()
+{
+ XamlIslandApplication.Start(_ => new App());
+}
+```
+
+Create the first `DesktopFlyout` or `DesktopMenuFlyout` on the main XAML thread. System XAML
+allows one top-level window per thread, so create every additional flyout in its own callback:
+
+```csharp
+private DesktopFlyout? _flyout;
+private DesktopMenuFlyout? _menu;
+
+protected override void OnLaunched(LaunchActivatedEventArgs args)
+{
+ _flyout = new MainDesktopFlyout();
+
+ XamlIslandApplication.CreateWindow(_ =>
+ {
+ _menu = new MainDesktopMenuFlyout();
+ });
+}
+```
+
+XamlHostingKit dispatches each callback on the matching XAML thread. Dispatch later menu or
+flyout operations back to the instance's `Dispatcher` when the caller runs on another thread.
+
Add the DesktopFlyouts namespace to XAML.
```xml
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml
new file mode 100644
index 0000000..9163926
--- /dev/null
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml
@@ -0,0 +1,6 @@
+
+
+
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml.cs
new file mode 100644
index 0000000..2a9776f
--- /dev/null
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/App.xaml.cs
@@ -0,0 +1,87 @@
+// Copyright (c) 0x5BFA. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.IO;
+using DesktopFlyouts.Shared;
+using Windows.ApplicationModel.Activation;
+using Windows.UI.Core;
+using Windows.UI.Xaml;
+
+namespace DesktopFlyouts
+{
+ public partial class App : Application
+ {
+ private static SystemTrayIcon? _systemTrayIcon;
+ private static DesktopFlyout? _desktopFlyout;
+ private static volatile DesktopMenuFlyout? _desktopMenuFlyout;
+
+ public App()
+ {
+ InitializeComponent();
+ }
+
+ protected override void OnLaunched(LaunchActivatedEventArgs args)
+ {
+ _systemTrayIcon = new(
+ Path.Combine(AppContext.BaseDirectory, "Tray.ico"),
+ "DesktopFlyouts sample app (UWP)",
+ new("022F5158-F05A-4FE1-B356-34F14B363625"));
+
+ _systemTrayIcon.LeftClicked += SystemTrayIcon_LeftClicked;
+ _systemTrayIcon.RightClicked += SystemTrayIcon_RightClicked;
+ _systemTrayIcon.Show();
+
+ // The first flyout uses XamlHostingKit's main XamlWindow.
+ _desktopFlyout = new CustomizableFlyout
+ {
+ // XamlHostingKit deactivates its CoreWindow during the first host activation.
+ HideOnLostFocus = false,
+ };
+
+ // System XAML permits one top-level window per thread. The menu therefore uses
+ // another XamlHostingKit window and is constructed on that window's XAML thread.
+ XamlIslandApplication.CreateWindow(_ =>
+ {
+ _desktopMenuFlyout = new MainDesktopMenuFlyout();
+ });
+ }
+
+ internal static void ExitApplication()
+ {
+ var dispatcher = _desktopFlyout?.Dispatcher;
+ if (dispatcher is not null)
+ _ = dispatcher.RunAsync(CoreDispatcherPriority.Normal, Current.Exit);
+ }
+
+ private static void SystemTrayIcon_LeftClicked(object? sender, MouseEventReceivedEventArgs e)
+ {
+ var flyout = _desktopFlyout;
+ if (flyout is null)
+ return;
+
+ _ = flyout.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ if (flyout.IsOpen)
+ flyout.Hide();
+ else
+ flyout.Show();
+ });
+ }
+
+ private static void SystemTrayIcon_RightClicked(object? sender, MouseEventReceivedEventArgs e)
+ {
+ var menuFlyout = _desktopMenuFlyout;
+ if (menuFlyout is null)
+ return;
+
+ _ = menuFlyout.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
+ {
+ if (menuFlyout.IsOpen)
+ menuFlyout.Hide();
+
+ menuFlyout.Show(new(e.Point.X, e.Point.Y - 32));
+ });
+ }
+ }
+}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml
deleted file mode 100644
index bc8ecf5..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml
+++ /dev/null
@@ -1,5 +0,0 @@
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs
deleted file mode 100644
index 5cc0681..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/App2.xaml.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 0x5BFA. All rights reserved.
-// Licensed under the MIT license.
-
-using System;
-using Windows.ApplicationModel;
-using Windows.ApplicationModel.Activation;
-using Windows.ApplicationModel.Core;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Controls;
-using Windows.UI.Xaml.Navigation;
-using WinRT;
-
-namespace DesktopFlyouts
-{
- public sealed partial class App2 : Application
- {
- public App2()
- {
- InitializeComponent();
-
- Suspending += OnSuspending;
- }
-
- protected override void OnActivated(IActivatedEventArgs args)
- {
- // Do not repeat app initialization when the Window already has content,
- // just ensure that the window is active.
- if (Window.Current.Content is not Frame rootFrame)
- {
- // Create a Frame to act as the navigation context and navigate to the first page
- rootFrame = new Frame();
- rootFrame.NavigationFailed += OnNavigationFailed;
-
- if (args.PreviousExecutionState == ApplicationExecutionState.Terminated)
- {
- // TODO: Load state from previously suspended application
- }
-
- // Place the frame in the current Window
- Window.Current.Content = rootFrame;
- }
-
- if (rootFrame.Content == null)
- {
- CoreApplication.GetCurrentView().TitleBar.ExtendViewIntoTitleBar = true;
-
- rootFrame.Navigate(typeof(MainPage), args.As().Data);
- }
-
- // Ensure the current window is active
- Window.Current.Activate();
- }
-
- private void OnNavigationFailed(object sender, NavigationFailedEventArgs e)
- {
- throw new Exception($"Failed to load page '{e.SourcePageType.FullName}'.");
- }
-
- private void OnSuspending(object sender, SuspendingEventArgs e)
- {
- SuspendingDeferral deferral = e.SuspendingOperation.GetDeferral();
-
- // TODO: Save application state and stop any background activity
- deferral.Complete();
- }
- }
-}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LargeTile.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LargeTile.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LockScreenLogo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/LockScreenLogo.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/LockScreenLogo.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/LockScreenLogo.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SmallTile.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SmallTile.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/SplashScreen.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/SplashScreen.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square150x150Logo.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square150x150Logo.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-16.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-24.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-256.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-32.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-lightunplated_targetsize-48.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-16.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-16.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-256.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-256.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-32.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-32.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-48.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.altform-unplated_targetsize-48.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-16.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-16.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-16.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-16.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-256.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-256.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-256.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-256.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-32.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-32.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-32.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-32.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-48.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-48.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Square44x44Logo.targetsize-48.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Square44x44Logo.targetsize-48.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/StoreLogo.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/StoreLogo.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-100.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-100.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-100.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-100.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-125.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-125.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-125.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-125.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-150.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-150.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-150.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-150.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-200.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-200.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-200.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-200.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-400.png b/samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-400.png
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Assets/Wide310x150Logo.scale-400.png
rename to samples/DesktopFlyouts.Uwp.Sample.App/Assets/Wide310x150Logo.scale-400.png
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj b/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj
index eae07fe..504161a 100644
--- a/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/DesktopFlyouts.Uwp.Sample.App.csproj
@@ -1,30 +1,39 @@
-
-
-
-
- WinExe
- net10.0-windows$(TargetWindowsVersion)
- 10.0.17763.0
- win-$(Platform).pubxml
- app.manifest
- true
-
- true
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ WinExe
+ net10.0-windows$(TargetWindowsVersion)
+ 10.0.17763.0
+ win-$(Platform).pubxml
+ app.manifest
+ true
+
+ $(DefineConstants);DISABLE_XAML_GENERATED_MAIN
+
+ true
+ true
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml
deleted file mode 100644
index 1eb3d9e..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml
+++ /dev/null
@@ -1,27 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml.cs
deleted file mode 100644
index 2e3b693..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/MainPage.xaml.cs
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 0x5BFA. All rights reserved.
-// Licensed under the MIT license.
-
-using Windows.ApplicationModel.Core;
-using Windows.UI.Xaml.Controls;
-
-namespace DesktopFlyouts
-{
- public sealed partial class MainPage : Page
- {
- public MainPage()
- {
- InitializeComponent();
- }
-
- private void CloseApp(object sender, Windows.UI.Xaml.RoutedEventArgs e)
- {
- CoreApplication.Exit();
- }
- }
-}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Package.appxmanifest b/samples/DesktopFlyouts.Uwp.Sample.App/Package.appxmanifest
similarity index 58%
rename from samples/DesktopFlyouts.Uwp.Sample.Packaging/Package.appxmanifest
rename to samples/DesktopFlyouts.Uwp.Sample.App/Package.appxmanifest
index 1ef5939..4d41e1b 100644
--- a/samples/DesktopFlyouts.Uwp.Sample.Packaging/Package.appxmanifest
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Package.appxmanifest
@@ -1,14 +1,11 @@
-
+ IgnorableNamespaces="uap rescap">
-
-
DesktopFlyouts sample app (UWP)
0x5BFA
@@ -16,7 +13,7 @@
-
+
@@ -24,20 +21,11 @@
-
+
-
-
-
-
- TIF
-
-
-
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs b/samples/DesktopFlyouts.Uwp.Sample.App/Program.cs
similarity index 71%
rename from samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs
rename to samples/DesktopFlyouts.Uwp.Sample.App/Program.cs
index 1108627..887ff05 100644
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Program.cs
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Program.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System;
+using DesktopFlyouts.Shared;
namespace DesktopFlyouts
{
@@ -10,7 +11,7 @@ public class Program
[STAThread]
static void Main()
{
- _ = new App();
+ XamlIslandApplication.Start(_ => new App());
}
}
}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/Properties/launchSettings.json b/samples/DesktopFlyouts.Uwp.Sample.App/Properties/launchSettings.json
index c6a92ef..ef0b4bc 100644
--- a/samples/DesktopFlyouts.Uwp.Sample.App/Properties/launchSettings.json
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Properties/launchSettings.json
@@ -1,7 +1,7 @@
{
"profiles": {
- "DesktopFlyouts.Uwp.Sample.App": {
+ "Run": {
"commandName": "MsixPackage"
}
}
-}
\ No newline at end of file
+}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml
deleted file mode 100644
index 6486e06..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml.cs
deleted file mode 100644
index 513b420..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/RootView.xaml.cs
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 0x5BFA. All rights reserved.
-// Licensed under the MIT license.
-
-using Windows.UI.Xaml.Controls;
-
-namespace DesktopFlyouts
-{
- public sealed partial class RootView : UserControl
- {
- public RootView()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/RootViewModel.cs b/samples/DesktopFlyouts.Uwp.Sample.App/RootViewModel.cs
deleted file mode 100644
index 22e22e7..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.App/RootViewModel.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-// Copyright (c) 0x5BFA. All rights reserved.
-// Licensed under the MIT license.
-
-using CommunityToolkit.Mvvm.ComponentModel;
-
-namespace DesktopFlyouts
-{
- internal partial class RootViewModel : ObservableObject
- {
- internal RootViewModel()
- {
- }
- }
-}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Tray.ico b/samples/DesktopFlyouts.Uwp.Sample.App/Tray.ico
similarity index 100%
rename from samples/DesktopFlyouts.Uwp.Sample.TrayHost/Tray.ico
rename to samples/DesktopFlyouts.Uwp.Sample.App/Tray.ico
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/Views/CustomizableFlyout.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/Views/CustomizableFlyout.xaml
new file mode 100644
index 0000000..d0d7832
--- /dev/null
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Views/CustomizableFlyout.xaml
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+ #09FFFFFF
+ #15FFFFFF
+ #18000000
+
+
+ #40FFFFFF
+ #0F000000
+ #10000000
+
+
+ #09FFFFFF
+ #15FFFFFF
+ #18000000
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/Views/CustomizableFlyout.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/Views/CustomizableFlyout.xaml.cs
new file mode 100644
index 0000000..9249b60
--- /dev/null
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Views/CustomizableFlyout.xaml.cs
@@ -0,0 +1,30 @@
+// Copyright (c) 0x5BFA. All rights reserved.
+// Licensed under the MIT license.
+
+using Windows.UI.Xaml;
+
+namespace DesktopFlyouts
+{
+ public sealed partial class CustomizableFlyout : DesktopFlyout
+ {
+ public CustomizableFlyout()
+ {
+ InitializeComponent();
+ }
+
+ private void CollapseFirstIslandButton_Click(object sender, RoutedEventArgs e)
+ {
+ FirstIsland.Visibility = Visibility.Collapsed;
+ }
+
+ private void CollapseSecondIslandButton_Click(object sender, RoutedEventArgs e)
+ {
+ SecondIsland.Visibility = Visibility.Collapsed;
+ }
+
+ private void CollapseThirdIslandButton_Click(object sender, RoutedEventArgs e)
+ {
+ ThirdIsland.Visibility = Visibility.Collapsed;
+ }
+ }
+}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.App/Views/MainDesktopMenuFlyout.xaml b/samples/DesktopFlyouts.Uwp.Sample.App/Views/MainDesktopMenuFlyout.xaml
new file mode 100644
index 0000000..18127de
--- /dev/null
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Views/MainDesktopMenuFlyout.xaml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.App/Views/MainDesktopMenuFlyout.xaml.cs
similarity index 60%
rename from samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs
rename to samples/DesktopFlyouts.Uwp.Sample.App/Views/MainDesktopMenuFlyout.xaml.cs
index 988477a..eaac94e 100644
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml.cs
+++ b/samples/DesktopFlyouts.Uwp.Sample.App/Views/MainDesktopMenuFlyout.xaml.cs
@@ -1,9 +1,7 @@
// Copyright (c) 0x5BFA. All rights reserved.
// Licensed under the MIT license.
-using System;
-
-using Windows.System;
+using Windows.UI.Xaml;
namespace DesktopFlyouts
{
@@ -14,9 +12,9 @@ public MainDesktopMenuFlyout()
InitializeComponent();
}
- private async void OpenSecondaryApp(object sender, Windows.UI.Xaml.RoutedEventArgs e)
+ private void Exit_Click(object sender, RoutedEventArgs e)
{
- await Launcher.LaunchUriAsync(new Uri("tif-secondaryapp:"));
+ App.ExitApplication();
}
}
}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.Packaging/DesktopFlyouts.Uwp.Sample.Packaging.wapproj b/samples/DesktopFlyouts.Uwp.Sample.Packaging/DesktopFlyouts.Uwp.Sample.Packaging.wapproj
deleted file mode 100644
index 33552e4..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.Packaging/DesktopFlyouts.Uwp.Sample.Packaging.wapproj
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
- 15.0
-
-
-
- Debug
- x86
-
-
- Release
- x86
-
-
- Debug
- x64
-
-
- Release
- x64
-
-
- Debug
- ARM64
-
-
- Release
- ARM64
-
-
-
- $(MSBuildExtensionsPath)\Microsoft\DesktopBridge\
-
-
-
- 8f9f53a2-0cda-4bb2-b6e9-060ce608dfb2
- 10.0.26100.0
- 10.0.17763.0
- en-US
- false
- $(NoWarn);NU1702
- ..\DesktopFlyouts.Uwp.Sample.TrayHost\DesktopFlyouts.Uwp.Sample.TrayHost.csproj
-
-
-
- Designer
-
-
-
-
-
-
-
-
-
-
-
- True
- True
- Properties\PublishProfiles\win-$(Platform).pubxml
-
-
-
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml
deleted file mode 100644
index 960e994..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml
+++ /dev/null
@@ -1,36 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
- #09FFFFFF
- #15FFFFFF
- #18000000
-
-
- #40FFFFFF
- #0F000000
- #10000000
-
-
- #09FFFFFF
- #15FFFFFF
- #18000000
-
-
-
-
-
-
-
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs
deleted file mode 100644
index 7f74b1b..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/App.xaml.cs
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright (c) 0x5BFA. All rights reserved.
-// Licensed under the MIT license.
-
-using System;
-using System.Threading;
-using Windows.System;
-using Windows.UI.Xaml;
-using Windows.UI.Xaml.Hosting;
-using Windows.Win32;
-using Windows.Win32.Foundation;
-using Windows.Win32.System.WinRT;
-using Windows.Win32.UI.WindowsAndMessaging;
-
-namespace DesktopFlyouts
-{
- public partial class App : Application
- {
- private static SystemTrayIcon? _systemTrayIcon;
- private static DesktopFlyout? _desktopFlyout;
- private static DesktopMenuFlyout? _desktopMenuFlyout;
-
- public unsafe App()
- {
- PInvoke.RoInitialize(RO_INIT_TYPE.RO_INIT_SINGLETHREADED);
-
- _systemTrayIcon = new(
- "Tray.ico",
- "DesktopFlyouts sample app (UWP)",
- new("022F5158-F05A-4FE1-B356-34F14B363625"));
-
- _systemTrayIcon.LeftClicked += SystemTrayIcon_LeftClicked;
- _systemTrayIcon.RightClicked += SystemTrayIcon_RightClicked;
- _systemTrayIcon.Show();
-
- // Initialize XAML Island
- WindowsXamlManager.InitializeForCurrentThread();
- SynchronizationContext.SetSynchronizationContext(new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread()));
-
- // Initialize XAML flyouts
- _desktopFlyout = new MainDesktopFlyout();
- _desktopMenuFlyout = new MainDesktopMenuFlyout();
-
- MSG msg;
- while (PInvoke.GetMessage(&msg, HWND.Null, 0U, 0U))
- {
- if (!TryPreTranslateMessage(&msg))
- {
- PInvoke.TranslateMessage(&msg);
- PInvoke.DispatchMessage(&msg);
- }
- }
- }
-
- private static unsafe bool TryPreTranslateMessage(MSG* msg)
- {
- return (_desktopFlyout?.TryPreTranslateMessage(msg) ?? false) ||
- (_desktopMenuFlyout?.TryPreTranslateMessage(msg) ?? false);
- }
-
- private static void SystemTrayIcon_LeftClicked(object? sender, MouseEventReceivedEventArgs e)
- {
- if (_desktopFlyout is null)
- return;
-
- if (_desktopFlyout.IsOpen)
- _desktopFlyout.Hide();
- else
- _desktopFlyout.Show();
- }
-
- private static void SystemTrayIcon_RightClicked(object? sender, MouseEventReceivedEventArgs e)
- {
- if (_desktopMenuFlyout is null)
- return;
-
- if (_desktopMenuFlyout.IsOpen)
- _desktopMenuFlyout.Hide();
-
- _desktopMenuFlyout.Show(new(e.Point.X, e.Point.Y - 32));
- }
- }
-}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj
deleted file mode 100644
index ce9021a..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/DesktopFlyouts.Uwp.Sample.TrayHost.csproj
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
- WinExe
- net10.0-windows$(TargetWindowsVersion)
- 10.0.17763.0
- win-$(Platform).pubxml
- app.manifest
- true
-
- $(DefineConstants);DISABLE_XAML_GENERATED_MAIN
-
- true
- false
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml
deleted file mode 100644
index 4a04cab..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml
+++ /dev/null
@@ -1,110 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs
deleted file mode 100644
index 2e56d10..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopFlyout.xaml.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 0x5BFA. All rights reserved.
-// Licensed under the MIT license.
-
-namespace DesktopFlyouts
-{
- public sealed partial class MainDesktopFlyout : DesktopFlyout
- {
- public MainDesktopFlyout()
- {
- InitializeComponent();
- }
- }
-}
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml
deleted file mode 100644
index b15804f..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/MainDesktopMenuFlyout.xaml
+++ /dev/null
@@ -1,33 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-arm64.pubxml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-arm64.pubxml
deleted file mode 100644
index 3481de2..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-arm64.pubxml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- FileSystem
- ARM64
- win-arm64
- bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
- true
-
-
\ No newline at end of file
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x64.pubxml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x64.pubxml
deleted file mode 100644
index 4463ecc..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x64.pubxml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- FileSystem
- x64
- win-x64
- bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
- true
-
-
\ No newline at end of file
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x86.pubxml b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x86.pubxml
deleted file mode 100644
index 31c51d6..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/PublishProfiles/win-x86.pubxml
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
-
- FileSystem
- x86
- win-x86
- bin\$(Configuration)\$(TargetFramework)\$(RuntimeIdentifier)\publish\
- true
-
-
\ No newline at end of file
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/launchSettings.json b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/launchSettings.json
deleted file mode 100644
index 669b6ad..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/Properties/launchSettings.json
+++ /dev/null
@@ -1,7 +0,0 @@
-{
- "profiles": {
- "DesktopFlyouts.Uwp.Sample.TrayHost": {
- "commandName": "MsixPackage"
- }
- }
-}
\ No newline at end of file
diff --git a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/app.manifest b/samples/DesktopFlyouts.Uwp.Sample.TrayHost/app.manifest
deleted file mode 100644
index 2f30ef5..0000000
--- a/samples/DesktopFlyouts.Uwp.Sample.TrayHost/app.manifest
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- true
- PerMonitorV2
- true
-
-
-
-
\ No newline at end of file
diff --git a/src/DesktopFlyouts.Shared/DesktopFlyout.cs b/src/DesktopFlyouts.Shared/DesktopFlyout.cs
index 48a5a76..8844c6b 100644
--- a/src/DesktopFlyouts.Shared/DesktopFlyout.cs
+++ b/src/DesktopFlyouts.Shared/DesktopFlyout.cs
@@ -70,6 +70,9 @@ public partial class DesktopFlyout : Control, IDisposable
private bool _isFocusManagerGettingFocusSubscribed;
private bool _isUpdatingOpenFlyoutLayout;
private bool _disposed;
+#if UWP
+ private bool _showPending;
+#endif
private Grid? RootGrid;
private ItemsControl? IslandsItemsControl;
@@ -85,6 +88,9 @@ public DesktopFlyout()
DefaultStyleKey = typeof(DesktopFlyout);
_host = new XamlIslandHostWindow();
+#if UWP
+ _host.ContentAttached += Host_ContentAttached;
+#endif
_host.SetContent(this);
_ = _host.UpdateWindowVisibility(false);
#if HAS_UNO
@@ -147,7 +153,22 @@ protected override void OnApplyTemplate()
///
public void Show()
{
- if (_disposed || _host?.DesktopWindowXamlSource is null || RootGrid is null || _isPopupAnimationPlaying)
+ Show(false);
+ }
+
+ private void Show(bool preserveCustomPlacementWhilePending)
+ {
+#if UWP
+ if (!_disposed && _host is not null && !_host.IsContentReady)
+ {
+ _showPending = true;
+ if (!preserveCustomPlacementWhilePending)
+ _customPlacementBottomCenterPoint = null;
+
+ return;
+ }
+#endif
+ if (_disposed || _host?.IsInitialized is not true || RootGrid is null || _isPopupAnimationPlaying)
{
_customPlacementBottomCenterPoint = null;
return;
@@ -222,7 +243,7 @@ public void Show(Point bottomCenterPoint)
return;
_customPlacementBottomCenterPoint = bottomCenterPoint;
- Show();
+ Show(true);
}
///
@@ -234,6 +255,13 @@ public void Show(Point bottomCenterPoint)
///
public void Hide()
{
+#if UWP
+ if (_showPending)
+ {
+ _showPending = false;
+ _customPlacementBottomCenterPoint = null;
+ }
+#endif
Hide(false);
}
@@ -297,8 +325,8 @@ public void NavigateFocus(XamlSourceFocusNavigationReason reason = XamlSourceFoc
/// The native message to process.
/// if the message was handled; otherwise, .
///
- /// UWP desktop-host scenarios should call this from their native message loop so keyboard
- /// navigation and accelerator processing can reach the hosted XAML island.
+ /// XamlHostingKit owns the UWP desktop message loop and performs XAML message translation.
+ /// This method remains available for source compatibility and returns .
///
public unsafe bool TryPreTranslateMessage(MSG* msg)
{
@@ -346,7 +374,7 @@ private DesktopFlyoutPopupDirection UpdateFlyoutRegion(
FoundationRect? resizeAnchorRegion = null,
DesktopFlyoutPopupDirection resizePopupDirection = DesktopFlyoutPopupDirection.Vertical)
{
- if (_host?.DesktopWindowXamlSource is null || IslandsItemsControl is null)
+ if (_host?.IsInitialized is not true || IslandsItemsControl is null)
return ResolvePopupDirection(PopupDirection, default, WindowHelpers.GetFlyoutWorkAreaRect(_customPlacementBottomCenterPoint));
var customBottomCenterPoint = _customPlacementBottomCenterPoint;
@@ -419,7 +447,7 @@ private void UpdateOpenFlyoutLayout()
_isPopupAnimationPlaying ||
_isUpdatingOpenFlyoutLayout ||
RootGrid is null ||
- _host?.DesktopWindowXamlSource is null)
+ _host?.IsInitialized is not true)
{
return;
}
@@ -1318,6 +1346,17 @@ private void HostWindow_SystemSettingsChanged(object? sender, EventArgs e)
UpdateIslandBackdrops();
}
+#if UWP
+ private void Host_ContentAttached(object? sender, EventArgs e)
+ {
+ if (_disposed || !_showPending)
+ return;
+
+ _showPending = false;
+ Show(true);
+ }
+#endif
+
///
public void Dispose()
{
@@ -1331,6 +1370,9 @@ public void Dispose()
_host?.WindowInactivated -= HostWindow_Inactivated;
_host?.SystemSettingsChanged -= HostWindow_SystemSettingsChanged;
+#if UWP
+ _host?.ContentAttached -= Host_ContentAttached;
+#endif
RootGrid?.GettingFocus -= RootGrid_GettingFocus;
RootGrid?.PointerPressed -= RootGrid_PointerPressed;
RootGrid?.PointerMoved -= RootGrid_PointerMoved;
diff --git a/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems b/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems
index 2aecb33..466344e 100644
--- a/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems
+++ b/src/DesktopFlyouts.Shared/DesktopFlyouts.Shared.projitems
@@ -41,6 +41,7 @@
+
diff --git a/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs b/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs
index 2f830aa..bcda84c 100644
--- a/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs
+++ b/src/DesktopFlyouts.Shared/DesktopMenuFlyout.cs
@@ -7,6 +7,7 @@
#if UWP
+using Windows.Graphics;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Markup;
@@ -36,6 +37,9 @@ public partial class DesktopMenuFlyout : ItemsControl, IDisposable
private readonly XamlIslandHostWindow? _host;
private MenuFlyout? _menuFlyout;
private bool _disposed;
+#if UWP
+ private Point? _pendingShowPoint;
+#endif
private Border? MenuFlyoutTargetControl;
@@ -57,6 +61,9 @@ public DesktopMenuFlyout()
DefaultStyleKey = typeof(DesktopMenuFlyout);
_host = new XamlIslandHostWindow();
+#if UWP
+ _host.ContentAttached += Host_ContentAttached;
+#endif
_host.SetContent(this);
_ = _host.UpdateWindowVisibility(false);
_host.SystemSettingsChanged += HostWindow_SystemSettingsChanged;
@@ -101,7 +108,14 @@ protected override void OnItemsChanged(object e)
///
public void Show(Point point)
{
- if (_disposed || _menuFlyout is null)
+#if UWP
+ if (!_disposed && _host is not null && !_host.IsContentReady)
+ {
+ _pendingShowPoint = point;
+ return;
+ }
+#endif
+ if (_disposed || _host?.IsInitialized is not true || _menuFlyout is null)
return;
UpdateFlyoutTheme();
@@ -110,6 +124,14 @@ public void Show(Point point)
_host?.SetHWndRectRegion(new RectInt32() { Width = 1, Height = 1 });
_ = _host?.UpdateWindowVisibility(true);
+ ApplyTemplate();
+ UpdateLayout();
+ if (MenuFlyoutTargetControl?.XamlRoot is null)
+ {
+ _ = _host?.UpdateWindowVisibility(false);
+ return;
+ }
+
_menuFlyout.ShowAt(MenuFlyoutTargetControl);
IsOpen = true;
@@ -123,6 +145,9 @@ public void Show(Point point)
///
public void Hide()
{
+#if UWP
+ _pendingShowPoint = null;
+#endif
if (_disposed)
return;
@@ -147,6 +172,17 @@ private void HostWindow_SystemSettingsChanged(object? sender, EventArgs e)
UpdateFlyoutTheme();
}
+#if UWP
+ private void Host_ContentAttached(object? sender, EventArgs e)
+ {
+ if (_disposed || _pendingShowPoint is not Point point)
+ return;
+
+ _pendingShowPoint = null;
+ Show(point);
+ }
+#endif
+
#if UWP
///
/// Lets the XAML island process a native keyboard message before dispatch.
@@ -154,8 +190,8 @@ private void HostWindow_SystemSettingsChanged(object? sender, EventArgs e)
/// The native message to process.
/// if the message was handled; otherwise, .
///
- /// UWP desktop-host scenarios should call this from their native message loop so keyboard
- /// navigation and accelerator processing can reach the hosted XAML island.
+ /// XamlHostingKit owns the UWP desktop message loop and performs XAML message translation.
+ /// This method remains available for source compatibility and returns .
///
public unsafe bool TryPreTranslateMessage(MSG* msg)
{
@@ -187,6 +223,10 @@ public void Dispose()
}
_host?.SystemSettingsChanged -= HostWindow_SystemSettingsChanged;
+#if UWP
+ _host?.ContentAttached -= Host_ContentAttached;
+ _pendingShowPoint = null;
+#endif
_host?.Dispose();
IsOpen = false;
diff --git a/src/DesktopFlyouts.Shared/XamlIslandApplication.cs b/src/DesktopFlyouts.Shared/XamlIslandApplication.cs
index 0e51709..f4ffb31 100644
--- a/src/DesktopFlyouts.Shared/XamlIslandApplication.cs
+++ b/src/DesktopFlyouts.Shared/XamlIslandApplication.cs
@@ -3,7 +3,9 @@
// Licensed under the MIT license.
#if UWP
+using System;
using Windows.UI.Xaml;
+using XamlHostingKit;
#endif
namespace DesktopFlyouts.Shared
@@ -18,6 +20,32 @@ namespace DesktopFlyouts.Shared
///
public partial class XamlIslandApplication : Application
{
+ ///
+ /// Starts System XAML through XamlHostingKit and runs its desktop message loop.
+ ///
+ /// Creates the application's instance.
+ public new static void Start(ApplicationInitializationCallback initializationCallback)
+ {
+ ArgumentNullException.ThrowIfNull(initializationCallback);
+
+ XamlApplication.Start(initializationCallback);
+ }
+
+ ///
+ /// Creates another XamlHostingKit window and invokes a callback on its XAML thread.
+ ///
+ ///
+ /// Creates one or for the new window.
+ ///
+ ///
+ /// System XAML permits one top-level XAML window per thread. Create each additional
+ /// DesktopFlyouts host inside a separate callback supplied to this method.
+ ///
+ public static void CreateWindow(ApplicationInitializationCallback initializationCallback)
+ {
+ ArgumentNullException.ThrowIfNull(initializationCallback);
+ XamlApplication.CreateWindow(new WindowCreationOptions(), initializationCallback);
+ }
}
#endif
}
diff --git a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs
new file mode 100644
index 0000000..6e65a49
--- /dev/null
+++ b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.Uwp.cs
@@ -0,0 +1,763 @@
+#if UWP
+// Copyright (c) 0x5BFA. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Marshalling;
+using System.Threading;
+using System.Threading.Tasks;
+using Windows.Foundation;
+using Windows.Graphics;
+using Windows.Graphics.Display;
+using Windows.UI.Core;
+using Windows.UI.ViewManagement;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls;
+using Windows.UI.Xaml.Hosting;
+using Windows.UI.Xaml.Input;
+using Windows.Win32;
+using Windows.Win32.Foundation;
+using Windows.Win32.Graphics.Gdi;
+using Windows.Win32.System.Com;
+using Windows.Win32.System.WinRT;
+using Windows.Win32.UI.WindowsAndMessaging;
+using WinRT;
+using XamlHostingKit;
+
+namespace DesktopFlyouts
+{
+ internal sealed class CoreDispatcherSynchronizationContext : SynchronizationContext
+ {
+ private readonly CoreDispatcher _dispatcher;
+
+ internal CoreDispatcherSynchronizationContext(CoreDispatcher dispatcher)
+ {
+ _dispatcher = dispatcher;
+ }
+
+ public override void Post(SendOrPostCallback callback, object? state)
+ {
+ ArgumentNullException.ThrowIfNull(callback);
+ _ = _dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => callback(state));
+ }
+
+ public override void Send(SendOrPostCallback callback, object? state)
+ {
+ ArgumentNullException.ThrowIfNull(callback);
+
+ if (!_dispatcher.HasThreadAccess)
+ throw new NotSupportedException("Cross-thread synchronous dispatch is not supported.");
+
+ callback(state);
+ }
+
+ public override SynchronizationContext CreateCopy()
+ {
+ return new CoreDispatcherSynchronizationContext(_dispatcher);
+ }
+ }
+
+ internal unsafe partial class XamlIslandHostWindow : IDisposable
+ {
+ private static readonly object s_cbtHookTargetsLock = new();
+ private static readonly Dictionary> s_cbtHookTargetsByThread = [];
+ private static readonly object s_claimedWindowsLock = new();
+ private static readonly HashSet s_claimedWindows = [];
+
+ private readonly WNDPROC _xamlWndProc;
+ private readonly UISettings _uiSettings = new();
+ private readonly Dictionary _subclassedXamlWndProcs = [];
+
+ private XamlWindow? _xamlWindow;
+ private CoreWindow? _coreWindow;
+ private UIElement? _content;
+ private Grid? _contentRoot;
+ private HWND _xamlHwnd = default;
+ private HHOOK _cbtHook = default;
+ private uint _cbtHookThreadId;
+ private HWND _preservedForegroundHWnd = default;
+ private HWND _preservedActiveHWnd = default;
+ private HWND _preservedFocusHWnd = default;
+ private DispatcherTimer? _contentReadyFallbackTimer;
+ private bool _isContentReady;
+ private bool _isContentReadyQueued;
+ private bool _hasSeenInitialDeactivation;
+ private bool _disposed;
+ private DesktopFlyoutActivationMode _activationMode = DesktopFlyoutActivationMode.Activate;
+
+ internal HWND HWnd { get; private set; }
+
+ internal bool IsInitialized => _xamlWindow is not null;
+ internal bool IsContentReady => !_disposed && _isContentReady;
+
+ internal Rect WindowSize
+ {
+ get
+ {
+ RECT rect;
+ PInvoke.GetWindowRect(HWnd, &rect);
+ return new(rect.X, rect.Y, rect.Width, rect.Height);
+ }
+ }
+
+ internal double XamlIslandRasterizationScale
+ {
+ get
+ {
+ try
+ {
+ return DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
+ }
+ catch
+ {
+ return 1.0D;
+ }
+ }
+ }
+
+ internal event EventHandler? WindowInactivated;
+ internal event EventHandler? SystemSettingsChanged;
+ internal event EventHandler? ContentAttached;
+
+ internal XamlIslandHostWindow()
+ {
+ _xamlWndProc = new(XamlWndProc);
+
+ _xamlWindow = XamlWindow.Current;
+ if (_xamlWindow is null)
+ {
+ throw new InvalidOperationException(
+ "UWP DesktopFlyouts must be created inside a XamlHostingKit XamlWindow. " +
+ "Start the app with XamlIslandApplication.Start and create additional flyouts " +
+ "inside XamlIslandApplication.CreateWindow callbacks.");
+ }
+
+ if (_xamlWindow.Content is not null)
+ {
+ throw new InvalidOperationException(
+ "A XamlHostingKit XamlWindow can host only one DesktopFlyout or DesktopMenuFlyout instance.");
+ }
+
+ _xamlWindow.Title = "DesktopFlyoutHostWindow";
+ _xamlWindow.Styles = WindowStyles.Popup;
+ _xamlWindow.ExtendedStyles =
+ WindowExtendedStyles.NoRedirectionBitmap |
+ WindowExtendedStyles.ToolWindow |
+ WindowExtendedStyles.Topmost;
+
+ HWnd = (HWND)unchecked((nint)_xamlWindow.WindowHandle.Value);
+ if (HWnd.IsNull)
+ throw new InvalidOperationException("XamlHostingKit did not provide a host window handle.");
+
+ lock (s_claimedWindowsLock)
+ {
+ if (!s_claimedWindows.Add((nint)HWnd.Value))
+ {
+ throw new InvalidOperationException(
+ "A XamlHostingKit XamlWindow can host only one DesktopFlyout or DesktopMenuFlyout instance.");
+ }
+ }
+
+ try
+ {
+ _coreWindow = _xamlWindow.CoreWindow;
+ SynchronizationContext.SetSynchronizationContext(
+ new CoreDispatcherSynchronizationContext(_coreWindow.Dispatcher));
+ InitializeCoreWindowHandle();
+
+ _coreWindow.Activated += CoreWindow_Activated;
+ _uiSettings.ColorValuesChanged += UISettings_ColorValuesChanged;
+
+ ApplyActivationModeToWindows();
+ }
+ catch
+ {
+ lock (s_claimedWindowsLock)
+ s_claimedWindows.Remove((nint)HWnd.Value);
+
+ throw;
+ }
+ }
+
+ internal void SetContent(UIElement content)
+ {
+ if (_disposed || _xamlWindow is null)
+ return;
+
+ _isContentReady = false;
+ _isContentReadyQueued = false;
+ StopContentReadyFallbackTimer();
+ _content = content;
+ var dispatcher = _coreWindow?.Dispatcher;
+ if (dispatcher is null)
+ throw new InvalidOperationException("XamlHostingKit did not provide a CoreDispatcher.");
+
+ // A derived control calls InitializeComponent after its base DesktopFlyout constructor.
+ // Attach it on the next dispatcher turn so XAML parsing completes before Window.Content
+ // starts loading templates and resources.
+ _ = dispatcher.RunAsync(CoreDispatcherPriority.High, () =>
+ {
+ if (!_disposed && _xamlWindow is not null && ReferenceEquals(_content, content))
+ {
+ _contentRoot = new Grid();
+ var desktopFlyoutsResources = new DesktopFlyoutResources();
+ _contentRoot.Resources.MergedDictionaries.Add(desktopFlyoutsResources);
+
+ if (content is Control control)
+ {
+ control.Style = content switch
+ {
+ DesktopFlyout => desktopFlyoutsResources.FlyoutStyle,
+ DesktopMenuFlyout => desktopFlyoutsResources.MenuFlyoutStyle,
+ _ => throw new InvalidOperationException($"Unsupported flyout type: {content.GetType().Name}."),
+ };
+ }
+
+ _contentRoot.Children.Add(content);
+ _xamlWindow.Content = _contentRoot;
+
+ var wasVisible = PInvoke.IsWindowVisible(HWnd);
+ if (!wasVisible)
+ {
+ PInvoke.SetWindowPos(
+ HWnd,
+ HWND.Null,
+ -32000,
+ -32000,
+ 1,
+ 1,
+ SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE);
+ PInvoke.ShowWindow(HWnd, SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE);
+ }
+
+ if (content is Control templatedControl)
+ {
+ templatedControl.ApplyTemplate();
+ templatedControl.UpdateLayout();
+ }
+
+ if (!wasVisible)
+ PInvoke.ShowWindow(HWnd, SHOW_WINDOW_CMD.SW_HIDE);
+
+ ApplyActivationModeToWindows();
+ if (wasVisible || _hasSeenInitialDeactivation)
+ QueueContentReady();
+ else
+ StartContentReadyFallbackTimer();
+ }
+ });
+ }
+
+ private void StartContentReadyFallbackTimer()
+ {
+ StopContentReadyFallbackTimer();
+
+ _contentReadyFallbackTimer = new DispatcherTimer
+ {
+ Interval = TimeSpan.FromMilliseconds(100),
+ };
+ _contentReadyFallbackTimer.Tick += ContentReadyFallbackTimer_Tick;
+ _contentReadyFallbackTimer.Start();
+ }
+
+ private void ContentReadyFallbackTimer_Tick(object? sender, object e)
+ {
+ StopContentReadyFallbackTimer();
+ QueueContentReady();
+ }
+
+ private void StopContentReadyFallbackTimer()
+ {
+ if (_contentReadyFallbackTimer is null)
+ return;
+
+ _contentReadyFallbackTimer.Stop();
+ _contentReadyFallbackTimer.Tick -= ContentReadyFallbackTimer_Tick;
+ _contentReadyFallbackTimer = null;
+ }
+
+ private void QueueContentReady()
+ {
+ var dispatcher = _coreWindow?.Dispatcher;
+ if (_disposed || dispatcher is null || _isContentReady || _isContentReadyQueued)
+ return;
+
+ StopContentReadyFallbackTimer();
+ _isContentReadyQueued = true;
+ _ = dispatcher.RunAsync(CoreDispatcherPriority.Low, () =>
+ {
+ _isContentReadyQueued = false;
+ if (_disposed || _xamlWindow is null || _content is null)
+ return;
+
+ _isContentReady = _contentRoot is not null && _content.XamlRoot is not null;
+ if (_isContentReady)
+ ContentAttached?.Invoke(this, EventArgs.Empty);
+ });
+ }
+
+ internal void PreserveActivationState()
+ {
+ if (_disposed)
+ return;
+
+ _preservedForegroundHWnd = PInvoke.GetForegroundWindow();
+ _preservedActiveHWnd = PInvoke.GetActiveWindow();
+ _preservedFocusHWnd = PInvoke.GetFocus();
+ }
+
+ internal void RestoreActivationState()
+ {
+ if (_disposed)
+ return;
+
+ if (!_preservedForegroundHWnd.IsNull)
+ PInvoke.SetForegroundWindow(_preservedForegroundHWnd);
+
+ if (!_preservedActiveHWnd.IsNull)
+ PInvoke.SetActiveWindow(_preservedActiveHWnd);
+
+ if (!_preservedFocusHWnd.IsNull)
+ PInvoke.SetFocus(_preservedFocusHWnd);
+ }
+
+ internal void MoveAndResize(RectInt32 rect, bool activate = true)
+ {
+ if (_disposed)
+ return;
+
+ var flags = activate ? 0 : SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE;
+ PInvoke.SetWindowPos(HWnd, HWND.HWND_TOP, rect.X, rect.Y, rect.Width, rect.Height, flags);
+ }
+
+ internal void Maximize(System.Drawing.Rectangle workArea, bool activate = true)
+ {
+ if (_disposed)
+ return;
+
+ var flags = activate ? 0 : SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE;
+ PInvoke.SetWindowPos(HWnd, HWND.HWND_TOP, workArea.X, workArea.Y, workArea.Width, workArea.Height, flags);
+ }
+
+ internal void SetHWndRectRegion(RectInt32 rect)
+ {
+ if (_disposed)
+ return;
+
+ SetWindowRectRegion(HWnd, rect);
+ SetWindowRectRegion(_xamlHwnd, rect);
+ }
+
+ private static void SetWindowRectRegion(HWND hWnd, RectInt32 rect)
+ {
+ if (hWnd.IsNull)
+ return;
+
+ HRGN region = PInvoke.CreateRectRgn(rect.X, rect.Y, rect.X + rect.Width, rect.Y + rect.Height);
+ if (region.IsNull)
+ return;
+
+ if (PInvoke.SetWindowRgn(hWnd, region, false) == 0)
+ PInvoke.DeleteObject(region);
+ }
+
+ internal ValueTask UpdateWindowVisibility(bool isVisible, bool activate = true)
+ {
+ if (_disposed)
+ return default;
+
+ var command = isVisible
+ ? activate ? SHOW_WINDOW_CMD.SW_SHOW : SHOW_WINDOW_CMD.SW_SHOWNOACTIVATE
+ : SHOW_WINDOW_CMD.SW_HIDE;
+
+ PInvoke.ShowWindow(HWnd, command);
+
+ if (isVisible)
+ ApplyActivationModeToWindows();
+
+ return default;
+ }
+
+ internal void SetActivationMode(DesktopFlyoutActivationMode activationMode)
+ {
+ if (_disposed)
+ return;
+
+ _activationMode = activationMode;
+ ApplyActivationModeToWindows();
+ }
+
+ internal bool NavigateFocus(XamlSourceFocusNavigationReason reason = XamlSourceFocusNavigationReason.Programmatic)
+ {
+ if (_disposed || !_isContentReady || _xamlHwnd.IsNull || _content is null || _activationMode is DesktopFlyoutActivationMode.NeverActivate)
+ return false;
+
+ PInvoke.SetFocus(_xamlHwnd);
+
+ var direction = reason switch
+ {
+ XamlSourceFocusNavigationReason.Left => FocusNavigationDirection.Left,
+ XamlSourceFocusNavigationReason.Up => FocusNavigationDirection.Up,
+ XamlSourceFocusNavigationReason.Right => FocusNavigationDirection.Right,
+ XamlSourceFocusNavigationReason.Down => FocusNavigationDirection.Down,
+ _ => FocusNavigationDirection.None,
+ };
+
+ if (direction is not FocusNavigationDirection.None && FocusManager.TryMoveFocus(direction))
+ return true;
+
+ var focusTarget = reason switch
+ {
+ XamlSourceFocusNavigationReason.Last or
+ XamlSourceFocusNavigationReason.Left or
+ XamlSourceFocusNavigationReason.Up => FocusManager.FindLastFocusableElement(_content),
+ _ => FocusManager.FindFirstFocusableElement(_content),
+ };
+
+ var focusState = reason is XamlSourceFocusNavigationReason.Programmatic or XamlSourceFocusNavigationReason.Restore
+ ? FocusState.Programmatic
+ : FocusState.Keyboard;
+
+ return focusTarget is Control control && control.Focus(focusState);
+ }
+
+ public bool TryPreTranslateMessage(MSG* msg)
+ {
+ // XamlHostingKit owns the CoreWindow message loop and performs XAML message translation.
+ return false;
+ }
+
+ private void InitializeCoreWindowHandle()
+ {
+ if (_coreWindow is null)
+ throw new InvalidOperationException("XamlHostingKit did not provide a CoreWindow.");
+
+ void* ppv;
+ ((IUnknown*)((IWinRTObject)_coreWindow).NativeObject.ThisPtr)->QueryInterface(
+ (Guid*)Unsafe.AsPointer(ref Unsafe.AsRef(in IID.IID_ICoreWindowInterop)), &ppv);
+
+ var wrappers = new StrategyBasedComWrappers();
+ var interop = (ICoreWindowInterop)wrappers.GetOrCreateObjectForComInstance(
+ (nint)ppv,
+ CreateObjectFlags.None);
+ interop.get_WindowHandle((HWND*)Unsafe.AsPointer(ref _xamlHwnd));
+ }
+
+ private void ApplyActivationModeToWindows()
+ {
+ var neverActivate = _activationMode is DesktopFlyoutActivationMode.NeverActivate;
+ UpdateCbtHook(neverActivate);
+ if (neverActivate)
+ RefreshXamlWindowSubclasses();
+
+ SetNoActivateStyle(HWnd, neverActivate);
+ SetNoActivateStyle(_xamlHwnd, neverActivate);
+
+ foreach (var hWnd in _subclassedXamlWndProcs.Keys)
+ SetNoActivateStyle((HWND)hWnd, neverActivate);
+
+ if (!neverActivate)
+ UnsubclassXamlWindows();
+ }
+
+ private static void SetNoActivateStyle(HWND hWnd, bool enabled)
+ {
+ if (hWnd.IsNull)
+ return;
+
+ var exStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE);
+ exStyle = enabled
+ ? exStyle | WINDOW_EX_STYLE.WS_EX_NOACTIVATE
+ : exStyle & ~WINDOW_EX_STYLE.WS_EX_NOACTIVATE;
+
+ PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)exStyle);
+ PInvoke.SetWindowPos(
+ hWnd,
+ HWND.Null,
+ 0,
+ 0,
+ 0,
+ 0,
+ SET_WINDOW_POS_FLAGS.SWP_NOMOVE |
+ SET_WINDOW_POS_FLAGS.SWP_NOSIZE |
+ SET_WINDOW_POS_FLAGS.SWP_NOZORDER |
+ SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE |
+ SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED);
+ }
+
+ private void UpdateCbtHook(bool enabled)
+ {
+ if (enabled)
+ {
+ EnsureCbtHook();
+ return;
+ }
+
+ RemoveCbtHook();
+ }
+
+ private void EnsureCbtHook()
+ {
+ if (_cbtHook != HHOOK.Null)
+ return;
+
+ _cbtHookThreadId = PInvoke.GetCurrentThreadId();
+ _cbtHook = PInvoke.SetWindowsHookEx(
+ WINDOWS_HOOK_ID.WH_CBT,
+ &CbtHookProc,
+ HINSTANCE.Null,
+ _cbtHookThreadId);
+
+ if (_cbtHook != HHOOK.Null)
+ RegisterCbtHookTarget(_cbtHookThreadId);
+ }
+
+ private void RemoveCbtHook()
+ {
+ if (_cbtHook == HHOOK.Null)
+ return;
+
+ PInvoke.UnhookWindowsHookEx(_cbtHook);
+ _cbtHook = default;
+ UnregisterCbtHookTarget(_cbtHookThreadId);
+ _cbtHookThreadId = 0;
+ }
+
+ private void RegisterCbtHookTarget(uint threadId)
+ {
+ lock (s_cbtHookTargetsLock)
+ {
+ if (!s_cbtHookTargetsByThread.TryGetValue(threadId, out var targets))
+ {
+ targets = [];
+ s_cbtHookTargetsByThread[threadId] = targets;
+ }
+
+ if (!targets.Contains(this))
+ targets.Add(this);
+ }
+ }
+
+ private void UnregisterCbtHookTarget(uint threadId)
+ {
+ if (threadId == 0)
+ return;
+
+ lock (s_cbtHookTargetsLock)
+ {
+ if (!s_cbtHookTargetsByThread.TryGetValue(threadId, out var targets))
+ return;
+
+ targets.Remove(this);
+ if (targets.Count == 0)
+ s_cbtHookTargetsByThread.Remove(threadId);
+ }
+ }
+
+ private static XamlIslandHostWindow[] GetCbtHookTargets(uint threadId)
+ {
+ lock (s_cbtHookTargetsLock)
+ {
+ return s_cbtHookTargetsByThread.TryGetValue(threadId, out var targets)
+ ? [.. targets]
+ : [];
+ }
+ }
+
+ private void RefreshXamlWindowSubclasses()
+ {
+ if (_xamlHwnd.IsNull)
+ return;
+
+ SubclassXamlWindow(_xamlHwnd);
+ SubclassChildWindows(HWnd);
+ SubclassChildWindows(_xamlHwnd);
+ }
+
+ private void SubclassChildWindows(HWND parentHWnd)
+ {
+ for (var childHWnd = PInvoke.GetWindow(parentHWnd, GET_WINDOW_CMD.GW_CHILD);
+ !childHWnd.IsNull;
+ childHWnd = PInvoke.GetWindow(childHWnd, GET_WINDOW_CMD.GW_HWNDNEXT))
+ {
+ SubclassXamlWindow(childHWnd);
+ SubclassChildWindows(childHWnd);
+ }
+ }
+
+ private void SubclassXamlWindow(HWND hWnd)
+ {
+ if (hWnd.IsNull || _subclassedXamlWndProcs.ContainsKey((nint)hWnd.Value))
+ return;
+
+ var previousWndProc = (nint)PInvoke.SetWindowLongPtr(
+ hWnd,
+ WINDOW_LONG_PTR_INDEX.GWLP_WNDPROC,
+ Marshal.GetFunctionPointerForDelegate(_xamlWndProc));
+
+ _subclassedXamlWndProcs[(nint)hWnd.Value] = previousWndProc;
+ }
+
+ private void UnsubclassXamlWindows()
+ {
+ foreach (var item in _subclassedXamlWndProcs)
+ {
+ var hWnd = (HWND)item.Key;
+ if (!hWnd.IsNull)
+ PInvoke.SetWindowLongPtr(hWnd, WINDOW_LONG_PTR_INDEX.GWLP_WNDPROC, item.Value);
+ }
+
+ _subclassedXamlWndProcs.Clear();
+ }
+
+ private LRESULT XamlWndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
+ {
+ switch (uMsg)
+ {
+ case PInvoke.WM_MOUSEACTIVATE:
+ if (_activationMode is DesktopFlyoutActivationMode.NeverActivate)
+ {
+ RestoreActivationState();
+ return (LRESULT)(int)PInvoke.MA_NOACTIVATE;
+ }
+ break;
+ case PInvoke.WM_SETFOCUS:
+ if (_activationMode is DesktopFlyoutActivationMode.NeverActivate)
+ {
+ RestoreActivationState();
+ return (LRESULT)0;
+ }
+ break;
+ }
+
+ return CallPreviousXamlWndProc(hWnd, uMsg, wParam, lParam);
+ }
+
+ [UnmanagedCallersOnly(CallConvs = [typeof(CallConvStdcall)])]
+ private static LRESULT CbtHookProc(int code, WPARAM wParam, LPARAM lParam)
+ {
+ if (code >= 0 && (code == PInvoke.HCBT_ACTIVATE || code == PInvoke.HCBT_SETFOCUS))
+ {
+ var targetHWnd = (HWND)(nint)(nuint)wParam;
+ foreach (var target in GetCbtHookTargets(PInvoke.GetCurrentThreadId()))
+ {
+ if (target._activationMode is DesktopFlyoutActivationMode.NeverActivate && target.IsFlyoutWindow(targetHWnd))
+ {
+ target.RestoreActivationState();
+ return (LRESULT)1;
+ }
+ }
+ }
+
+ return PInvoke.CallNextHookEx(HHOOK.Null, code, wParam, lParam);
+ }
+
+ private bool IsFlyoutWindow(HWND hWnd)
+ {
+ if (hWnd.IsNull)
+ return false;
+
+ if (hWnd == HWnd || hWnd == _xamlHwnd)
+ return true;
+
+ if (_subclassedXamlWndProcs.ContainsKey((nint)hWnd.Value))
+ return true;
+
+ if (!HWnd.IsNull && PInvoke.IsChild(HWnd, hWnd))
+ return true;
+
+ if (!_xamlHwnd.IsNull && PInvoke.IsChild(_xamlHwnd, hWnd))
+ return true;
+
+ var rootHWnd = PInvoke.GetAncestor(hWnd, GET_ANCESTOR_FLAGS.GA_ROOT);
+ if (rootHWnd == HWnd || rootHWnd == _xamlHwnd)
+ return true;
+
+ var rootOwnerHWnd = PInvoke.GetAncestor(hWnd, GET_ANCESTOR_FLAGS.GA_ROOTOWNER);
+ return rootOwnerHWnd == HWnd || rootOwnerHWnd == _xamlHwnd;
+ }
+
+ private LRESULT CallPreviousXamlWndProc(HWND hWnd, uint uMsg, WPARAM wParam, LPARAM lParam)
+ {
+ if (!_subclassedXamlWndProcs.TryGetValue((nint)hWnd.Value, out var previousWndProc) || previousWndProc == 0)
+ return PInvoke.DefWindowProc(hWnd, uMsg, wParam, lParam);
+
+ return PInvoke.CallWindowProc(
+ (delegate* unmanaged[Stdcall])(void*)previousWndProc,
+ hWnd,
+ uMsg,
+ wParam,
+ lParam);
+ }
+
+ private void CoreWindow_Activated(CoreWindow sender, WindowActivatedEventArgs args)
+ {
+ if (_disposed || args.WindowActivationState is not CoreWindowActivationState.Deactivated)
+ return;
+
+ if (!_isContentReady)
+ {
+ _hasSeenInitialDeactivation = true;
+ if (_contentRoot is not null)
+ QueueContentReady();
+
+ return;
+ }
+
+ WindowInactivated?.Invoke(this, EventArgs.Empty);
+ }
+
+ private void UISettings_ColorValuesChanged(UISettings sender, object args)
+ {
+ var dispatcher = _coreWindow?.Dispatcher;
+ if (_disposed || dispatcher is null)
+ return;
+
+ _ = dispatcher.TryRunAsync(
+ CoreDispatcherPriority.Normal,
+ () => SystemSettingsChanged?.Invoke(this, EventArgs.Empty));
+ }
+
+ public void Dispose()
+ {
+ if (_disposed)
+ return;
+
+ _disposed = true;
+ StopContentReadyFallbackTimer();
+ RemoveCbtHook();
+ UnsubclassXamlWindows();
+
+ _uiSettings.ColorValuesChanged -= UISettings_ColorValuesChanged;
+ if (_coreWindow is not null)
+ _coreWindow.Activated -= CoreWindow_Activated;
+
+ if (_xamlWindow is not null)
+ _xamlWindow.Content = null;
+
+ _contentRoot?.Children.Clear();
+ _contentRoot = null;
+ _content = null;
+ _isContentReady = false;
+ _isContentReadyQueued = false;
+ _hasSeenInitialDeactivation = false;
+
+ lock (s_claimedWindowsLock)
+ s_claimedWindows.Remove((nint)HWnd.Value);
+
+ if (!HWnd.IsNull)
+ PInvoke.DestroyWindow(HWnd);
+
+ _xamlWindow = null;
+ _coreWindow = null;
+ HWnd = default;
+ _xamlHwnd = default;
+
+ GC.SuppressFinalize(this);
+ }
+ }
+}
+#endif
diff --git a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs
index cbe4cd1..7642cd0 100644
--- a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs
+++ b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.X11.cs
@@ -33,6 +33,8 @@ internal partial class XamlIslandHostWindow : IDisposable
internal object? DesktopWindowXamlSource { get; private set; }
+ internal bool IsInitialized => DesktopWindowXamlSource is not null;
+
internal Rect WindowSize
{
get
diff --git a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs
index 9a96af9..3578cf5 100644
--- a/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs
+++ b/src/DesktopFlyouts.Shared/XamlIslandHostWindow.cs
@@ -1,4 +1,4 @@
-#if !HAS_UNO
+#if WASDK && !HAS_UNO
// Copyright (c) 0x5BFA. All rights reserved.
// Licensed under the MIT license.
@@ -75,6 +75,8 @@ internal DesktopWindowXamlSource? DesktopWindowXamlSource
private set;
}
+ internal bool IsInitialized => DesktopWindowXamlSource is not null;
+
internal Rect WindowSize
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml b/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml
index 5a14ebb..62e89e9 100644
--- a/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml
+++ b/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml
@@ -3,10 +3,22 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DesktopFlyouts">
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
+ 8
+
+
+
+
diff --git a/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml.cs b/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml.cs
new file mode 100644
index 0000000..1493fe9
--- /dev/null
+++ b/src/DesktopFlyouts.Uwp/DesktopFlyout.xaml.cs
@@ -0,0 +1,30 @@
+// Copyright (c) 0x5BFA. All rights reserved.
+// Licensed under the MIT license.
+
+using System;
+using Windows.UI.Xaml;
+using Windows.UI.Xaml.Controls.Primitives;
+
+namespace DesktopFlyouts
+{
+ internal sealed class DesktopFlyoutResources : ResourceDictionary
+ {
+ internal Style FlyoutStyle => GetStyle("DefaultWindows11DesktopFlyoutStyle");
+
+ internal Style MenuFlyoutStyle => GetStyle("DefaultWindows11DesktopMenuFlyoutStyle");
+
+ public DesktopFlyoutResources()
+ {
+ Application.LoadComponent(
+ this,
+ new Uri("ms-appx:///DesktopFlyouts.Uwp/DesktopFlyout.xaml"),
+ ComponentResourceLocation.Nested);
+ }
+
+ private Style GetStyle(string key)
+ {
+ return this[key] as Style
+ ?? throw new InvalidOperationException($"Could not find {key}.");
+ }
+ }
+}
diff --git a/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj b/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj
index ddf7a7e..12b02f9 100644
--- a/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj
+++ b/src/DesktopFlyouts.Uwp/DesktopFlyouts.Uwp.csproj
@@ -1,4 +1,4 @@
-
+
@@ -41,6 +41,8 @@
+
+