diff --git a/README.md b/README.md index 5740bc3d7..76b12d055 100644 --- a/README.md +++ b/README.md @@ -62,6 +62,16 @@ git clone https://github.com/microsoft/WinUI-Gallery.git Ensure that the `WinUIGallery` project is set as the startup project in Visual Studio. +Gallery uses an experimental Windows App SDK to demonstrate upcoming features. +Use the normal `Debug` or `Release` configuration; the SDK version is pinned in +`standalone.props`. The runtime is bundled with the app (self-contained deployment) +instead of referencing a shared Windows App SDK framework package. Experimental +APIs can change or be removed before a stable release. + +The **Windowing APIs** page combines stable window creation with experimental +window sizing examples. Each experimental example has its own label and warning; +the stable example retains its original APIs. + > [!WARNING] > Try deleting [`nuget.config`](nuget.config) and building again if you get the following build error: > > Assets file 'C:\Users\\...\source\repos\WinUI-Gallery\WinUIGallery\obj\WinUIGallery\project.assets.json' not found. Run a NuGet package restore to generate this file. diff --git a/WinUIGallery/Assets/ControlImages/CreateMultipleWindows.png b/WinUIGallery/Assets/ControlImages/Windowing.png similarity index 100% rename from WinUIGallery/Assets/ControlImages/CreateMultipleWindows.png rename to WinUIGallery/Assets/ControlImages/Windowing.png diff --git a/WinUIGallery/Controls/ControlExample.xaml b/WinUIGallery/Controls/ControlExample.xaml index 46bb3a4db..187df199e 100644 --- a/WinUIGallery/Controls/ControlExample.xaml +++ b/WinUIGallery/Controls/ControlExample.xaml @@ -38,7 +38,19 @@ Duration="0:0:0.1" /> - + + + + @@ -61,17 +73,40 @@ + - + + + + + + + + - + + + + + @@ -240,5 +287,6 @@ + diff --git a/WinUIGallery/Controls/ControlExample.xaml.cs b/WinUIGallery/Controls/ControlExample.xaml.cs index 5d715de1e..8caee04c5 100644 --- a/WinUIGallery/Controls/ControlExample.xaml.cs +++ b/WinUIGallery/Controls/ControlExample.xaml.cs @@ -181,6 +181,23 @@ public Visibility SourceCodeVisibility set { SetValue(SourceCodeVisibilityProperty, value); } } + public static readonly DependencyProperty IsExperimentalProperty = DependencyProperty.Register(nameof(IsExperimental), typeof(bool), typeof(ControlExample), new PropertyMetadata(false)); + public bool IsExperimental + { + get { return (bool)GetValue(IsExperimentalProperty); } + set { SetValue(IsExperimentalProperty, value); } + } + + public static Thickness BoolToExperimentalMargin(bool value) + { + return value ? new Thickness(8) : new Thickness(0); + } + + public static Thickness BoolToHeaderMargin(bool value) + { + return value ? new Thickness(12, 0, 0, 0) : new Thickness(0); + } + public ControlExample() { this.InitializeComponent(); diff --git a/WinUIGallery/Helpers/ControlInfoDataSource.cs b/WinUIGallery/Helpers/ControlInfoDataSource.cs index fe707b568..41d1f3cd7 100644 --- a/WinUIGallery/Helpers/ControlInfoDataSource.cs +++ b/WinUIGallery/Helpers/ControlInfoDataSource.cs @@ -104,6 +104,7 @@ private async Task GetControlInfoDataAsync() { string badgeString = item switch { + { IsExperimental: true } => "Experimental", { IsNew: true } => "New", { IsUpdated: true } => "Updated", { IsPreview: true } => "Preview", diff --git a/WinUIGallery/Helpers/NativeMethods.cs b/WinUIGallery/Helpers/NativeMethods.cs index 87b87db4f..77c0040bf 100644 --- a/WinUIGallery/Helpers/NativeMethods.cs +++ b/WinUIGallery/Helpers/NativeMethods.cs @@ -9,22 +9,12 @@ namespace WinUIGallery.Helpers; internal partial class NativeMethods { - [DllImport("user32.dll", EntryPoint = "SetWindowLong")] - internal static extern int SetWindowLong32(IntPtr hWnd, WindowLongIndexFlags nIndex, IntPtr newProc); - - [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")] - internal static extern IntPtr SetWindowLongPtr64(IntPtr hWnd, WindowLongIndexFlags nIndex, IntPtr newProc); - [DllImport("User32.dll", CharSet = CharSet.Auto, EntryPoint = "SetWindowLongPtr")] internal static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong); [DllImport("User32.dll", CharSet = CharSet.Auto, EntryPoint = "SetWindowLong")] internal static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong); - - [DllImport("user32.dll")] - internal static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage Msg, IntPtr wParam, IntPtr lParam); - internal unsafe static void SetWindowKeyHook() { delegate* unmanaged[Stdcall] callback = &HookCallback; @@ -56,19 +46,6 @@ internal static bool IsKeyDownHook(IntPtr lWord) return (lWord >> 30 & 1) == 0; } - internal delegate IntPtr WinProc(IntPtr hWnd, WindowMessage Msg, IntPtr wParam, IntPtr lParam); - - [Flags] - internal enum WindowLongIndexFlags : int - { - GWL_WNDPROC = -4, - } - - internal enum WindowMessage : int - { - WM_GETMINMAXINFO = 0x0024, - } - internal static bool IsAppPackaged { get; } = GetCurrentPackageName() != null; internal static string? GetCurrentPackageName() { diff --git a/WinUIGallery/Helpers/VersionHelper.cs b/WinUIGallery/Helpers/VersionHelper.cs index df1d621d9..d563a5896 100644 --- a/WinUIGallery/Helpers/VersionHelper.cs +++ b/WinUIGallery/Helpers/VersionHelper.cs @@ -1,14 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -using Microsoft.Windows.ApplicationModel.WindowsAppRuntime; +using Microsoft.WindowsAppSDK; namespace WinUIGallery.Helpers; + internal static partial class VersionHelper { + // Use SDK-generated version info; self-contained apps don't deploy the framework's Insights resource DLL. public static string WinAppSdkDetails => - $"Windows App SDK {ReleaseInfo.Major}.{ReleaseInfo.Minor}"; + $"Windows App SDK {Release.Major}.{Release.Minor}" + + (string.IsNullOrEmpty(Release.Channel) ? string.Empty : $" ({Release.Channel})"); public static string WinAppSdkRuntimeDetails => - WinAppSdkDetails + $", Windows App Runtime {RuntimeInfo.AsString}"; + WinAppSdkDetails + $", Windows App Runtime {Microsoft.WindowsAppSDK.Runtime.Version.DotQuadString}"; } diff --git a/WinUIGallery/Helpers/Win32WindowHelper.cs b/WinUIGallery/Helpers/Win32WindowHelper.cs deleted file mode 100644 index faacb1161..000000000 --- a/WinUIGallery/Helpers/Win32WindowHelper.cs +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.UI.Xaml; -using System.Runtime.InteropServices; -using static WinUIGallery.Helpers.NativeMethods; - -namespace WinUIGallery.Helpers; - -internal partial class Win32WindowHelper -{ - private static WinProc? newWndProc = null; - private static nint oldWndProc = nint.Zero; - - private POINT? minWindowSize = null; - private POINT? maxWindowSize = null; - - private readonly Window window; - - public Win32WindowHelper(Window window) - { - this.window = window; - } - - public void SetWindowMinMaxSize(POINT? minWindowSize = null, POINT? maxWindowSize = null) - { - this.minWindowSize = minWindowSize; - this.maxWindowSize = maxWindowSize; - - var hwnd = GetWindowHandleForCurrentWindow(window); - - newWndProc = new WinProc(WndProc); - oldWndProc = SetWindowLongPtr(hwnd, WindowLongIndexFlags.GWL_WNDPROC, newWndProc); - } - - private static nint GetWindowHandleForCurrentWindow(object target) => - WinRT.Interop.WindowNative.GetWindowHandle(target); - - private nint WndProc(nint hWnd, WindowMessage Msg, nint wParam, nint lParam) - { - switch (Msg) - { - case WindowMessage.WM_GETMINMAXINFO: - var dpi = Windows.Win32.PInvoke.GetDpiForWindow(new Windows.Win32.Foundation.HWND(hWnd)); - var scalingFactor = (float)dpi / 96; - - var minMaxInfo = Marshal.PtrToStructure(lParam); - if (minWindowSize != null) - { - minMaxInfo.ptMinTrackSize.x = (int)(minWindowSize.Value.x * scalingFactor); - minMaxInfo.ptMinTrackSize.y = (int)(minWindowSize.Value.y * scalingFactor); - } - if (maxWindowSize != null) - { - minMaxInfo.ptMaxTrackSize.x = (int)(maxWindowSize.Value.x * scalingFactor); - minMaxInfo.ptMaxTrackSize.y = (int)(maxWindowSize.Value.y * scalingFactor); - } - - Marshal.StructureToPtr(minMaxInfo, lParam, true); - break; - - } - return CallWindowProc(oldWndProc, hWnd, Msg, wParam, lParam); - } - - private nint SetWindowLongPtr(nint hWnd, WindowLongIndexFlags nIndex, WinProc newProc) - { - nint funcPtr = Marshal.GetFunctionPointerForDelegate(newProc); - if (nint.Size == 8) - return SetWindowLongPtr64(hWnd, nIndex, funcPtr); - else - return new nint(SetWindowLong32(hWnd, nIndex, funcPtr)); - } - - internal struct POINT - { - public int x; - public int y; - } - - [StructLayout(LayoutKind.Sequential)] - private struct MINMAXINFO - { - public POINT ptReserved; - public POINT ptMaxSize; - public POINT ptMaxPosition; - public POINT ptMinTrackSize; - public POINT ptMaxTrackSize; - } -} diff --git a/WinUIGallery/MainWindow.xaml.cs b/WinUIGallery/MainWindow.xaml.cs index 439c55342..4f2084f1d 100644 --- a/WinUIGallery/MainWindow.xaml.cs +++ b/WinUIGallery/MainWindow.xaml.cs @@ -9,6 +9,7 @@ using Microsoft.UI.Xaml.Input; using Microsoft.UI.Xaml.Media.Animation; using Microsoft.UI.Xaml.Navigation; +using Microsoft.Windows.Storage; using System; using System.Collections.Generic; using System.Linq; @@ -54,6 +55,41 @@ public MainWindow() AdjustNavigationViewMargin(force: true); AppWindow.Changed += (_, _) => AdjustNavigationViewMargin(); } + + InitializeSizePersistence(); + } + + // Use a rudimenentary persistence mechanism to save the window size between app launches. + // In the future we hope to have persistence APIs directly in WinUI/WinAppSDK. + private void InitializeSizePersistence() + { + ApplicationData appData = NativeMethods.IsAppPackaged + ? ApplicationData.GetDefault() + : ApplicationData.GetForUnpackaged(ProcessInfoHelper.Publisher, ProcessInfoHelper.ProductName); + + const string containerName = "MainWindow_Settings"; + const string valueName = "SavedSize"; + + if (appData.LocalSettings.Containers.TryGetValue(containerName, out var settingsContainer) && + settingsContainer.Values.TryGetValue(valueName, out object? value) && + value is Size savedSize) + { + Width = savedSize.Width; + Height = savedSize.Height; + } + + Size lastSize = new Size(Width, Height); + + SizeChanged += (s, e) => + { + lastSize = new Size(Width, Height); + }; + + Closed += (s, e) => + { + ApplicationDataContainer appDataContainer = appData.LocalSettings.CreateContainer(containerName, ApplicationDataCreateDisposition.Always); + appDataContainer.Values[valueName] = lastSize; + }; } // Adjusts the NavigationView margin based on the window state @@ -75,14 +111,6 @@ private void AdjustNavigationViewMargin(bool? force = null) private void RootGrid_Loaded(object sender, RoutedEventArgs e) { - // We need to set the minimum size here because the XamlRoot is not available in the constructor. - WindowHelper.SetWindowMinSize(this, 640, 500); - - if (sender is FrameworkElement rootGrid && rootGrid.XamlRoot is not null) - { - rootGrid.XamlRoot.Changed += RootGridXamlRoot_Changed; - } - NavigationOrientationHelper.UpdateNavigationViewForElement(NavigationOrientationHelper.IsLeftMode()); TitleBarHelper.ApplySystemThemeToCaptionButtons(this, RootGrid.ActualTheme); } @@ -109,11 +137,6 @@ private void RootGrid_PointerPressed(object sender, PointerRoutedEventArgs e) } } - private void RootGridXamlRoot_Changed(XamlRoot sender, XamlRootChangedEventArgs args) - { - WindowHelper.SetWindowMinSize(this, 640, 500); - } - private void SetWindowProperties() { #if DEBUG || DEBUG_UNPACKAGED @@ -126,6 +149,9 @@ private void SetWindowProperties() this.SetTitleBar(titleBar); this.AppWindow.SetIcon("Assets/Tiles/GalleryIcon.ico"); this.AppWindow.TitleBar.PreferredHeightOption = TitleBarHeightOption.Tall; + + MinWidth = 640; + MinHeight = 500; } private void OnPaneDisplayModeChanged(NavigationView sender, NavigationViewDisplayModeChangedEventArgs args) diff --git a/WinUIGallery/Models/ControlInfoData.cs b/WinUIGallery/Models/ControlInfoData.cs index 22927cdf5..5d13a3fef 100644 --- a/WinUIGallery/Models/ControlInfoData.cs +++ b/WinUIGallery/Models/ControlInfoData.cs @@ -41,6 +41,7 @@ public partial class ControlInfoDataItem public bool IsNew { get; set; } public bool IsUpdated { get; set; } public bool IsPreview { get; set; } + public bool IsExperimental { get; set; } public ObservableCollection Docs { get; set; } = []; public bool IncludedInBuild { get; set; } diff --git a/WinUIGallery/NativeMethods.txt b/WinUIGallery/NativeMethods.txt index cd2c02982..2b899b698 100644 --- a/WinUIGallery/NativeMethods.txt +++ b/WinUIGallery/NativeMethods.txt @@ -1,7 +1,6 @@ GetCurrentPackageFullName GetModuleHandle GetDpiForWindow -CallWindowProc GetCurrentThreadId SetWindowsHookEx CallNextHookEx diff --git a/WinUIGallery/Pages/ItemPage.xaml b/WinUIGallery/Pages/ItemPage.xaml index 9b1aaee5a..3252b1848 100644 --- a/WinUIGallery/Pages/ItemPage.xaml +++ b/WinUIGallery/Pages/ItemPage.xaml @@ -31,18 +31,27 @@ + + diff --git a/WinUIGallery/SampleSupport/Data/ControlInfoData.json b/WinUIGallery/SampleSupport/Data/ControlInfoData.json index 681e0569f..4e0fc9f7f 100644 --- a/WinUIGallery/SampleSupport/Data/ControlInfoData.json +++ b/WinUIGallery/SampleSupport/Data/ControlInfoData.json @@ -4035,6 +4035,45 @@ "Title": "Windowing", "IconGlyph": "", "Items": [ + { + "UniqueId": "Windowing", + "Tags": [ + "multiple windows", + "new window", + "multi window", + "experimental", + "window sizing", + "client area", + "DIPs", + "Width", + "Height", + "MinWidth", + "MinHeight", + "MaxWidth", + "MaxHeight" + ], + "Title": "Windowing APIs", + "ApiNamespace": "Microsoft.UI.Xaml", + "Subtitle": "Create top-level XAML windows and configure their client-area size.", + "ImagePath": "ms-appx:///Assets/ControlImages/Windowing.png", + "Description": "Create single-threaded top-level XAML windows, then explore experimental client-area sizing and minimum/maximum limits.", + "IsNew": true, + "Docs": [ + { + "Title": "Windows App SDK release channels", + "Uri": "https://learn.microsoft.com/windows/apps/windows-app-sdk/release-channels" + }, + { + "Title": "Window - API", + "Uri": "https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.window?view=windows-app-sdk-2.0-experimental" + } + ], + "RelatedControls": [ + "AppWindow", + "AppWindowTitleBar", + "TitleBar" + ] + }, { "UniqueId": "AppWindow", "Tags": [ @@ -4079,7 +4118,7 @@ "RelatedControls": [ "AppWindowTitleBar", "TitleBar", - "CreateMultipleWindows" + "Windowing" ] }, { @@ -4112,34 +4151,6 @@ "TitleBar" ] }, - { - "UniqueId": "CreateMultipleWindows", - "Tags": [ - "multiple windows", - "new window", - "multi window" - ], - "Title": "Multiple windows", - "ApiNamespace": "Microsoft.UI.Xaml", - "Subtitle": "An example showing the creation of single-threaded top level Xaml windows.", - "ImagePath": "ms-appx:///Assets/ControlImages/CreateMultipleWindows.png", - "Description": "With Windows App SDK 1.0 we are allowing creation of single-threaded multiple top level Xaml windows in Desktop apps", - "Docs": [ - { - "Title": "MultipleWindow - API", - "Uri": "https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.window" - }, - { - "Title": "Guidelines", - "Uri": "https://learn.microsoft.com/windows/windows-app-sdk/api/winrt/microsoft.ui.xaml.window" - } - ], - "RelatedControls": [ - "AppWindow", - "AppWindowTitleBar", - "TitleBar" - ] - }, { "UniqueId": "TitleBar", "Tags": [ diff --git a/WinUIGallery/SampleSupport/Data/ControlInfoDataSchema.json b/WinUIGallery/SampleSupport/Data/ControlInfoDataSchema.json index e033b88cb..8651990f7 100644 --- a/WinUIGallery/SampleSupport/Data/ControlInfoDataSchema.json +++ b/WinUIGallery/SampleSupport/Data/ControlInfoDataSchema.json @@ -66,6 +66,11 @@ "default": true, "description": "Whether the example page of this control has been updated lately. If true, it will be displayed in the Recently Updated Sample section on the main page and will have a dot on the overview card of this control" }, + "IsExperimental": { + "type": "boolean", + "default": false, + "description": "Whether this sample requires the experimental Windows App SDK. Experimental samples are only included in experimental builds and display experimental banners." + }, "Docs": { "type": "array", "items": { diff --git a/WinUIGallery/Samples/AppNotification/AppNotificationPage.xaml b/WinUIGallery/Samples/AppNotification/AppNotificationPage.xaml index e9e6c5beb..ae7f7fd9a 100644 --- a/WinUIGallery/Samples/AppNotification/AppNotificationPage.xaml +++ b/WinUIGallery/Samples/AppNotification/AppNotificationPage.xaml @@ -11,33 +11,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/WinUIGallery/Samples/CreateMultipleWindows/CreateMultipleWindowsPage.xaml.cs b/WinUIGallery/Samples/CreateMultipleWindows/CreateMultipleWindowsPage.xaml.cs deleted file mode 100644 index 1db65fd08..000000000 --- a/WinUIGallery/Samples/CreateMultipleWindows/CreateMultipleWindowsPage.xaml.cs +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using Microsoft.UI.Xaml; -using Microsoft.UI.Xaml.Controls; -using Microsoft.UI.Xaml.Media; -using Windows.Graphics; -using WinUIGallery.Helpers; - -namespace WinUIGallery.ControlPages; - -public sealed partial class CreateMultipleWindowsPage : Page -{ - public CreateMultipleWindowsPage() - { - this.InitializeComponent(); - } - - private void CreateNewWindow_Click(object sender, RoutedEventArgs e) - { - var childWindow = new Window() - { - ExtendsContentIntoTitleBar = true, - SystemBackdrop = new MicaBackdrop(), - Content = new Page() - { - Content = new TextBlock() - { - Text = "New child window!", - HorizontalAlignment = HorizontalAlignment.Center, - VerticalAlignment = VerticalAlignment.Center, - }, - // Get the theme from the parent. - RequestedTheme = this.ActualTheme, - } - }; - - // We need to track the new window so it can be closed when the app is closing, - // otherwise it will crash the app. - // This is also used to change the theme for all windows when the app theme changes. - WindowHelper.TrackWindow(childWindow); - childWindow.AppWindow.ResizeClient(new SizeInt32(500, 500)); - childWindow.Activate(); - } -} diff --git a/WinUIGallery/Samples/Windowing/CreateMultipleWindows.txt b/WinUIGallery/Samples/Windowing/CreateMultipleWindows.txt new file mode 100644 index 000000000..85bccdd9f --- /dev/null +++ b/WinUIGallery/Samples/Windowing/CreateMultipleWindows.txt @@ -0,0 +1,21 @@ +--- header +Create a single-threaded top-level Window. +--- c# +Window childWindow = new Window +{ + ExtendsContentIntoTitleBar = true, + SystemBackdrop = new MicaBackdrop(), + Content = new TextBlock + { + Text = "New child window!", + HorizontalAlignment = HorizontalAlignment.Center, + VerticalAlignment = VerticalAlignment.Center, + RequestedTheme = ActualTheme + } +}; + +// Gallery tracks child windows so they close when the app closes. +WindowHelper.TrackWindow(childWindow); +// ResizeClient takes physical pixels, independent of display scaling. +childWindow.AppWindow.ResizeClient(new SizeInt32(500, 500)); +childWindow.Activate(); \ No newline at end of file diff --git a/WinUIGallery/Samples/Windowing/WindowClientSize.txt b/WinUIGallery/Samples/Windowing/WindowClientSize.txt new file mode 100644 index 000000000..3d35fcb80 --- /dev/null +++ b/WinUIGallery/Samples/Windowing/WindowClientSize.txt @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +--- header +Set the initial client size with Window.Width and Window.Height +--- xaml + + + + +--- c# +// The same size properties can be set in C# instead of XAML. +Window window = new Window +{ + Width = $(Width), + Height = $(Height), + Content = new TextBlock { Margin = new Thickness(24), Text = "Resize this window." } +}; + +// Gallery tracks child windows so they close when the app closes. +WindowHelper.TrackWindow(window); +// Use Window.Activate() for the first show, including for XAML-defined windows. +window.Activate(); diff --git a/WinUIGallery/Samples/Windowing/WindowSizeConstraints.txt b/WinUIGallery/Samples/Windowing/WindowSizeConstraints.txt new file mode 100644 index 000000000..6a9e1649c --- /dev/null +++ b/WinUIGallery/Samples/Windowing/WindowSizeConstraints.txt @@ -0,0 +1,42 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +--- header +Constrain the client size with minimum and maximum dimensions +--- xaml + + + + + +--- c# +// The same size constraints can be set in C# instead of XAML. +Window window = new Window +{ + Width = 640, + Height = 480, + MinWidth = $(MinWidth), + MinHeight = $(MinHeight), + MaxWidth = $(MaxWidth), + MaxHeight = $(MaxHeight), + Content = new TextBlock { Margin = new Thickness(24), Text = "Resize this window." } +}; + +if (window.AppWindow.Presenter is OverlappedPresenter presenter) +{ + presenter.IsMaximizable = false; +} + +// Gallery tracks child windows so they close when the app closes. +WindowHelper.TrackWindow(window); +// Use Window.Activate() for the first show, including for XAML-defined windows. +window.Activate(); diff --git a/WinUIGallery/Samples/Windowing/WindowingPage.xaml b/WinUIGallery/Samples/Windowing/WindowingPage.xaml new file mode 100644 index 000000000..148fe8a80 --- /dev/null +++ b/WinUIGallery/Samples/Windowing/WindowingPage.xaml @@ -0,0 +1,166 @@ + + + + + + + + +