From e9ee31a6045504841773e20274f51a3a198baf1d Mon Sep 17 00:00:00 2001 From: DismissedLight <1686188646@qq.com> Date: Mon, 17 Jul 2023 17:14:15 +0800 Subject: [PATCH] refactor controls --- .../Control/Behavior/AutoHeightBehavior.cs | 11 ++- .../Control/Behavior/AutoWidthBehavior.cs | 11 ++- ...dsContentIntoTitleBarWorkaroundBehavior.cs | 16 ++-- .../Behavior/OpenAttachedFlyoutAction.cs | 2 +- .../Snap.Hutao/Control/BoxedValues.cs | 2 +- .../Extension/ContentDialogExtension.cs | 2 +- .../Extension/ContentDialogHideToken.cs | 26 +++++- .../Extension/DependencyObjectExtension.cs | 14 ++++ .../Extension/FrameworkElementExtension.cs | 31 +++++++ .../Snap.Hutao/Control/Image/CachedImage.cs | 14 +--- .../Control/Image/CompositionImage.cs | 81 +++++++++++++------ .../Snap.Hutao/Control/Image/Gradient.cs | 10 +-- .../Snap.Hutao/Control/Image/MonoChrome.cs | 16 +++- .../Control/Panel/PanelSelector.xaml | 9 +-- .../Control/Panel/PanelSelector.xaml.cs | 71 +++++++++++----- src/Snap.Hutao/Snap.Hutao/Control/Property.cs | 1 + .../Snap.Hutao/Control/ScopedPage.cs | 34 ++++---- .../Control/Text/DescriptionTextBlock.cs | 38 +++++---- .../Snap.Hutao/Control/Theme/FontStyle.xaml | 2 +- .../Snap.Hutao/Control/ValueConverter.cs | 4 +- .../Snap.Hutao/Core/Caching/ImageCache.cs | 25 ++---- .../Core/Database/ScopedDbCurrent.cs | 1 + .../Core/ExceptionService/ExceptionFormat.cs | 8 +- .../Core/Windowing/ExtendedWindow.cs | 4 +- ...essage.cs => FlyoutStateChangedMessage.cs} | 9 ++- 25 files changed, 292 insertions(+), 150 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Extension/DependencyObjectExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Control/Extension/FrameworkElementExtension.cs rename src/Snap.Hutao/Snap.Hutao/Message/{FlyoutOpenCloseMessage.cs => FlyoutStateChangedMessage.cs} (66%) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs index 10e84fe0..74f4c9c5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoHeightBehavior.cs @@ -14,17 +14,24 @@ namespace Snap.Hutao.Control.Behavior; [DependencyProperty("TargetHeight", typeof(double), 1.0D)] internal sealed partial class AutoHeightBehavior : BehaviorBase { + private readonly SizeChangedEventHandler sizeChangedEventHandler; + + public AutoHeightBehavior() + { + sizeChangedEventHandler = OnSizeChanged; + } + /// protected override void OnAssociatedObjectLoaded() { UpdateElement(); - AssociatedObject.SizeChanged += OnSizeChanged; + AssociatedObject.SizeChanged += sizeChangedEventHandler; } /// protected override void OnDetaching() { - AssociatedObject.SizeChanged -= OnSizeChanged; + AssociatedObject.SizeChanged -= sizeChangedEventHandler; base.OnDetaching(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs index 793055c0..e566e69e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/AutoWidthBehavior.cs @@ -14,17 +14,24 @@ namespace Snap.Hutao.Control.Behavior; [DependencyProperty("TargetHeight", typeof(double), 1.0D)] internal sealed partial class AutoWidthBehavior : BehaviorBase { + private readonly SizeChangedEventHandler sizeChangedEventHandler; + + public AutoWidthBehavior() + { + sizeChangedEventHandler = OnSizeChanged; + } + /// protected override void OnAssociatedObjectLoaded() { UpdateElement(); - AssociatedObject.SizeChanged += OnSizeChanged; + AssociatedObject.SizeChanged += sizeChangedEventHandler; } /// protected override void OnDetaching() { - AssociatedObject.SizeChanged -= OnSizeChanged; + AssociatedObject.SizeChanged -= sizeChangedEventHandler; base.OnDetaching(); } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs index 39ceb65a..52b3fdff 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs @@ -14,6 +14,8 @@ namespace Snap.Hutao.Control.Behavior; internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase { private readonly IMessenger messenger; + private readonly EventHandler dropDownOpenedHandler; + private readonly EventHandler dropDownClosedHandler; /// /// AppTitleBar Workaround @@ -21,31 +23,33 @@ internal sealed class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : Beh public ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior() { messenger = Ioc.Default.GetRequiredService(); + dropDownOpenedHandler = OnDropDownOpened; + dropDownClosedHandler = OnDropDownClosed; } /// protected override void OnAssociatedObjectLoaded() { - AssociatedObject.DropDownOpened += OnDropDownOpened; - AssociatedObject.DropDownClosed += OnDropDownClosed; + AssociatedObject.DropDownOpened += dropDownOpenedHandler; + AssociatedObject.DropDownClosed += dropDownClosedHandler; } /// protected override void OnDetaching() { - AssociatedObject.DropDownOpened -= OnDropDownOpened; - AssociatedObject.DropDownClosed -= OnDropDownClosed; + AssociatedObject.DropDownOpened -= dropDownOpenedHandler; + AssociatedObject.DropDownClosed -= dropDownClosedHandler; base.OnDetaching(); } private void OnDropDownOpened(object? sender, object e) { - messenger.Send(new Message.FlyoutOpenCloseMessage(true)); + messenger.Send(Message.FlyoutStateChangedMessage.Open); } private void OnDropDownClosed(object? sender, object e) { - messenger.Send(new Message.FlyoutOpenCloseMessage(false)); + messenger.Send(Message.FlyoutStateChangedMessage.Close); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/OpenAttachedFlyoutAction.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/OpenAttachedFlyoutAction.cs index d8eb0670..89d3281e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/OpenAttachedFlyoutAction.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/OpenAttachedFlyoutAction.cs @@ -17,6 +17,6 @@ internal sealed class OpenAttachedFlyoutAction : DependencyObject, IAction public object Execute(object sender, object parameter) { FlyoutBase.ShowAttachedFlyout(sender as FrameworkElement); - return null!; + return default!; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs b/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs index 26b6d7cf..f705497a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/BoxedValues.cs @@ -15,7 +15,7 @@ internal static class BoxedValues public static readonly object DoubleZero = 0D; /// - /// 0 + /// 1 /// public static readonly object DoubleOne = 1D; diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs index 65977650..4d85edba 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogExtension.cs @@ -20,10 +20,10 @@ internal static class ContentDialogExtension public static async ValueTask BlockAsync(this ContentDialog contentDialog, ITaskContext taskContext) { await taskContext.SwitchToMainThreadAsync(); - contentDialog.ShowAsync().AsTask().SafeForget(); // E_ASYNC_OPERATION_NOT_STARTED 0x80000019 // Only a single ContentDialog can be open at any time. + contentDialog.ShowAsync().AsTask().SafeForget(); return new ContentDialogHideToken(contentDialog, taskContext); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs index c955bbe1..0e938e27 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/ContentDialogHideToken.cs @@ -5,11 +5,14 @@ using Microsoft.UI.Xaml.Controls; namespace Snap.Hutao.Control.Extension; -internal readonly struct ContentDialogHideToken : IDisposable +internal struct ContentDialogHideToken : IDisposable, IAsyncDisposable { private readonly ContentDialog contentDialog; private readonly ITaskContext taskContext; + private bool disposed = false; + private bool disposing = false; + public ContentDialogHideToken(ContentDialog contentDialog, ITaskContext taskContext) { this.contentDialog = contentDialog; @@ -18,7 +21,24 @@ internal readonly struct ContentDialogHideToken : IDisposable public void Dispose() { - // Hide() must be called on main thread. - taskContext.InvokeOnMainThread(contentDialog.Hide); + if (!disposed && !disposing) + { + disposing = true; + taskContext.InvokeOnMainThread(contentDialog.Hide); // Hide() must be called on main thread. + disposing = false; + disposed = true; + } + } + + public async ValueTask DisposeAsync() + { + if (!disposed && !disposing) + { + disposing = true; + await taskContext.SwitchToMainThreadAsync(); + contentDialog.Hide(); + disposing = false; + disposed = true; + } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/DependencyObjectExtension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/DependencyObjectExtension.cs new file mode 100644 index 00000000..87e29747 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/DependencyObjectExtension.cs @@ -0,0 +1,14 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml; + +namespace Snap.Hutao.Control.Extension; + +internal static class DependencyObjectExtension +{ + public static IServiceProvider ServiceProvider(this DependencyObject obj) + { + return Ioc.Default; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Extension/FrameworkElementExtension.cs b/src/Snap.Hutao/Snap.Hutao/Control/Extension/FrameworkElementExtension.cs new file mode 100644 index 00000000..358294f7 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Extension/FrameworkElementExtension.cs @@ -0,0 +1,31 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml; + +namespace Snap.Hutao.Control.Extension; + +internal static class FrameworkElementExtension +{ + /// + /// Make properties below false: + /// + /// * AllowFocusOnInteraction + /// * IsDoubleTapEnabled + /// * IsHitTestVisible + /// * IsHoldingEnabled + /// * IsRightTapEnabled + /// * IsTabStop + /// + /// + /// 元素 + public static void DisableInteraction(this FrameworkElement frameworkElement) + { + frameworkElement.AllowFocusOnInteraction = false; + frameworkElement.IsDoubleTapEnabled = false; + frameworkElement.IsHitTestVisible = false; + frameworkElement.IsHoldingEnabled = false; + frameworkElement.IsRightTapEnabled = false; + frameworkElement.IsTabStop = false; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs index 412dd581..f89e1ffc 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CachedImage.cs @@ -32,16 +32,10 @@ internal sealed class CachedImage : ImageEx try { - Verify.Operation(imageUri.Host != string.Empty, SH.ControlImageCachedImageInvalidResourceUri); - - // BitmapImage need to be created by main thread. - string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); - - // check token state to determine whether the operation should be canceled. - token.ThrowIfCancellationRequested(); - - // BitmapImage initialize with a uri will increase image quality and loading speed. - return new BitmapImage(file.ToUri()); + Verify.Operation(!string.IsNullOrEmpty(imageUri.Host), SH.ControlImageCachedImageInvalidResourceUri); + string file = await imageCache.GetFileFromCacheAsync(imageUri).ConfigureAwait(true); // BitmapImage need to be created by main thread. + token.ThrowIfCancellationRequested(); // check token state to determine whether the operation should be canceled. + return new BitmapImage(file.ToUri()); // BitmapImage initialize with a uri will increase image quality and loading speed. } catch (COMException) { diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs index 7b54f361..c9828b7f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs @@ -7,11 +7,13 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Hosting; using Microsoft.UI.Xaml.Media; using Snap.Hutao.Control.Animation; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Core.Caching; using Snap.Hutao.Service.Notification; using System.IO; using System.Net.Http; using System.Runtime.InteropServices; +using Windows.Foundation; namespace Snap.Hutao.Control.Image; @@ -28,6 +30,11 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co private readonly IServiceProvider serviceProvider; + private readonly RoutedEventHandler unloadEventHandler; + private readonly SizeChangedEventHandler sizeChangedEventHandler; + private readonly TypedEventHandler loadedImageSourceLoadCompletedEventHandler; + + private TaskCompletionSource? surfaceLoadTaskCompletionSource; private SpriteVisual? spriteVisual; private bool isShow = true; @@ -36,16 +43,16 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co /// protected CompositionImage() { - serviceProvider = Ioc.Default; + serviceProvider = this.ServiceProvider(); + this.DisableInteraction(); - AllowFocusOnInteraction = false; - IsDoubleTapEnabled = false; - IsHitTestVisible = false; - IsHoldingEnabled = false; - IsRightTapEnabled = false; - IsTabStop = false; + unloadEventHandler = OnUnload; + Unloaded += unloadEventHandler; - SizeChanged += OnSizeChanged; + sizeChangedEventHandler = OnSizeChanged; + SizeChanged += sizeChangedEventHandler; + + loadedImageSourceLoadCompletedEventHandler = OnLoadImageSurfaceLoadCompleted; } /// @@ -65,26 +72,19 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co /// 拼合视觉 protected abstract SpriteVisual CompositeSpriteVisual(Compositor compositor, LoadedImageSurface imageSurface); - /// - /// 异步加载图像表面 - /// - /// 文件 - /// 取消令牌 - /// 加载的图像表面 - protected virtual async Task LoadImageSurfaceAsync(string file, CancellationToken token) + protected virtual void LoadImageSurfaceCompleted(LoadedImageSurface surface) + { + } + + protected virtual void Unloading() { - TaskCompletionSource loadCompleteTaskSource = new(); - LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri()); - surface.LoadCompleted += (s, e) => loadCompleteTaskSource.TrySetResult(); - await loadCompleteTaskSource.Task.ConfigureAwait(true); - return surface; } /// /// 更新视觉对象 /// /// 拼合视觉 - protected virtual void OnUpdateVisual(SpriteVisual spriteVisual) + protected virtual void UpdateVisual(SpriteVisual spriteVisual) { spriteVisual.Size = ActualSize; } @@ -158,15 +158,28 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co if (imageSurface != null) { - spriteVisual = CompositeSpriteVisual(compositor, imageSurface); - OnUpdateVisual(spriteVisual); + using (imageSurface) + { + spriteVisual = CompositeSpriteVisual(compositor, imageSurface); + UpdateVisual(spriteVisual); - ElementCompositionPreview.SetElementChildVisual(this, spriteVisual); - await ShowAsync(token).ConfigureAwait(true); + ElementCompositionPreview.SetElementChildVisual(this, spriteVisual); + await ShowAsync(token).ConfigureAwait(true); + } } } } + private async Task LoadImageSurfaceAsync(string file, CancellationToken token) + { + surfaceLoadTaskCompletionSource = new(); + LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri()); + surface.LoadCompleted += loadedImageSourceLoadCompletedEventHandler; + await surfaceLoadTaskCompletionSource.Task.ConfigureAwait(true); + LoadImageSurfaceCompleted(surface); + return surface; + } + private async Task ShowAsync(CancellationToken token) { if (!isShow) @@ -209,11 +222,27 @@ internal abstract partial class CompositionImage : Microsoft.UI.Xaml.Controls.Co } } + private void OnLoadImageSurfaceLoadCompleted(LoadedImageSurface surface, LoadedImageSourceLoadCompletedEventArgs e) + { + surfaceLoadTaskCompletionSource?.TrySetResult(); + surface.LoadCompleted -= loadedImageSourceLoadCompletedEventHandler; + } + private void OnSizeChanged(object sender, SizeChangedEventArgs e) { if (e.NewSize != e.PreviousSize && spriteVisual != null) { - OnUpdateVisual(spriteVisual); + UpdateVisual(spriteVisual); } } + + private void OnUnload(object sender, RoutedEventArgs e) + { + Unloading(); + spriteVisual?.Dispose(); + spriteVisual = null; + + SizeChanged -= sizeChangedEventHandler; + Unloaded -= unloadEventHandler; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs index 4b0187cb..919b948e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs @@ -20,7 +20,7 @@ internal sealed partial class Gradient : CompositionImage private double imageAspectRatio; /// - protected override void OnUpdateVisual(SpriteVisual? spriteVisual) + protected override void UpdateVisual(SpriteVisual? spriteVisual) { if (spriteVisual is null) { @@ -31,15 +31,9 @@ internal sealed partial class Gradient : CompositionImage spriteVisual.Size = ActualSize; } - /// - protected override async Task LoadImageSurfaceAsync(string file, CancellationToken token) + protected override void LoadImageSurfaceCompleted(LoadedImageSurface surface) { - TaskCompletionSource loadCompleteTaskSource = new(); - LoadedImageSurface surface = LoadedImageSurface.StartLoadFromUri(file.ToUri()); - surface.LoadCompleted += (_, _) => loadCompleteTaskSource.TrySetResult(); - await loadCompleteTaskSource.Task.ConfigureAwait(true); imageAspectRatio = surface.NaturalSize.AspectRatio(); - return surface; } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs index 286da729..870904be 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/MonoChrome.cs @@ -7,6 +7,7 @@ using Microsoft.UI.Composition; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Snap.Hutao.Control.Theme; +using Windows.Foundation; namespace Snap.Hutao.Control.Image; @@ -16,6 +17,8 @@ namespace Snap.Hutao.Control.Image; [HighQuality] internal sealed class MonoChrome : CompositionImage { + private readonly TypedEventHandler actualThemeChangedEventHandler; + private CompositionColorBrush? backgroundBrush; /// @@ -23,7 +26,8 @@ internal sealed class MonoChrome : CompositionImage /// public MonoChrome() { - ActualThemeChanged += OnActualThemeChanged; + actualThemeChangedEventHandler = OnActualThemeChanged; + ActualThemeChanged += actualThemeChangedEventHandler; } /// @@ -41,6 +45,16 @@ internal sealed class MonoChrome : CompositionImage return compositor.CompositeSpriteVisual(alphaMaskEffectBrush); } + protected override void Unloading() + { + ActualThemeChanged -= actualThemeChangedEventHandler; + + backgroundBrush?.Dispose(); + backgroundBrush = null; + + base.Unloading(); + } + private void OnActualThemeChanged(FrameworkElement sender, object args) { if (backgroundBrush != null) diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml index 1725dee1..38ed24b8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml +++ b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml @@ -5,27 +5,26 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:shcm="using:Snap.Hutao.Control.Markup" - Name="RootSplitButton" Padding="0,6" - Click="SplitButtonClick" - Loaded="OnRootControlLoaded" mc:Ignorable="d"> + + diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs index 0d33234f..2e435593 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Panel/PanelSelector.xaml.cs @@ -3,6 +3,7 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; +using Windows.Foundation; namespace Snap.Hutao.Control.Panel; @@ -16,12 +17,28 @@ internal sealed partial class PanelSelector : SplitButton private static readonly DependencyProperty CurrentProperty = Property.Depend(nameof(Current), List, OnCurrentChanged); + private readonly RoutedEventHandler loadedEventHandler; + private readonly RoutedEventHandler unloadedEventHandler; + private readonly TypedEventHandler clickEventHandler; + private readonly RoutedEventHandler menuItemClickEventHandler; + /// /// 构造一个新的面板选择器 /// public PanelSelector() { InitializeComponent(); + + loadedEventHandler = OnRootLoaded; + Loaded += loadedEventHandler; + + clickEventHandler = OnRootClick; + Click += clickEventHandler; + + menuItemClickEventHandler = OnMenuItemClick; + + unloadedEventHandler = OnRootUnload; + Unloaded += unloadedEventHandler; } /// @@ -33,37 +50,54 @@ internal sealed partial class PanelSelector : SplitButton set => SetValue(CurrentProperty, value); } - private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) + private static void InitializeItems(PanelSelector selector) { - OnCurrentChanged((PanelSelector)obj, (string)args.NewValue); + MenuFlyout menuFlyout = (MenuFlyout)selector.Flyout; + int hash = selector.GetHashCode(); + foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast()) + { + item.GroupName = $"{nameof(PanelSelector)}GroupOf@{hash}"; + item.Click += selector.menuItemClickEventHandler; + } } - private static void OnCurrentChanged(PanelSelector sender, string current) + private static void OnCurrentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) { - MenuFlyout menuFlyout = (MenuFlyout)sender.RootSplitButton.Flyout; - RadioMenuFlyoutItem targetItem = menuFlyout.Items - .Cast() + UpdateRootGlyphAndItemIsCheck((PanelSelector)obj, (string)args.NewValue); + } + + private static void UpdateRootGlyphAndItemIsCheck(PanelSelector sender, string current) + { + RadioMenuFlyoutItem targetItem = (RadioMenuFlyoutItem)((MenuFlyout)sender.Flyout).Items .Single(i => (string)i.Tag == current); + targetItem.IsChecked = true; sender.IconPresenter.Glyph = ((FontIcon)targetItem.Icon).Glyph; } - private void OnRootControlLoaded(object sender, RoutedEventArgs e) + private void OnRootLoaded(object sender, RoutedEventArgs e) { // because the GroupName shares in global // we have to implement a control scoped GroupName. PanelSelector selector = (PanelSelector)sender; - MenuFlyout menuFlyout = (MenuFlyout)selector.RootSplitButton.Flyout; - int hash = GetHashCode(); - foreach (RadioMenuFlyoutItem item in menuFlyout.Items.Cast()) - { - item.GroupName = $"{nameof(PanelSelector)}GroupOf@{hash}"; - } - - OnCurrentChanged(selector, Current); + InitializeItems(selector); + UpdateRootGlyphAndItemIsCheck(selector, Current); } - private void SplitButtonClick(SplitButton sender, SplitButtonClickEventArgs args) + private void OnRootUnload(object sender, RoutedEventArgs e) + { + Loaded -= loadedEventHandler; + Click -= clickEventHandler; + + foreach (MenuFlyoutItemBase item in ((MenuFlyout)((PanelSelector)sender).Flyout).Items) + { + ((RadioMenuFlyoutItem)item).Click -= menuItemClickEventHandler; + } + + Unloaded -= unloadedEventHandler; + } + + private void OnRootClick(SplitButton sender, SplitButtonClickEventArgs args) { MenuFlyout menuFlyout = (MenuFlyout)sender.Flyout; @@ -84,9 +118,8 @@ internal sealed partial class PanelSelector : SplitButton Current = (string)item.Tag; } - private void RadioMenuFlyoutItemClick(object sender, RoutedEventArgs e) + private void OnMenuItemClick(object sender, RoutedEventArgs e) { - RadioMenuFlyoutItem item = (RadioMenuFlyoutItem)sender; - Current = (string)item.Tag; + Current = (string)((FrameworkElement)sender).Tag; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Property.cs b/src/Snap.Hutao/Snap.Hutao/Control/Property.cs index 113981e3..fca8a789 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Property.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Property.cs @@ -10,6 +10,7 @@ namespace Snap.Hutao.Control; /// /// 所有者的类型 [HighQuality] +[Obsolete("Use DependencyPropertyAttribute whenever possible")] internal static class Property { /// diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs index 6ac47c07..20f94689 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/ScopedPage.cs @@ -20,6 +20,7 @@ internal class ScopedPage : Page // Allow GC to Collect the IServiceScope private static readonly WeakReference PreviousScopeReference = new(null!); + private readonly RoutedEventHandler unloadEventHandler; private readonly CancellationTokenSource viewCancellationTokenSource = new(); private readonly IServiceScope currentScope; @@ -28,7 +29,8 @@ internal class ScopedPage : Page /// protected ScopedPage() { - Unloaded += OnScopedPageUnloaded; + unloadEventHandler = OnUnloaded; + Unloaded += unloadEventHandler; currentScope = Ioc.Default.CreateScope(); DisposePreviousScope(); @@ -47,19 +49,6 @@ internal class ScopedPage : Page } } - /// - /// 初始化 - /// 应当在 InitializeComponent() 前调用 - /// - /// 视图模型类型 - protected void InitializeWith() - where TViewModel : class, IViewModel - { - IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService(); - viewModel.CancellationToken = viewCancellationTokenSource.Token; - DataContext = viewModel; - } - /// /// 异步通知接收器 /// @@ -75,6 +64,19 @@ internal class ScopedPage : Page extra.NotifyNavigationCompleted(); } + /// + /// 初始化 + /// 应当在 InitializeComponent() 前调用 + /// + /// 视图模型类型 + protected void InitializeWith() + where TViewModel : class, IViewModel + { + IViewModel viewModel = currentScope.ServiceProvider.GetRequiredService(); + viewModel.CancellationToken = viewCancellationTokenSource.Token; + DataContext = viewModel; + } + /// protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) { @@ -105,9 +107,9 @@ internal class ScopedPage : Page } } - private void OnScopedPageUnloaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e) + private void OnUnloaded(object sender, RoutedEventArgs e) { DataContext = null; - Unloaded -= OnScopedPageUnloaded; + Unloaded -= unloadEventHandler; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs index 443b19f6..852cf008 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Text/DescriptionTextBlock.cs @@ -7,8 +7,10 @@ using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Media; +using Snap.Hutao.Control.Extension; using Snap.Hutao.Control.Media; using Snap.Hutao.Control.Theme; +using Windows.Foundation; using Windows.UI; namespace Snap.Hutao.Control.Text; @@ -29,19 +31,22 @@ internal sealed class DescriptionTextBlock : ContentControl private static readonly int ItalicTagFullLength = "".Length; private static readonly int ItalicTagLeftLength = "".Length; + private readonly TypedEventHandler actualThemeChangedEventHandler; + /// /// 构造一个新的呈现描述文本的文本块 /// public DescriptionTextBlock() { - IsTabStop = false; + this.DisableInteraction(); Content = new TextBlock() { TextWrapping = TextWrapping.Wrap, }; - ActualThemeChanged += OnActualThemeChanged; + actualThemeChangedEventHandler = OnActualThemeChanged; + ActualThemeChanged += actualThemeChangedEventHandler; } /// @@ -55,47 +60,47 @@ internal sealed class DescriptionTextBlock : ContentControl private static void OnDescriptionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - TextBlock text = (TextBlock)((DescriptionTextBlock)d).Content; + TextBlock textBlock = (TextBlock)((DescriptionTextBlock)d).Content; ReadOnlySpan description = (string)e.NewValue; - UpdateDescription(text, description); + UpdateDescription(textBlock, description); } - private static void UpdateDescription(TextBlock text, in ReadOnlySpan description) + private static void UpdateDescription(TextBlock textBlock, in ReadOnlySpan description) { - text.Inlines.Clear(); + textBlock.Inlines.Clear(); int last = 0; for (int i = 0; i < description.Length;) { // newline - if (description[i..].IndexOf(@"\n") is 0) + if (description[i..].StartsWith(@"\n")) { - AppendText(text, description[last..i]); - AppendLineBreak(text); + AppendText(textBlock, description[last..i]); + AppendLineBreak(textBlock); i += 1; last = i; } // color tag - else if (description[i..].IndexOf(" - + diff --git a/src/Snap.Hutao/Snap.Hutao/Control/ValueConverter.cs b/src/Snap.Hutao/Snap.Hutao/Control/ValueConverter.cs index 6f185773..c8b322cf 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/ValueConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/ValueConverter.cs @@ -25,9 +25,9 @@ internal abstract class ValueConverter : IValueConverter Ioc.Default .GetRequiredService>>() .LogError(ex, "值转换器异常"); - } - return null; + throw; + } #else return Convert((TFrom)value); #endif diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 7c58cf85..e418d9fb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -35,7 +35,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation }; private readonly ILogger logger; - private readonly HttpClient httpClient; + private readonly IHttpClientFactory httpClientFactory; private readonly IServiceProvider serviceProvider; private readonly ConcurrentDictionary concurrentTasks = new(); @@ -50,7 +50,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation public ImageCache(IServiceProvider serviceProvider) { logger = serviceProvider.GetRequiredService>(); - httpClient = serviceProvider.GetRequiredService().CreateClient(nameof(ImageCache)); + httpClientFactory = serviceProvider.GetRequiredService(); this.serviceProvider = serviceProvider; } @@ -58,20 +58,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation /// public void RemoveInvalid() { - string folder = GetCacheFolder(); - string[] files = Directory.GetFiles(folder); - - List filesToDelete = new(); - - foreach (string file in files) - { - if (IsFileInvalid(file, false)) - { - filesToDelete.Add(file); - } - } - - RemoveInternal(filesToDelete); + RemoveInternal(Directory.GetFiles(GetCacheFolder()).Where(file => IsFileInvalid(file, false))); } /// @@ -92,11 +79,10 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation string[] files = Directory.GetFiles(folder); List filesToDelete = new(); - - foreach (Uri uri in uriForCachedItems) + foreach (ref readonly Uri uri in uriForCachedItems) { string filePath = Path.Combine(folder, GetCacheFileName(uri)); - if (Array.IndexOf(files, filePath) >= 0) + if (files.Contains(filePath)) { filesToDelete.Add(filePath); } @@ -184,6 +170,7 @@ internal sealed class ImageCache : IImageCache, IImageCacheFilePathOperation logger.LogInformation("Begin downloading for {Uri}", uri); int retryCount = 0; + HttpClient httpClient = httpClientFactory.CreateClient(nameof(ImageCache)); while (retryCount < 6) { using (HttpResponseMessage message = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false)) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs index 342cbe76..cade03ff 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/ScopedDbCurrent.cs @@ -46,6 +46,7 @@ internal sealed class ScopedDbCurrent return; } + // TODO: Troubeshooting why the serviceProvider will NRE using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionFormat.cs b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionFormat.cs index 7949a5a4..de50e2e8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionFormat.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/ExceptionService/ExceptionFormat.cs @@ -25,13 +25,7 @@ internal sealed class ExceptionFormat foreach (DictionaryEntry entry in exception.Data) { - builder - .Append(entry.Key) - .Append(":[") - .Append(TypeNameHelper.GetTypeDisplayName(entry.Value)) - .Append("]:") - .Append(entry.Value) - .Append(StringLiterals.CRLF); + builder.AppendLine($"{entry.Key}:[{TypeNameHelper.GetTypeDisplayName(entry.Value)}]:entry.Value"); } builder.AppendLine(SectionSeparator); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs index 7e047e29..173214c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs @@ -24,7 +24,7 @@ namespace Snap.Hutao.Core.Windowing; /// /// 窗体类型 [SuppressMessage("", "CA1001")] -internal sealed class ExtendedWindow : IRecipient +internal sealed class ExtendedWindow : IRecipient where TWindow : Window, IWindowOptionsSource { private readonly TWindow window; @@ -53,7 +53,7 @@ internal sealed class ExtendedWindow : IRecipient - public void Receive(FlyoutOpenCloseMessage message) + public void Receive(FlyoutStateChangedMessage message) { UpdateDragRectangles(message.IsOpen); } diff --git a/src/Snap.Hutao/Snap.Hutao/Message/FlyoutOpenCloseMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs similarity index 66% rename from src/Snap.Hutao/Snap.Hutao/Message/FlyoutOpenCloseMessage.cs rename to src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs index 13c386b3..ed543fe5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Message/FlyoutOpenCloseMessage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs @@ -7,17 +7,22 @@ namespace Snap.Hutao.Message; /// Flyout开启关闭消息 /// [HighQuality] -internal sealed class FlyoutOpenCloseMessage +internal sealed class FlyoutStateChangedMessage { /// /// 构造一个新的Flyout开启关闭消息 /// /// 是否为开启状态 - public FlyoutOpenCloseMessage(bool isOpen) + public FlyoutStateChangedMessage(bool isOpen) { IsOpen = isOpen; } + public static FlyoutStateChangedMessage Open { get; } = new(true); + + public static FlyoutStateChangedMessage Close { get; } = new(false); + + /// /// 是否为开启状态 ///