/
githubmirror
/
PowerToys
Обзор
Документация
Войти
/
githubmirror
/
PowerToys
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/modules/cmdpal/Microsoft.CmdPal.UI/Controls/IconBox.cs
282 строки
9 KB
Jiří Polášek
CmdPal: Remove workaround for FontIconSource.CreateIconElement (#45790)
28 мар 2026, 02:37
Не верифицирован
28 мар 2026, 02:37
9afa1ec
Код
Авторство
О чём код?
// Copyright (c) Microsoft Corporation // The Microsoft Corporation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using CommunityToolkit.WinUI.Deferred; using ManagedCommon; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; using Microsoft.UI.Xaml.Input; using Windows.Foundation; namespace Microsoft.CmdPal.UI.Controls; /// <summary> /// A helper control which takes an <see cref="IconSource"/> and creates the corresponding <see cref="IconElement"/>. /// </summary> public partial class IconBox : ContentControl { private const double DefaultIconFontSize = 16.0; private double _lastScale; private ElementTheme _lastTheme; private double _lastFontSize; /// <summary> /// Gets or sets the <see cref="IconSource"/> to display within the <see cref="IconBox"/>. Overwritten, if <see cref="SourceKey"/> is used instead. /// </summary> public IconSource? Source { get => (IconSource?)GetValue(SourceProperty); set => SetValue(SourceProperty, value); } // Using a DependencyProperty as the backing store for Source. This enables animation, styling, binding, etc... public static readonly DependencyProperty SourceProperty = DependencyProperty.Register(nameof(Source), typeof(IconSource), typeof(IconBox), new PropertyMetadata(null, OnSourcePropertyChanged)); /// <summary> /// Gets or sets a value to use as the <see cref="SourceKey"/> to retrieve an <see cref="IconSource"/> to set as the <see cref="Source"/>. /// </summary> public object? SourceKey { get => (object?)GetValue(SourceKeyProperty); set => SetValue(SourceKeyProperty, value); } // Using a DependencyProperty as the backing store for SourceKey. This enables animation, styling, binding, etc... public static readonly DependencyProperty SourceKeyProperty = DependencyProperty.Register(nameof(SourceKey), typeof(object), typeof(IconBox), new PropertyMetadata(null, OnSourceKeyPropertyChanged)); private TypedEventHandler<IconBox, SourceRequestedEventArgs>? _sourceRequested; /// <summary> /// Gets or sets the <see cref="SourceRequested"/> event handler to provide the value of the <see cref="IconSource"/> for the <see cref="Source"/> property from the provided <see cref="SourceKey"/>. /// </summary> public event TypedEventHandler<IconBox, SourceRequestedEventArgs>? SourceRequested { add { _sourceRequested += value; if (_sourceRequested?.GetInvocationList().Length == 1) { Refresh(); } #if DEBUG if (_sourceRequested?.GetInvocationList().Length > 1) { Logger.LogWarning("There shouldn't be more than one handler for IconBox.SourceRequested"); } #endif } remove => _sourceRequested -= value; } public IconBox() { TabFocusNavigation = KeyboardNavigationMode.Once; IsTabStop = false; HorizontalContentAlignment = HorizontalAlignment.Center; VerticalContentAlignment = VerticalAlignment.Center; Loaded += OnLoaded; Unloaded += OnUnloaded; ActualThemeChanged += OnActualThemeChanged; SizeChanged += OnSizeChanged; UpdateLastFontSize(); } private void UpdateLastFontSize() { _lastFontSize = Pick(Width) ?? Pick(Height) ?? Pick(ActualWidth) ?? Pick(ActualHeight) ?? DefaultIconFontSize; return; static double? Pick(double value) => double.IsFinite(value) && value > 0 ? value : null; } private void OnSizeChanged(object s, SizeChangedEventArgs e) { UpdateLastFontSize(); if (Source is FontIconSource fontIcon) { fontIcon.FontSize = _lastFontSize; UpdatePaddingForFontIcon(); } } private void UpdatePaddingForFontIcon() => Padding = new Thickness(Math.Round(_lastFontSize * -0.2)); private void OnActualThemeChanged(FrameworkElement sender, object args) { if (_lastTheme == ActualTheme) { return; } _lastTheme = ActualTheme; Refresh(); } private void OnLoaded(object sender, RoutedEventArgs e) { _lastTheme = ActualTheme; UpdateLastFontSize(); if (XamlRoot is not null) { _lastScale = XamlRoot.RasterizationScale; XamlRoot.Changed += OnXamlRootChanged; } Refresh(); } private void OnUnloaded(object sender, RoutedEventArgs e) { if (XamlRoot is not null) { XamlRoot.Changed -= OnXamlRootChanged; } } private void OnXamlRootChanged(XamlRoot sender, XamlRootChangedEventArgs args) { var newScale = sender.RasterizationScale; var changedLastTheme = _lastTheme != ActualTheme; var changedScale = Math.Abs(newScale - _lastScale) > 0.01; _lastScale = newScale; _lastTheme = ActualTheme; if ((changedLastTheme || changedScale) && SourceKey is not null) { UpdateSourceKey(this, SourceKey); } } private void Refresh() { UpdateSourceKey(this, SourceKey); } private static void OnSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not IconBox self) { return; } switch (e.NewValue) { case null: self.Content = null; self.Padding = default; break; case FontIconSource fontIcon: self.UpdateLastFontSize(); fontIcon.FontSize = self._lastFontSize; if (self.Content is IconSourceElement iconSourceElement) { iconSourceElement.IconSource = fontIcon; } else { self.Content = fontIcon.CreateIconElement(); } self.UpdatePaddingForFontIcon(); break; case BitmapIconSource bitmapIcon: if (self.Content is IconSourceElement iconSourceElement2) { iconSourceElement2.IconSource = bitmapIcon; } else { self.Content = bitmapIcon.CreateIconElement(); } self.Padding = default; break; case IconSource source: self.Content = source.CreateIconElement(); self.Padding = default; break; default: throw new InvalidOperationException($"New value of {e.NewValue} is not of type IconSource."); } } private static void OnSourceKeyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is not IconBox self) { return; } UpdateSourceKey(self, e.NewValue); } private static void UpdateSourceKey(IconBox iconBox, object? sourceKey) { if (sourceKey is null) { iconBox.Source = null; return; } RequestIconFromSource(iconBox, sourceKey); } private static async void RequestIconFromSource(IconBox iconBox, object? sourceKey) { try { var iconBoxSourceRequestedHandler = iconBox._sourceRequested; if (iconBoxSourceRequestedHandler is null) { return; } var scale = iconBox._lastScale > 0 ? iconBox._lastScale : (iconBox.XamlRoot?.RasterizationScale > 0 ? iconBox.XamlRoot.RasterizationScale : 1.0); var eventArgs = new SourceRequestedEventArgs(sourceKey, iconBox._lastTheme, scale); await iconBoxSourceRequestedHandler.InvokeAsync(iconBox, eventArgs); // After the await: // Is the icon we're looking up now, the one we still // want to find? Since this IconBox might be used in a // list virtualization situation, it's very possible we // may have already been set to a new icon before we // even got back from the await. if (!ReferenceEquals(sourceKey, iconBox.SourceKey)) { // If the requested icon has changed, then just bail return; } iconBox.Source = eventArgs.Value; } catch (Exception ex) { // Exception from TryEnqueue bypasses the global error handler, // and crashes the app. Logger.LogError("Failed to set icon", ex); } } }