如何在XAML或代码中复用带可变参数的画刷定义?
当然可以!你完全不用重复写一堆几乎一样的VisualBrush定义,有几种实用的方法能帮你复用带可变参数的画刷,下面给你详细讲讲:
方案1:用附加属性实现XAML内动态参数配置
这种方式适合在XAML里直接灵活配置参数(比如背景色、线条色、Tile尺寸),核心思路是通过附加属性传递可变值,再绑定到通用的VisualBrush模板上。
首先定义一个静态类来承载附加属性:
public static class HatchBrushParams { // 背景色参数 public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.RegisterAttached("BackgroundColor", typeof(Color), typeof(HatchBrushParams), new PropertyMetadata(Colors.Gray)); public static Color GetBackgroundColor(DependencyObject obj) => (Color)obj.GetValue(BackgroundColorProperty); public static void SetBackgroundColor(DependencyObject obj, Color value) => obj.SetValue(BackgroundColorProperty, value); // 线条颜色参数 public static readonly DependencyProperty StrokeColorProperty = DependencyProperty.RegisterAttached("StrokeColor", typeof(Color), typeof(HatchBrushParams), new PropertyMetadata(Colors.White)); public static Color GetStrokeColor(DependencyObject obj) => (Color)obj.GetValue(StrokeColorProperty); public static void SetStrokeColor(DependencyObject obj, Color value) => obj.SetValue(StrokeColorProperty, value); // Tile尺寸参数 public static readonly DependencyProperty TileSizeProperty = DependencyProperty.RegisterAttached("TileSize", typeof(double), typeof(HatchBrushParams), new PropertyMetadata(5.0)); public static double GetTileSize(DependencyObject obj) => (double)obj.GetValue(TileSizeProperty); public static void SetTileSize(DependencyObject obj, double value) => obj.SetValue(TileSizeProperty, value); }
接着在资源字典里定义通用的VisualBrush模板,用绑定关联附加属性:
<ResourceDictionary> <!-- 转换器:把TileSize转成Viewport/Viewbox需要的Rect --> <local:DoubleToRectConverter x:Key="DoubleToRectConverter" /> <!-- 转换器:把TileSize转成对应的Path Data --> <local:TileSizeToPathDataConverter x:Key="TileSizeToPathDataConverter" /> <VisualBrush x:Key="ReusableHatchBrush" TileMode="Tile" Viewport="{Binding Path=(local:HatchBrushParams.TileSize), RelativeSource={RelativeSource Self}, Converter={StaticResource DoubleToRectConverter}}" ViewportUnits="Absolute" Viewbox="{Binding Path=(local:HatchBrushParams.TileSize), RelativeSource={RelativeSource Self}, Converter={StaticResource DoubleToRectConverter}}" ViewboxUnits="Absolute"> <VisualBrush.Visual> <Grid Background="{Binding Path=(local:HatchBrushParams.BackgroundColor), RelativeSource={RelativeSource AncestorType=VisualBrush}}"> <Path Data="{Binding Path=(local:HatchBrushParams.TileSize), RelativeSource={RelativeSource AncestorType=VisualBrush}, Converter={StaticResource TileSizeToPathDataConverter}}" Stroke="{Binding Path=(local:HatchBrushParams.StrokeColor), RelativeSource={RelativeSource AncestorType=VisualBrush}}" StrokeEndLineCap="Square" RenderOptions.EdgeMode="Aliased" /> </Grid> </VisualBrush.Visual> </VisualBrush> </ResourceDictionary>
需要补充两个转换器的实现:
// 把TileSize数值转成Rect对象 public class DoubleToRectConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is double size) return new Rect(0, 0, size, size); return Rect.Empty; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } // 根据TileSize生成对应的斜线Path Data public class TileSizeToPathDataConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is double size) { var offset = size - 2; // 对应你原代码里的偏移量逻辑 return Geometry.Parse($"M 0 {size} L {size} 0 M -{offset} 2 L 2 -{offset} M {offset} {size+offset} L {size+offset} {offset}"); } return Geometry.Empty; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { throw new NotImplementedException(); } }
使用时直接给画刷设置附加属性即可:
<Rectangle Width="200" Height="200"> <Rectangle.Fill> <StaticResource ResourceKey="ReusableHatchBrush"> <local:HatchBrushParams.BackgroundColor>#46768c</local:HatchBrushParams.BackgroundColor> <local:HatchBrushParams.StrokeColor>#80ffffff</local:HatchBrushParams.StrokeColor> <local:HatchBrushParams.TileSize>5</local:HatchBrushParams.TileSize> </StaticResource> </Rectangle.Fill> </Rectangle>
方案2:自定义画刷类,代码+XAML双端可控
如果需要更灵活的代码逻辑控制,直接继承VisualBrush写自定义类,把可变参数暴露成依赖属性:
public class HatchBrush : VisualBrush { // 背景色依赖属性 public static readonly DependencyProperty BackgroundColorProperty = DependencyProperty.Register(nameof(BackgroundColor), typeof(Color), typeof(HatchBrush), new PropertyMetadata(Colors.Gray, UpdateBrush)); // 线条颜色依赖属性 public static readonly DependencyProperty StrokeColorProperty = DependencyProperty.Register(nameof(StrokeColor), typeof(Color), typeof(HatchBrush), new PropertyMetadata(Colors.White, UpdateBrush)); // Tile尺寸依赖属性 public static readonly DependencyProperty TileSizeProperty = DependencyProperty.Register(nameof(TileSize), typeof(double), typeof(HatchBrush), new PropertyMetadata(5.0, UpdateBrush)); public Color BackgroundColor { get => (Color)GetValue(BackgroundColorProperty); set => SetValue(BackgroundColorProperty, value); } public Color StrokeColor { get => (Color)GetValue(StrokeColorProperty); set => SetValue(StrokeColorProperty, value); } public double TileSize { get => (double)GetValue(TileSizeProperty); set => SetValue(TileSizeProperty, value); } private static void UpdateBrush(DependencyObject d, DependencyPropertyChangedEventArgs e) { if (d is HatchBrush brush) brush.RefreshVisual(); } private void RefreshVisual() { var size = TileSize; var offset = size - 2; // 重新生成画刷的Visual内容 var grid = new Grid { Background = new SolidColorBrush(BackgroundColor) }; var path = new Path { Data = Geometry.Parse($"M 0 {size} L {size} 0 M -{offset} 2 L 2 -{offset} M {offset} {size+offset} L {size+offset} {offset}"), Stroke = new SolidColorBrush(StrokeColor), StrokeEndLineCap = PenLineCap.Square, RenderOptions.SetEdgeMode(this, EdgeMode.Aliased) }; grid.Children.Add(path); Visual = grid; Viewport = new Rect(0, 0, size, size); Viewbox = new Rect(0, 0, size, size); TileMode = TileMode.Tile; ViewportUnits = BrushMappingMode.Absolute; ViewboxUnits = BrushMappingMode.Absolute; } public HatchBrush() { RefreshVisual(); } }
使用时直接在XAML里实例化并设置参数:
<Rectangle Width="200" Height="200"> <Rectangle.Fill> <local:HatchBrush BackgroundColor="#46768c" StrokeColor="#80ffffff" TileSize="5" /> </Rectangle.Fill> </Rectangle>
方案3:动态资源绑定(适合简单颜色变化场景)
如果只有颜色需要变化,Tile尺寸固定,用动态资源绑定最省事:
<ResourceDictionary> <VisualBrush x:Key="BaseHatchBrush" TileMode="Tile" Viewport="0,0,5,5" ViewportUnits="Absolute" Viewbox="0,0,5,5" ViewboxUnits="Absolute"> <VisualBrush.Visual> <Grid Background="{DynamicResource HatchBg}"> <Path Data="M 0 5 L 5 0 M -2 2 L 2 -2 M 3 7 L 7 3" Stroke="{DynamicResource HatchStroke}" StrokeEndLineCap="Square" RenderOptions.EdgeMode="Aliased" /> </Grid> </VisualBrush.Visual> </VisualBrush> </ResourceDictionary>
使用时在局部资源里定义颜色:
<StackPanel> <StackPanel.Resources> <SolidColorBrush x:Key="HatchBg" Color="#46768c" /> <SolidColorBrush x:Key="HatchStroke" Color="#80ffffff" /> </StackPanel.Resources> <Rectangle Width="200" Height="200" Fill="{StaticResource BaseHatchBrush}" /> </StackPanel>
总结一下:自定义画刷类适合需要复杂逻辑或代码控制的场景;附加属性方案更适合纯XAML内灵活配置;动态资源绑定则是颜色变化场景的极简方案。
内容的提问来源于stack exchange,提问作者Shane
相关产品推荐
相关产品推荐

