You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:33:32