From 746753a3fe10be36793590fab33dffcad9c4e33d Mon Sep 17 00:00:00 2001 From: Morten Nielsen Date: Mon, 18 Nov 2024 19:54:48 -0800 Subject: [PATCH 1/2] Initial work on the new fast splash screen (#186) * new fast and simple splash screen --- docs/concepts/Splashscreen.md | 126 ++++++-- src/Directory.Build.targets | 2 +- src/WinUIEx/NativeMethods.txt | 13 +- src/WinUIEx/SimpleSplashScreen.cs | 381 +++++++++++++++++++++++++ src/WinUIEx/SplashScreen.cs | 5 +- src/WinUIEx/WinUIEx.csproj | 4 +- src/WinUIExSample/App.xaml.cs | 41 ++- src/WinUIExSample/WinUIExSample.csproj | 1 + 8 files changed, 535 insertions(+), 38 deletions(-) create mode 100644 src/WinUIEx/SimpleSplashScreen.cs diff --git a/docs/concepts/Splashscreen.md b/docs/concepts/Splashscreen.md index 33c8228..965239f 100644 --- a/docs/concepts/Splashscreen.md +++ b/docs/concepts/Splashscreen.md @@ -1,7 +1,73 @@ ## Splash Screen +WinUIEx provides two kinds of splash screens: + - `SimpleSplashScreen`: A simple splash screen that shows just an image and can be launched before any UI loads. + - `SplashScreen`: A more advanced splash screen that can show any XAML including a progress bar and status text. + + +### SimpleSplashScreen + +`SimpleSplashScreen` will show an image while the app is loading. To use it, create a new `SimpleSplashScreen` in App.xaml.cs: + +```cs +private SimpleSplashScreen vss { get; set; } + +public App() +{ + fss = SimpleSplashScreen.ShowDefaultSplashScreen(); // Shows the splash screen you already defined in your app manifest. For unpackaged apps use .ShowSplashScreenImage(imagepath): + // fss = SimpleSplashScreen.ShowSplashScreenImage(full_path_to_image_); // Shows a custom splash screen image. Must be a full-path (no relative paths) + this.InitializeComponent(); +} +``` + +Once your window is activated, you can remove the splash screen by either calling `.Dispose()` or `Hide()`. + +```cs +protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) +{ + m_window = new MainWindow(); + m_window.Activate(); + Window_Activated += Window_Activated; + +} + +private void Window_Activated(object sender, WindowActivatedEventArgs args) +{ + ((Window)sender).Activated -= Window_Activated; + fss?.Hide(); + fss = null; +} +``` + +For even faster splash screen, disable the XAML generated main method by defining the `DISABLE_XAML_GENERATED_MAIN` preprocessor directive +and instead defining your own start method. You'll then be able to display the splash screen as the very first thing before the application is created. For example: + +```cs +#if DISABLE_XAML_GENERATED_MAIN + public static class Program + { + [System.STAThreadAttribute] + static void Main(string[] args) + { + // If you're using the WebAuthenticator, make sure you call this method first before the splashscreen shows + if (WebAuthenticator.CheckOAuthRedirectionActivation(true)) + return; + var fss = SimpleSplashScreen.ShowDefaultSplashScreen(); + WinRT.ComWrappersSupport.InitializeComWrappers(); + Microsoft.UI.Xaml.Application.Start((p) => { + var context = new Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext(Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread()); + System.Threading.SynchronizationContext.SetSynchronizationContext(context); + new App(fss); // Pass the splash screen to your app so it can close it on activation + }); + } + } +#endif +``` + +### SplashScreen + To create a new splash screen, first create a new WinUI Window. -Next change the baseclass from "Window" to SplashScreen: +Next change the baseclass from `Window` to `SplashScreen`: Before: ```xml @@ -21,52 +87,52 @@ Before: ```cs public sealed partial class SplashScreen : Window { - public SplashScreen() - { - this.InitializeComponent(); - } + public SplashScreen() + { + this.InitializeComponent(); + } } ``` After: ```cs public sealed partial class SplashScreen : WinUIEx.SplashScreen { - public SplashScreen(Type window) : base(window) - { - this.InitializeComponent(); - } + public SplashScreen(Type window) : base(window) + { + this.InitializeComponent(); + } } ``` Next in App.xaml.cs, change OnLaunched from: ```cs - protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) - { - m_window = new MainWindow(); - m_window.Activate(); - } +protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) +{ + m_window = new MainWindow(); + m_window.Activate(); +} ``` To: ```cs - protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) - { - var splash = new SplashScreen(typeof(MainWindow)); - splash.Completed += (s, e) => m_window = e; - } +protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) +{ + var splash = new SplashScreen(typeof(MainWindow)); + splash.Completed += (s, e) => m_window = e; +} ``` Lastly, override OnLoading, to create some long-running setup work - once this method completes, the splash screen will close and the window in the type parameter will launch. Example: -``` - protected override async Task OnLoading() - { - //TODO: Do some actual work - for (int i = 0; i < 100; i+=5) - { - statusText.Text = $"Loading {i}%..."; - progressBar.Value = i; - await Task.Delay(50); - } - } +```cs +protected override async Task OnLoading() +{ + //TODO: Do some actual work + for (int i = 0; i < 100; i+=5) + { + statusText.Text = $"Loading {i}%..."; + progressBar.Value = i; + await Task.Delay(50); + } +} ``` \ No newline at end of file diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index cc23f14..9a1a617 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -17,7 +17,7 @@ Morten Nielsen - https://xaml.dev Morten Nielsen - https://xaml.dev logo.png - 2.4.2 + 2.5.0 diff --git a/src/WinUIEx/NativeMethods.txt b/src/WinUIEx/NativeMethods.txt index c2b8722..39ed06f 100644 --- a/src/WinUIEx/NativeMethods.txt +++ b/src/WinUIEx/NativeMethods.txt @@ -54,4 +54,15 @@ DwmExtendFrameIntoClientArea CreateRectRgn CreateSolidBrush FillRect -GetDC \ No newline at end of file +GetDC +ReleaseDC +GdiplusStartup +GdiplusShutdown +GetObject +GdipCreateBitmapFromFile +GdipCreateHBITMAPFromBitmap +WNDCLASSEXW +RegisterClassEx +CreateCompatibleDC +SelectObject +MonitorFromPoint diff --git a/src/WinUIEx/SimpleSplashScreen.cs b/src/WinUIEx/SimpleSplashScreen.cs new file mode 100644 index 0000000..148a8e9 --- /dev/null +++ b/src/WinUIEx/SimpleSplashScreen.cs @@ -0,0 +1,381 @@ +// Based on an implementation by Castorix: https://github.com/castorix/WinUI3_SplashScreen + +//#define MEDIAPLAYER +using Microsoft.UI.Xaml; +using System; +using System.Runtime.InteropServices; +using System.Xml.Linq; +using System.Xml.XPath; + +#if MEDIAPLAYER +using MFPlay; +#endif +using Windows.Win32; +using Windows.Win32.UI.WindowsAndMessaging; + +namespace WinUIEx +{ + /// + /// Simple and fast splash screen for display images during application startup. + /// + /// + /// Once your application window has launched/loaded, The splashscreen should be removed by either disposing this instance or calling . + /// + /// + public sealed class SimpleSplashScreen : IDisposable + { + private const nint COLOR_BACKGROUND = 1; + + private DispatcherTimer? dTimer; + private TimeSpan tsFadeoutDuration; + private DateTime tsFadeoutEnd; + Windows.Win32.Foundation.HWND hWndSplash = Windows.Win32.Foundation.HWND.Null; + private Windows.Win32.Graphics.Gdi.HGDIOBJ hBitmap = Windows.Win32.Graphics.Gdi.HGDIOBJ.Null; + private nuint initToken = 0; +#if MEDIAPLAYER + private MFPlayer? mediaPlayer; +#endif + + /// + /// Shows the splashscreen image specified in the application manifest. + /// + /// + /// + public static SimpleSplashScreen ShowDefaultSplashScreen() + { + var image = GetManifestSplashScreen(); + if (image is null) + throw new InvalidOperationException("SplashScreen section not found in AppxManifest.xml"); + var manager = new Microsoft.Windows.ApplicationModel.Resources.ResourceManager(); + var context = manager.CreateResourceContext(); + + uint dpi = 96; + var monitor = PInvoke.MonitorFromPoint(new System.Drawing.Point(0, 0), Windows.Win32.Graphics.Gdi.MONITOR_FROM_FLAGS.MONITOR_DEFAULTTOPRIMARY); + + if(monitor != IntPtr.Zero) + { + PInvoke.GetDpiForMonitor(monitor, Windows.Win32.UI.HiDpi.MONITOR_DPI_TYPE.MDT_EFFECTIVE_DPI, out var dpiX, out var dpiy); + dpi = dpiX; + } + var scale = (int)(dpi / 96d * 100); + if (scale == 0) scale = 100; + context.QualifierValues["Scale"] = scale.ToString(); + var splashScreenImageResource = manager.MainResourceMap.TryGetValue("Files/" + image.Replace('\\','/'), context); + if (splashScreenImageResource is not null && splashScreenImageResource.Kind == Microsoft.Windows.ApplicationModel.Resources.ResourceCandidateKind.FilePath) + { + return SimpleSplashScreen.ShowSplashScreenImage(splashScreenImageResource.ValueAsString); + } + throw new InvalidOperationException("Splash screen image not found in resources"); + } + + private static string? GetManifestSplashScreen() + { + var rootFolder = Windows.ApplicationModel.Package.Current?.InstalledLocation.Path ?? AppContext.BaseDirectory; + var docPath = System.IO.Path.Combine(rootFolder, "AppxManifest.xml"); + if (!System.IO.File.Exists(docPath)) + throw new System.IO.FileNotFoundException("AppxManifest.xml not found"); + + var doc = XDocument.Load(docPath, LoadOptions.None); + var reader = doc.CreateReader(); + var namespaceManager = new System.Xml.XmlNamespaceManager(reader.NameTable); + namespaceManager.AddNamespace("x", "http://schemas.microsoft.com/appx/manifest/foundation/windows10"); + namespaceManager.AddNamespace("uap", "http://schemas.microsoft.com/appx/manifest/uap/windows10"); + + var element = doc.Root?.XPathSelectElement("/x:Package/x:Applications/x:Application/uap:VisualElements/uap:SplashScreen", namespaceManager); + return element?.Attribute(XName.Get("Image"))?.Value; + } + + /// + /// Shows a splash screen image at the center of the screen + /// + /// + /// + public static SimpleSplashScreen ShowSplashScreenImage(string image) + { + var s = new SimpleSplashScreen(); + s.Initialize(); + var hBitmap = s.GetBitmap(image); + s.DisplaySplash(Windows.Win32.Foundation.HWND.Null, hBitmap, null); + return s; + } +#if MEDIAPLAYER + public static SimpleSplashScreen ShowSplashScreenVideo(string video) + { + var s = new SimpleSplashScreen(); + s.Initialize(); + s.DisplaySplash(Windows.Win32.Foundation.HWND.Null, Windows.Win32.Graphics.Gdi.HBITMAP.Null, video); + return s; + } +#endif + private void Initialize() + { + var input = new Windows.Win32.Graphics.GdiPlus.GdiplusStartupInput() + { + GdiplusVersion = 1, + SuppressBackgroundThread = false, + SuppressExternalCodecs = false + }; + var output = new Windows.Win32.Graphics.GdiPlus.GdiplusStartupOutput(); + var nStatus = PInvoke.GdiplusStartup(ref initToken, input, ref output); + } + + /// + public void Dispose() + { + Dispose(true); + } + + private void Dispose(bool disposing) + { + CleanUp(); + if (disposing) + { +#if MEDIAPLAYER + mediaPlayer.Dispose(); +#endif + } + } + + private void CleanUp() + { + if (hWndSplash != IntPtr.Zero) + { + PInvoke.DestroyWindow(hWndSplash); + } + if (hBitmap != IntPtr.Zero) + { + PInvoke.DeleteObject(new Windows.Win32.Graphics.Gdi.HGDIOBJ(hBitmap)); + hBitmap = Windows.Win32.Graphics.Gdi.HGDIOBJ.Null; + } + PInvoke.GdiplusShutdown(initToken); + } + + /// + /// Finalizer + /// + ~SimpleSplashScreen() => Dispose(false); + + private unsafe void DisplaySplash(Windows.Win32.Foundation.HWND hWnd, Windows.Win32.Graphics.Gdi.HBITMAP bitmap, string? sVideo) + { + Windows.Win32.UI.WindowsAndMessaging.WNDCLASSEXW wcex; + this.hBitmap = bitmap; + wcex.cbSize = (uint)Marshal.SizeOf(typeof(WNDCLASSEXW)); + wcex.style = WNDCLASS_STYLES.CS_HREDRAW | WNDCLASS_STYLES.CS_VREDRAW | WNDCLASS_STYLES.CS_DBLCLKS; + wcex.hbrBackground = new Windows.Win32.Graphics.Gdi.HBRUSH(COLOR_BACKGROUND + 1); + wcex.cbClsExtra = 0; + wcex.cbWndExtra = 0; + wcex.hInstance = new Windows.Win32.Foundation.HINSTANCE(Marshal.GetHINSTANCE(this.GetType().Module)); + wcex.hIcon = HICON.Null; + wcex.hCursor = HCURSOR.Null; + wcex.lpszMenuName = null; + string sClassName = "Win32Class"; + fixed (char* name = sClassName) + wcex.lpszClassName = name; + wcex.lpfnWndProc = &Win32WndProc; + + wcex.hIconSm = HICON.Null; + ushort nRet = PInvoke.RegisterClassEx(wcex); + if (nRet == 0) + { + int nError = Marshal.GetLastWin32Error(); + if (nError != 1410) //0x582 ERROR_CLASS_ALREADY_EXISTS + return; + } + int nWidth = 0, nHeight = 0; + if (hBitmap != IntPtr.Zero) + { + BITMAP bm; + PInvoke.GetObject(new DeleteObjectSafeHandle(hBitmap), Marshal.SizeOf(typeof(BITMAP)), &bm); + nWidth = bm.bmWidth; + nHeight = bm.bmHeight; + } + + hWndSplash = PInvoke.CreateWindowEx(WINDOW_EX_STYLE.WS_EX_TOOLWINDOW | WINDOW_EX_STYLE.WS_EX_LAYERED | WINDOW_EX_STYLE.WS_EX_TRANSPARENT | WINDOW_EX_STYLE.WS_EX_TOPMOST, + sClassName, "Win32 window", WINDOW_STYLE.WS_POPUP | WINDOW_STYLE.WS_VISIBLE, 400, 400, nWidth, nHeight, hWnd, null, + new DestroyIconSafeHandle(wcex.hInstance), null); + if (hBitmap != IntPtr.Zero) + { + CenterToScreen(hWndSplash); + SetPictureToLayeredWindow(hWndSplash, hBitmap); + } +#if MEDIAPLAYER + if (sVideo != null) + { + mediaPlayer = new MFPlayer(this, hWndSplash, sVideo); + } +#endif + } + [StructLayoutAttribute(LayoutKind.Sequential)] + private struct BITMAP + { + public int bmType; + public int bmWidth; + public int bmHeight; + public int bmWidthBytes; + public short bmPlanes; + public short bmBitsPixel; + public IntPtr bmBits; + } + +#if MEDIAPLAYER + private class MFPlayer : IMFPMediaPlayerCallback, IDisposable + { + private readonly SimpleSplashScreen m_ss; + private readonly Windows.Win32.Foundation.HWND m_hWndParent; + private readonly IMFPMediaPlayer m_pMediaPlayer; + + internal unsafe MFPlayer(SimpleSplashScreen ss, Windows.Win32.Foundation.HWND hWnd, string sVideo) + { + GlobalStructures.HRESULT hr = MFPlayTools.MFPCreateMediaPlayer(sVideo, false, MFPlay.MFPlayTools.MFP_CREATION_OPTIONS.MFP_OPTION_NONE, this, hWnd, out m_pMediaPlayer); + m_hWndParent = hWnd; + m_ss = ss; + } + + void IMFPMediaPlayerCallback.OnMediaPlayerEvent(MFP_EVENT_HEADER pEventHeader) + { + switch (pEventHeader.eEventType) + { + case MFP_EVENT_TYPE.MFP_EVENT_TYPE_MEDIAITEM_SET: + { + GlobalStructures.SIZE szVideo, szARVideo; + GlobalStructures.HRESULT hr = m_pMediaPlayer.GetNativeVideoSize(out szVideo, out szARVideo); + if (hr == GlobalStructures.HRESULT.S_OK) + { + PInvoke.SetWindowPos(m_hWndParent, Windows.Win32.Foundation.HWND.Null, 0, 0, szVideo.cx, szVideo.cy, SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); + m_ss.CenterToScreen(m_hWndParent); + hr = m_pMediaPlayer.Play(); + } + } + break; + case MFP_EVENT_TYPE.MFP_EVENT_TYPE_PLAYBACK_ENDED: + { + PlaybackEnded?.Invoke(this, EventArgs.Empty); + } + break; + } + return; + } + + ~MFPlayer() => Dispose(false); + + public void Dispose() => Dispose(true); + + private void Dispose(bool disposing) => m_pMediaPlayer.Shutdown(); + + public event EventHandler PlaybackEnded; + } +#endif + + /// + /// Hides the splashscreen + /// + /// Time spend fading out the splash screen (defaults to no fade) + public void Hide(TimeSpan fadeTimout = default) + { + if (fadeTimout.Ticks <= 0) + CleanUp(); + else + { + dTimer = new DispatcherTimer(); + dTimer.Interval = TimeSpan.FromMilliseconds(16); + tsFadeoutDuration = fadeTimout; + tsFadeoutEnd = DateTime.UtcNow + tsFadeoutDuration; + dTimer.Tick += FadeTimer_Tick; + dTimer.Start(); + } + } + + private unsafe Windows.Win32.Graphics.Gdi.HBITMAP GetBitmap(string sBitmapFile) + { + var bitmap = new Windows.Win32.Graphics.GdiPlus.GpBitmap(); + var pBitmap = &bitmap; + + var nStatus = PInvoke.GdipCreateBitmapFromFile(sBitmapFile, ref pBitmap); + + if (nStatus == Windows.Win32.Graphics.GdiPlus.Status.Ok) + { + var hBitmap = new Windows.Win32.Graphics.Gdi.HBITMAP(); + PInvoke.GdipCreateHBITMAPFromBitmap(pBitmap, &hBitmap, (uint)System.Drawing.ColorTranslator.ToWin32(System.Drawing.Color.FromArgb(0))); + return hBitmap; + } + throw new InvalidOperationException("Failed to open bitmap: " + nStatus.ToString()); + } + + private void FadeTimer_Tick(object? sender, object e) + { + DateTime dtNow = DateTime.UtcNow; + if (dtNow >= tsFadeoutEnd) + { + if (dTimer != null) + { + dTimer.Stop(); + dTimer = null; + } + CleanUp(); + } + else + { + double nProgress = (tsFadeoutEnd - dtNow).TotalMilliseconds / tsFadeoutDuration.TotalMilliseconds; + var bf = new Windows.Win32.Graphics.Gdi.BLENDFUNCTION() + { + BlendOp = AC_SRC_OVER, + SourceConstantAlpha = (byte)(255 * nProgress), + AlphaFormat = AC_SRC_ALPHA + }; + + PInvoke.UpdateLayeredWindow(hWndSplash, + new Windows.Win32.Graphics.Gdi.HDC(0), null, null, new Windows.Win32.Graphics.Gdi.HDC(0), (System.Drawing.Point?)null, new Windows.Win32.Foundation.COLORREF(0), bf, UPDATE_LAYERED_WINDOW_FLAGS.ULW_ALPHA); + } + } + + private const byte AC_SRC_OVER = 0x00; + private const byte AC_SRC_ALPHA = 0x01; + + private unsafe void SetPictureToLayeredWindow(Windows.Win32.Foundation.HWND hWnd, IntPtr hBitmap) + { + BITMAP bm; + Windows.Win32.Graphics.Gdi.HBITMAP bitmap = new Windows.Win32.Graphics.Gdi.HBITMAP(hBitmap); + PInvoke.GetObject(new Windows.Win32.DeleteObjectSafeHandle(hBitmap), Marshal.SizeOf(typeof(BITMAP)), &bm); + System.Drawing.Size sizeBitmap = new System.Drawing.Size(bm.bmWidth, bm.bmHeight); + + var hDCScreen = PInvoke.GetDC(Windows.Win32.Foundation.HWND.Null); + var hDCMem = PInvoke.CreateCompatibleDC(hDCScreen); + using var hBitmapOld = PInvoke.SelectObject(hDCMem, new DestroyIconSafeHandle(hBitmap)); + + var bf = new Windows.Win32.Graphics.Gdi.BLENDFUNCTION() + { + BlendOp = AC_SRC_OVER, + SourceConstantAlpha = 255, + AlphaFormat = AC_SRC_ALPHA + }; + PInvoke.GetWindowRect(hWnd, out var rectWnd); + + System.Drawing.Point ptSrc = new System.Drawing.Point(); + System.Drawing.Point ptDest = new System.Drawing.Point(rectWnd.left, rectWnd.top); + + bool bRet = PInvoke.UpdateLayeredWindow(hWnd, + hDCScreen, ptDest, new Windows.Win32.Foundation.SIZE(sizeBitmap.Width, sizeBitmap.Height), + hDCMem, ptSrc, new Windows.Win32.Foundation.COLORREF(0), bf, UPDATE_LAYERED_WINDOW_FLAGS.ULW_ALPHA); + + PInvoke.ReleaseDC(Windows.Win32.Foundation.HWND.Null, hDCScreen); + } + + private unsafe void CenterToScreen(IntPtr hWnd) + { + var rcWorkArea = new Windows.Win32.Foundation.RECT(); + PInvoke.SystemParametersInfo(SYSTEM_PARAMETERS_INFO_ACTION.SPI_GETWORKAREA, 0, &rcWorkArea, 0); + var h = new Windows.Win32.Foundation.HWND(hWnd); + PInvoke.GetWindowRect(h, out Windows.Win32.Foundation.RECT rc); + int nX = System.Convert.ToInt32((rcWorkArea.left + rcWorkArea.right) / (double)2 - (rc.right - rc.left) / (double)2); + int nY = System.Convert.ToInt32((rcWorkArea.top + rcWorkArea.bottom) / (double)2 - (rc.bottom - rc.top) / (double)2); + PInvoke.SetWindowPos(h, Windows.Win32.Foundation.HWND.Null, nX, nY, -1, -1, SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_FRAMECHANGED | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); + } + + [UnmanagedCallersOnly(CallConvs = new Type[] { typeof(System.Runtime.CompilerServices.CallConvStdcall) })] + private static Windows.Win32.Foundation.LRESULT Win32WndProc(Windows.Win32.Foundation.HWND hwnd, + uint msg, Windows.Win32.Foundation.WPARAM wParam, Windows.Win32.Foundation.LPARAM lParam) + { + return PInvoke.DefWindowProc(hwnd, msg, wParam, lParam); + } + } +} diff --git a/src/WinUIEx/SplashScreen.cs b/src/WinUIEx/SplashScreen.cs index 3cf1b6d..5caa33a 100644 --- a/src/WinUIEx/SplashScreen.cs +++ b/src/WinUIEx/SplashScreen.cs @@ -7,9 +7,10 @@ namespace WinUIEx { /// - /// A splash screen window that shows with no chrome, and once has completed, - /// opens a new window + /// A splash screen window for rendering XAML that shows with no chrome, and once has completed, + /// opens a new window. /// + /// public class SplashScreen : Window { private Window? _window; diff --git a/src/WinUIEx/WinUIEx.csproj b/src/WinUIEx/WinUIEx.csproj index 28e6d66..51350d9 100644 --- a/src/WinUIEx/WinUIEx.csproj +++ b/src/WinUIEx/WinUIEx.csproj @@ -25,7 +25,7 @@ WinUIEx WinUI Extensions - - Workaround package bug in WebView2 nuget package. (Issue #188) + - Added `SimpleSplashScreen` for display application launch splash screens @@ -37,7 +37,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/src/WinUIExSample/App.xaml.cs b/src/WinUIExSample/App.xaml.cs index 5ba3758..db5a56b 100644 --- a/src/WinUIExSample/App.xaml.cs +++ b/src/WinUIExSample/App.xaml.cs @@ -23,8 +23,11 @@ public partial class App : Application /// public App() { +#if !DISABLE_XAML_GENERATED_MAIN // With custom main, we'd rather want to do this code in main if (WebAuthenticator.CheckOAuthRedirectionActivation()) return; + fss = SimpleSplashScreen.ShowDefaultSplashScreen(); +#endif this.InitializeComponent(); int length = 0; var sb = new System.Text.StringBuilder(0); @@ -34,8 +37,9 @@ public App() // Not a packaged app. Configure file-based persistence instead WinUIEx.WindowManager.PersistenceStorage = new FilePersistence("WinUIExPersistence.json"); } + } - + internal SimpleSplashScreen fss { get; set; } /// /// Invoked when the application is launched normally by the end user. Other entry points /// will be used such as when the application is launched to open a specific file. @@ -45,7 +49,18 @@ protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs ar { var window = new MainWindow(); var splash = new SplashScreen(window); - splash.Completed += (s, e) => m_window = (WindowEx)e; + splash.Completed += (s, e) => + { + m_window = (WindowEx)e; + }; + splash.Activated += Splash_Activated; + } + + private void Splash_Activated(object sender, WindowActivatedEventArgs args) + { + ((Window)sender).Activated -= Splash_Activated; + fss?.Hide(TimeSpan.FromSeconds(1)); + fss = null; } private WindowEx m_window; @@ -129,4 +144,26 @@ public void Clear() IEnumerator IEnumerable.GetEnumerator() => throw new NotImplementedException(); // TODO } } + +#if DISABLE_XAML_GENERATED_MAIN + /// + /// Program class + /// + public static class Program + { + [global::System.STAThreadAttribute] + static void Main(string[] args) + { + if (WebAuthenticator.CheckOAuthRedirectionActivation(true)) + return; + var fss = SimpleSplashScreen.ShowDefaultSplashScreen(); + global::WinRT.ComWrappersSupport.InitializeComWrappers(); + global::Microsoft.UI.Xaml.Application.Start((p) => { + var context = new global::Microsoft.UI.Dispatching.DispatcherQueueSynchronizationContext(global::Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread()); + global::System.Threading.SynchronizationContext.SetSynchronizationContext(context); + new App() { fss = fss }; + }); + } + } +#endif } diff --git a/src/WinUIExSample/WinUIExSample.csproj b/src/WinUIExSample/WinUIExSample.csproj index 5b2bd76..258cacb 100644 --- a/src/WinUIExSample/WinUIExSample.csproj +++ b/src/WinUIExSample/WinUIExSample.csproj @@ -17,6 +17,7 @@ 10.0.22621.38 + DISABLE_XAML_GENERATED_MAIN;$(DefineConstants) true From 3a255aa9591dce279c497c982e66f722041ffe1c Mon Sep 17 00:00:00 2001 From: Andrew KeepCoding Date: Tue, 19 Nov 2024 13:18:43 +0900 Subject: [PATCH 2/2] Fix sample code on Splashscreen.md (#197) --- docs/concepts/Splashscreen.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/concepts/Splashscreen.md b/docs/concepts/Splashscreen.md index 965239f..dbf47dc 100644 --- a/docs/concepts/Splashscreen.md +++ b/docs/concepts/Splashscreen.md @@ -10,7 +10,7 @@ WinUIEx provides two kinds of splash screens: `SimpleSplashScreen` will show an image while the app is loading. To use it, create a new `SimpleSplashScreen` in App.xaml.cs: ```cs -private SimpleSplashScreen vss { get; set; } +private SimpleSplashScreen fss { get; set; } public App() { @@ -135,4 +135,4 @@ protected override async Task OnLoading() await Task.Delay(50); } } -``` \ No newline at end of file +```