首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >让HandyControl支持PackIconKind

让HandyControl支持PackIconKind

原创
作者头像
秦建辉
发布2026-06-28 08:12:49
发布2026-06-28 08:12:49
1230
举报
文章被收录于专栏:WPF编程WPF编程

修改PackIconDataFactory.cs

代码语言:cs
复制
private static readonly Lazy<IReadOnlyDictionary<PackIconKind, string>> s_instanceHolder = new(Create);

internal static IReadOnlyDictionary<PackIconKind, string> Data => s_instanceHolder.Value;

private static IReadOnlyDictionary<PackIconKind, string> Create() => new Dictionary<PackIconKind, string>
{
    // 字典
}

编写自己的IconElement.cs

代码语言:cs
复制
using System.Windows;
using System.Windows.Media;

namespace AtmsII;

/// <summary>
/// 图标元素
/// </summary>
public static class IconElement
{
    #region PackIconKind
    /// <summary>
    /// 依赖属性:图标标识
    /// </summary>
    public static readonly DependencyProperty PackIconKindProperty = DependencyProperty.RegisterAttached(
        "PackIconKind",
        typeof(PackIconKind),
        typeof(IconElement),
        new PropertyMetadata(default(PackIconKind), (d, e) => {
            if (e.NewValue is PackIconKind kind)
            {
                HandyControl.Controls.IconElement.SetGeometry(d, Geometry.Parse(PackIconDataFactory.Data[kind]));
            }
            else
            {
                HandyControl.Controls.IconElement.SetGeometry(d, Geometry.Empty);
            }
        }));

    /// <summary>
    /// 设置图标标识
    /// </summary>
    /// <param name="element">依赖对象</param>
    /// <param name="value">属性值</param>
    public static void SetPackIconKind(DependencyObject element, PackIconKind value) => element.SetValue(PackIconKindProperty, value);

    /// <summary>
    /// 获取图标标识
    /// </summary>
    /// <param name="element">依赖对象</param>
    /// <returns>属性值</returns>
    public static PackIconKind GetPackIconKind(DependencyObject element) => (PackIconKind)element.GetValue(PackIconKindProperty);
    #endregion

    #region PackIconKindSelected
    /// <summary>
    /// 依赖属性:图标标识
    /// </summary>
    public static readonly DependencyProperty PackIconKindSelectedProperty = DependencyProperty.RegisterAttached(
        "PackIconKindSelected",
        typeof(PackIconKind),
        typeof(IconElement),
        new PropertyMetadata(default(PackIconKind), (d, e) => {
            if (e.NewValue is PackIconKind kind)
            {
                HandyControl.Controls.IconSwitchElement.SetGeometrySelected(d, Geometry.Parse(PackIconDataFactory.Data[kind]));
            }
            else
            {
                HandyControl.Controls.IconSwitchElement.SetGeometrySelected(d, Geometry.Empty);
            }
        }));

    /// <summary>
    /// 设置图标标识
    /// </summary>
    /// <param name="element">依赖对象</param>
    /// <param name="value">属性值</param>
    public static void SetPackIconKindSelected(DependencyObject element, PackIconKind value) => element.SetValue(PackIconKindSelectedProperty, value);

    /// <summary>
    /// 获取图标标识
    /// </summary>
    /// <param name="element">依赖对象</param>
    /// <returns>属性值</returns>
    public static PackIconKind GetPackIconKindSelected(DependencyObject element) => (PackIconKind)element.GetValue(PackIconKindSelectedProperty);
    #endregion
}

编写自己的MetaPackIcon.cs

代码语言:cs
复制
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace AtmsII;

/// <summary>
/// 图标控件
/// </summary>
public class MetaPackIcon : Control
{
    /// <summary>
    /// 外部图标索引
    /// </summary>
    private static readonly Lazy<IDictionary<string, string>> s_iconIndex = new(() =>
    {
        try
        {
            string path = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Icons", $"{nameof(MetaPackIcon)}.json");
            if (System.IO.File.Exists(path))
            {
                return JsonConvert.DeserializeObject<Dictionary<string, string>>(System.IO.File.ReadAllText(path)) ?? [];
            }
        }
        catch
        {
            // 忽略异常
        }
        return new Dictionary<string, string>();
    });

    static MetaPackIcon() => DefaultStyleKeyProperty.OverrideMetadata(typeof(MetaPackIcon), new FrameworkPropertyMetadata(typeof(MetaPackIcon)));

    /// <summary>
    /// 应用模板时获取 Path 引用
    /// </summary>
    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        UpdateData();
    }

    #region Kind
    /// <summary>
    /// 依赖属性:内部图标标识
    /// </summary>
    public static readonly DependencyProperty PackIconKindProperty = DependencyProperty.Register(
        nameof(PackIconKind),
        typeof(PackIconKind),
        typeof(MetaPackIcon),
        new PropertyMetadata(default(PackIconKind), static (d, e) =>
        {
            ((MetaPackIcon)d).UpdateData();
        }));

    /// <summary>
    /// 内部图标标识
    /// </summary>
    public PackIconKind PackIconKind
    {
        get => (PackIconKind)GetValue(PackIconKindProperty); set => SetValue(PackIconKindProperty, value);
    }

    /// <summary>
    /// 依赖属性:图标标识
    /// </summary>
    public static readonly DependencyProperty KindProperty = DependencyProperty.Register(
        nameof(Kind),
        typeof(string),
        typeof(MetaPackIcon),
        new FrameworkPropertyMetadata(default(string), static (d, e) =>
        {
            ((MetaPackIcon)d).UpdateData();
        }));

    /// <summary>
    /// 图标标识
    /// </summary>
    public string Kind
    {
        get => (string)GetValue(KindProperty); set => SetValue(KindProperty, value);
    }

    private void UpdateData()
    {
        string? data;
        if (string.IsNullOrEmpty(Kind))
        {
            data = PackIconDataFactory.Data[PackIconKind];
        }
        else
        {
            s_iconIndex.Value.TryGetValue(Kind, out data);
        }
        Data = data;
    }
    #endregion

    #region Data
    private static readonly DependencyPropertyKey DataPropertyKey = DependencyProperty.RegisterReadOnly(nameof(Data), typeof(string), typeof(MetaPackIcon), new PropertyMetadata(string.Empty));

    /// <summary>
    /// 依赖属性:路径数据
    /// </summary>
    public static readonly DependencyProperty DataProperty = DataPropertyKey.DependencyProperty;

    /// <summary>
    /// 路径数据
    /// </summary>
    [TypeConverter(typeof(GeometryConverter))]
    public string? Data
    {
        get => (string?)GetValue(DataProperty); private set => SetValue(DataPropertyKey, value);
    }
    #endregion

    #region ScaleToSizeOfWith
    /// <summary>
    /// 依赖属性:自适应大小
    /// </summary>
    public static readonly DependencyProperty ScaleToSizeOfWithProperty = DependencyProperty.Register(
        nameof(ScaleToSizeOfWith),
        typeof(FrameworkElement),
        typeof(MetaPackIcon),
        new PropertyMetadata(null, OnScaleToSizeOfWithChanged));

    /// <summary>
    /// 自适应大小
    /// </summary>
    public FrameworkElement? ScaleToSizeOfWith
    {
        get => (FrameworkElement?)GetValue(ScaleToSizeOfWithProperty); set => SetValue(ScaleToSizeOfWithProperty, value);
    }

    private static void OnScaleToSizeOfWithChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is MetaPackIcon icon)
        {
            BindingOperations.ClearBinding(icon, HeightProperty);
            BindingOperations.ClearBinding(icon, WidthProperty);
            if (e.NewValue is FrameworkElement source)
            {
                var multiBinding = new MultiBinding
                {
                    Converter = new ElementInnerSizeConverter(),
                    Mode = BindingMode.OneWay
                };

                multiBinding.Bindings.Add(new Binding(nameof(ActualHeight)) { Source = source });
                multiBinding.Bindings.Add(new Binding(nameof(ActualWidth)) { Source = source });
                multiBinding.Bindings.Add(new Binding("(Control.Padding)") { Source = source, FallbackValue = new Thickness(0) });

                icon.SetBinding(HeightProperty, multiBinding);
                icon.SetBinding(WidthProperty, multiBinding);
            }
        }
    }

    /// <summary>
    /// 计算控件扣除Padding后的内部可用正方形尺寸
    /// 参数顺序:[ActualHeight, ActualWidth, Padding]
    /// </summary>
    internal class ElementInnerSizeConverter : IMultiValueConverter
    {
        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            if ((values.Length >= 3)
                && (values[0] is double actualHeight)
                && (values[1] is double actualWidth)
                && values[2] is Thickness padding)
            {
                double innerH = actualHeight - padding.Top - padding.Bottom;
                double innerW = actualWidth - padding.Left - padding.Right;
                return Math.Max(16.0, Math.Min(innerH, innerW));
            }
            return 16.0;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) => [];
    }
    #endregion

    #region StrokeThickness
    /// <summary>
    /// 依赖属性:笔画粗细
    /// </summary>
    public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
        nameof(StrokeThickness),
        typeof(double),
        typeof(MetaPackIcon),
        new FrameworkPropertyMetadata(1.0, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

    /// <summary>
    /// 笔画粗细
    /// </summary>
    public double StrokeThickness
    {
        get => (double)GetValue(StrokeThicknessProperty); set => SetValue(StrokeThicknessProperty, value);
    }
    #endregion

    #region IsFill
    /// <summary>
    /// 依赖属性:是否填充
    /// </summary>
    public static readonly DependencyProperty IsFillProperty = DependencyProperty.Register(
        nameof(IsFill),
        typeof(bool),
        typeof(MetaPackIcon),
        new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.AffectsRender | FrameworkPropertyMetadataOptions.AffectsMeasure));

    /// <summary>
    /// 是否填充
    /// </summary>
    public bool IsFill
    {
        get => (bool)GetValue(IsFillProperty); set => SetValue(IsFillProperty, value);
    }
    #endregion

    #region Utilities
    /// <summary>
    /// 获取所有可用图标名称
    /// </summary>
    public static ICollection<string> GetAvailableIcons() => s_iconIndex.Value.Keys;

    /// <summary>
    /// 检查图标是否存在
    /// </summary>
    public static bool IconExists(string kind) => !string.IsNullOrEmpty(kind) && s_iconIndex.Value.ContainsKey(kind);
    #endregion
}

编写MetaPackIcon样式Themes\Generic.xaml

代码语言:xml
复制
<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:AtmsII">
    <Style TargetType="{x:Type local:MetaPackIcon}">
        <Setter Property="Width" Value="16"/>
        <Setter Property="Height" Value="16"/>
        <Setter Property="FlowDirection" Value="LeftToRight" />
        <Setter Property="HorizontalAlignment" Value="Center" />
        <Setter Property="VerticalAlignment" Value="Center" />
        <Setter Property="IsTabStop" Value="False" />
        <Setter Property="SnapsToDevicePixels" Value="True" />
        <Setter Property="UseLayoutRounding" Value="True" />
        <Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
        <Setter Property="Background" Value="{DynamicResource RegionBrush}" />

        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:MetaPackIcon}">
                    <Grid Background="{TemplateBinding Background}"
                          SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                          UseLayoutRounding="{TemplateBinding UseLayoutRounding}">
                        <Path x:Name="IconPath"
                              Data="{Binding Data, RelativeSource={RelativeSource TemplatedParent}}"
                              Stroke="{TemplateBinding Foreground}"
                              StrokeThickness="{TemplateBinding StrokeThickness}"
                              Fill="{TemplateBinding Foreground}"
                              Stretch="Uniform"
                              SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
                              RenderOptions.EdgeMode="Aliased"/>
                    </Grid>

                    <ControlTemplate.Triggers>
                        <Trigger Property="IsFill" Value="False">
                            <Setter TargetName="IconPath" Property="Fill" Value="{x:Null}" />
                        </Trigger>

                        <Trigger Property="IsEnabled" Value="False">
                            <Setter TargetName="IconPath" Property="Opacity" Value="0.5" />
                        </Trigger>

                        <Trigger Property="IsMouseOver" Value="True">
                            <Setter Property="Cursor" Value="Hand" />
                        </Trigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
</ResourceDictionary>

用法示例:

代码语言:xml
复制
<Button Margin="4" Style="{StaticResource ButtonBaseStyle}" Width="48" Height="48">
    <atmsii:MetaPackIcon PackIconKind="Plus" Foreground="Green" Width="32" Height="32"/>
</Button>

<ToggleButton Margin="4" Style="{StaticResource ToggleButtonIcon}" Width="48" Height="48" Foreground="Green"
              atmsii:IconElement.PackIconKind="{x:Static atmsii:PackIconKind.Plus}"
              atmsii:IconElement.PackIconKindSelected="{x:Static atmsii:PackIconKind.Minus}"/>

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档