Files
Shrlalgo.RvKits/Melskin/Markup/IconExtension.cs
2026-02-17 22:17:13 +08:00

404 lines
18 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Windows.Markup;
using System.Windows.Media.Imaging;
using System.Xaml;
using Melskin.Appearance;
using Melskin.Assets;
using Melskin.Controls;
namespace Melskin.Markup
{
/// <summary>
/// 方便在 XAML 中内联创建 IconElement 的标记扩展。
/// 用法示例:
/// <code lang="xaml">
/// xmlns:neo="clr-namespace:Melskin.Markup;assembly=Melskin"
/// <!-- MaterialSymbol -->
/// <Button Content="{neo:Icon Search Size=16}"/>
/// <!-- 指定 Glyph -->
/// <Button Content="{neo:Icon Glyph=&#xE001; FontFamily=/Melskin;component/Assets/Fonts/#YourFont Size=16}"/>
/// <!-- 几何 -->
/// <Button Content="{neo:Icon Geo=M0,0 L10,0 10,10 0,10Z GeometryFill=DodgerBlue Size=16}"/>
/// <!-- 图像 -->
/// <Button Content="{neo:Icon Image=/Melskin;component/Assets/Images/add.png Size=20}"/>
/// <!-- 资源中的 DrawingBrush -->
/// <Button Content="{neo:Icon BrushKey=SomeDrawingBrush Size=20}"/>
/// </code>
/// </summary>
[MarkupExtensionReturnType(typeof(IconElement))]
public class IconExtension : MarkupExtension
{
/// <summary>
/// 获取或设置图标符号的字符串表示形式。此属性用于指定要显示的图标的名称。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以使用一个字符串来标识所需的图标。如果设置了此属性且<see cref="Symbol"/>未被设置,则会尝试将这个字符串解析为有效的图标符号。
/// </remarks>
public string? Symbol { get; set; }
/// <summary>
/// 获取或设置图标的值。此属性用于直接指定图标的具体值。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以直接指定一个具体的图标值。如果<see cref="SymbolValue"/>被设置,则会优先使用这个值来显示图标,而不是通过字符串解析的方式。
/// </remarks>
public MaterialSymbol? SymbolValue { get; set; }
/// <summary>
/// 获取或设置图标的字符表示。此属性用于指定要显示的图标的具体字符。
/// </summary>
/// <remarks>
/// 通过设置此属性可以使用一个字符来标识所需的图标。这个字符通常是从特定字体中选择的一个图标符号例如BoxIcons等图标字体中的某个字符。
/// </remarks>
public string? Glyph { get; set; }
/// <summary>
/// 获取或设置图标图像的源。此属性用于指定要显示的图像的位置。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以使用一个字符串来标识图像的来源路径。如果设置了此属性且其他与图像相关的属性未被设置,则会尝试将这个字符串解析为有效的图像源,并加载相应的图像。
/// </remarks>
public string? Image { get; set; }
/// <summary>
/// 获取或设置几何图形的字符串表示形式。此属性用于指定要显示的几何图形的数据。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以使用一个字符串来定义所需的几何图形。如果设置了此属性且字符串有效,则会尝试将这个字符串解析为几何图形数据并应用到图标元素上。
/// </remarks>
public string? Geo { get; set; }
/// <summary>
/// 获取或设置用于查找绘图资源的键。此属性允许通过指定的键从资源中检索绘图对象。
/// </summary>
/// <remarks>
/// 当设置了此属性且提供的键有效时,将尝试从当前资源上下文中查找对应的绘图资源,并将其应用于图标元素。如果找到了与键匹配的绘图资源,则该资源会被用作图标的内容。
/// </remarks>
public string? DrawingKey { get; set; }
/// <summary>
/// 获取或设置用于查找画刷资源的键。此属性允许通过资源字典中的键来指定一个画刷或绘图画刷。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以使用一个字符串作为键来引用应用程序资源中的画刷对象(<see cref="Brush"/>)或绘图画刷对象(<see cref="DrawingBrush"/>)。如果设置了此属性,并且在资源字典中找到了对应的资源,则会将该资源应用到图标元素上。
/// </remarks>
public string? BrushKey { get; set; }
/// <summary>
/// 获取或设置图标的大小。此属性用于指定图标在显示时的尺寸。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以控制图标元素的高度和宽度。如果设置了<see cref="Size"/>属性而未明确设置<see cref="Height"/>和<see cref="Width"/>,则<see cref="Height"/>和<see cref="Width"/>将默认采用<see cref="Size"/>的值。
/// </remarks>
public double Size { get; set; } = double.NaN;
/// <summary>
/// 获取或设置图标的宽度。此属性用于指定图标元素的宽度。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以自定义图标元素的宽度。如果设置了此属性且没有设置<see cref="Size"/>属性,则会直接使用这个值作为图标的宽度。如果同时设置了<see cref="Size"/>和<see cref="Width"/>,则<see cref="Width"/>优先级更高。
/// </remarks>
public double Width { get; set; } = double.NaN;
/// <summary>
/// 获取或设置图标的高度。此属性用于指定图标在显示时的高度。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以控制图标元素的高度。如果设置了<see cref="Size"/>属性而未明确设置<see cref="Height"/>,则<see cref="Height"/>将默认采用<see cref="Size"/>的值。
/// </remarks>
public double Height { get; set; } = double.NaN;
/// <summary>
/// 获取或设置图标的前景色。此属性用于指定图标中文字或几何图形部分的颜色。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以自定义图标的颜色。如果未显式设置此属性,则可能使用默认值或其他相关属性来确定颜色。
/// </remarks>
public Brush? Foreground { get; set; }
/// <summary>
/// 获取或设置几何图形填充的画刷。此属性用于指定图标中几何图形部分的填充颜色。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以自定义图标的几何图形部分的填充效果。如果未显式设置此属性,则可能使用默认值或其他相关属性(如<see cref="Foreground"/>)来确定填充颜色。
/// </remarks>
public Brush? GeometryFill { get; set; }
/// <summary>
/// 获取或设置几何图形描边的颜色。此属性用于指定图标的几何形状边缘所使用的画刷。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以自定义图标中几何形状边缘的颜色。如果设置了此属性,则会使用指定的画刷来绘制几何形状的边缘。
/// </remarks>
public Brush? GeometryStroke { get; set; }
/// <summary>
/// 获取或设置几何图形描边的厚度。此属性用于控制图标的几何形状描边宽度。
/// </summary>
/// <remarks>
/// 通过设置此属性可以自定义图标中几何图形描边的宽度。如果设置了此属性且值不是NaN则会应用指定的描边厚度。
/// </remarks>
public double GeometryStrokeThickness { get; set; } = double.NaN;
/// <summary>
/// 获取或设置图标内容的类型。此属性用于指定图标的显示方式。
/// </summary>
/// <remarks>
/// 通过设置此属性可以定义图标是作为材料图标、字形、图像、几何图形、绘图还是画刷来展示。默认值为Auto表示自动选择最合适的内容类型。
/// </remarks>
public IconElement.IconContentType ContentType { get; set; } = IconElement.IconContentType.Auto;
/// <summary>
/// 获取或设置图标的字体系列。此属性用于指定图标所使用的字体,以便正确显示图标符号。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以指定一个字体系列来确保图标能够以正确的样式显示。如果设置了此属性,则会应用到图标元素的字体系列上。
/// </remarks>
public FontFamily? FontFamily { get; set; }
/// <summary>
/// 获取或设置图标的字体大小。此属性用于控制图标显示时的字体大小。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以指定图标字体的具体大小。如果设置了此属性且未被其他尺寸属性覆盖,则会使用这个值来设置图标的字体大小。
/// </remarks>
public double FontSize { get; set; } = double.NaN;
/// <summary>
/// 获取或设置图像的拉伸模式。此属性决定了图像如何填充其布局空间。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以控制图像在显示时的拉伸方式。默认值为 <see cref="Stretch.Uniform"/>,表示图像将按比例缩放以适应可用空间,同时保持其原始宽高比。
/// </remarks>
public Stretch ImageStretch { get; set; } = Stretch.Uniform;
/// <summary>
/// 获取或设置图标元素的边距。此属性用于控制图标与其容器之间的空间。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以自定义图标在布局中的位置和间距。边距值是一个<see cref="Thickness"/>类型的对象,允许指定左、上、右、下四个方向的边距。
/// </remarks>
public Thickness Margin { get; set; }
/// <summary>
/// 获取或设置图标元素的水平对齐方式。此属性用于指定图标在其容器中的水平位置。
/// </summary>
/// <remarks>
/// 通过设置此属性可以控制图标在水平方向上的对齐方式。默认值为居中对齐HorizontalAlignment.Center
/// </remarks>
public HorizontalAlignment HorizontalAlignment { get; set; } = HorizontalAlignment.Center;
/// <summary>
/// 获取或设置图标在垂直方向上的对齐方式。此属性用于控制图标在其容器中的垂直位置。
/// </summary>
/// <remarks>
/// 通过设置此属性,可以指定图标在其容器中是顶部对齐、居中对齐还是底部对齐。默认情况下,图标是居中对齐的。
/// </remarks>
public VerticalAlignment VerticalAlignment { get; set; } = VerticalAlignment.Center;
/// <summary>
/// 用于在XAML中创建图标元素的标记扩展。通过设置不同的属性可以自定义图标的样式和内容。
/// </summary>
public IconExtension()
{
}
/// <summary>
/// 用于在XAML中创建图标元素的标记扩展。通过设置不同的属性可以自定义图标的样式、内容和外观。
/// </summary>
public IconExtension(string symbol)
{
Symbol = symbol;
}
/// <summary>
/// 用于在XAML中创建图标元素的标记扩展。通过设置不同的属性可以自定义图标的样式、内容和外观。
/// </summary>
public IconExtension(MaterialSymbol materialSymbol)
{
SymbolValue = materialSymbol;
}
/// <inheritdoc />
public override object ProvideValue(IServiceProvider serviceProvider)
{
var icon = new IconElement();
// 尺寸
if (!double.IsNaN(Size))
{
if (double.IsNaN(Width)) icon.Width = Size;
if (double.IsNaN(Height)) icon.Height = Size;
if (double.IsNaN(FontSize)) FontSize = Size;
}
if (!double.IsNaN(Width)) icon.Width = Width;
if (!double.IsNaN(Height)) icon.Height = Height;
if (!double.IsNaN(FontSize)) icon.SetValue(Control.FontSizeProperty, FontSize);
if (FontFamily != null) icon.SetValue(Control.FontFamilyProperty, FontFamily);
if (Margin != default) icon.Margin = Margin;
icon.HorizontalAlignment = HorizontalAlignment;
icon.VerticalAlignment = VerticalAlignment;
if (Foreground != null)
icon.SetValue(Control.ForegroundProperty, Foreground);
// 上下文(已移除元组)
var ctx = GetContext(serviceProvider);
var resourceOwner = ctx.RootObject as FrameworkElement ?? ctx.TargetObject as FrameworkElement;
// Drawing
if (!string.IsNullOrEmpty(DrawingKey))
{
var drawingObj = TryFindResource(resourceOwner, DrawingKey);
if (drawingObj is Drawing d)
{
icon.Drawing = d;
}
}
// DrawingBrush / Brush
if (!string.IsNullOrEmpty(BrushKey))
{
var brushObj = TryFindResource(resourceOwner, BrushKey);
switch (brushObj)
{
case DrawingBrush db:
icon.DrawingBrush = db;
break;
case Brush b:
GeometryFill ??= b;
break;
}
}
// Geometry
if (!string.IsNullOrEmpty(Geo))
{
try
{
var g = Geometry.Parse(Geo);
if (g.CanFreeze) g.Freeze();
icon.Geometry = g;
}
catch
{
// ignored
}
}
// Image
if (!string.IsNullOrEmpty(Image))
{
try
{
var uri = TryCreateUri(Image);
if (uri != null)
{
var bmp = new BitmapImage();
bmp.BeginInit();
bmp.UriSource = uri;
bmp.CacheOption = BitmapCacheOption.OnLoad;
bmp.EndInit();
if (bmp.CanFreeze) bmp.Freeze();
icon.ImageSource = bmp;
}
}
catch { }
}
// Glyph
if (!string.IsNullOrEmpty(Glyph))
icon.Glyph = Glyph;
// Symbol
if (SymbolValue.HasValue)
icon.Symbol = SymbolValue.Value;
else if (!string.IsNullOrEmpty(Symbol))
icon.Symbol = IconElement.Parse(Symbol);
// ContentType
if (ContentType != IconElement.IconContentType.Auto)
icon.ContentType = ContentType;
// Geometry 样式
if (icon.Geometry != null)
{
if (GeometryFill != null)
icon.GeometryFill = GeometryFill;
else if (Foreground != null && icon.GeometryFill == System.Windows.Media.Brushes.Black)
icon.GeometryFill = Foreground;
if (GeometryStroke != null)
icon.GeometryStroke = GeometryStroke;
if (!double.IsNaN(GeometryStrokeThickness))
icon.GeometryStrokeThickness = GeometryStrokeThickness;
}
icon.ImageStretch = ImageStretch;
return icon;
}
private sealed class ProvideContext
{
public object? TargetObject { get; set; }
public object? TargetProperty { get; set; }
public object? RootObject { get; set; }
}
private static ProvideContext GetContext(IServiceProvider serviceProvider)
{
object? targetObject = null;
object? targetProperty = null;
object? rootObject = null;
var pvt = serviceProvider?.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget;
if (pvt != null)
{
targetObject = pvt.TargetObject;
targetProperty = pvt.TargetProperty;
}
var rop = serviceProvider?.GetService(typeof(IRootObjectProvider)) as IRootObjectProvider;
if (rop != null)
{
rootObject = rop.RootObject;
}
return new ProvideContext
{
TargetObject = targetObject,
TargetProperty = targetProperty,
RootObject = rootObject
};
}
private static object? TryFindResource(FrameworkElement? fe, string? key)
{
if (fe == null||key == null) return null;
var found = fe?.TryFindResource(key);
if (found != null) return found;
if (Application.Current != null && Application.Current.Resources.Contains(key))
return Application.Current.Resources[key];
else
{
return ThemeManager.Current?.TryFindResource(key);
}
//return null;
}
private static Uri? TryCreateUri(string? path)
{
if (string.IsNullOrEmpty(path)) return null;
if (path!.IndexOf(";component/", StringComparison.OrdinalIgnoreCase) >= 0 ||
path.StartsWith("pack://", StringComparison.OrdinalIgnoreCase))
{
if (!path.StartsWith("pack://", StringComparison.OrdinalIgnoreCase))
path = "pack://application:,,," + (path.StartsWith("/") ? path : "/" + path);
return new Uri(path, UriKind.Absolute);
}
return Uri.TryCreate(path, UriKind.RelativeOrAbsolute, out var u) ? u : null;
}
}
}