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

自定义WPF XAML图片绑定机制解析及ImageBrush遮罩扩展咨询

动态XAML绑定BitmapSource的原理解析与OpacityMask扩展方案

一、原代码工作原理解析

1. 核心:WPF附加属性机制

XamlImageStore定义了ImageId附加属性,这是WPF允许给任意DependencyObject添加额外属性的特性,这里专门用来建立Image控件与内存中位图的关联。

2. 静态字典存储动态位图

静态构造函数初始化了全局静态字典images,以字符串ID为键存储BitmapSource对象。通过AddImage方法,可以提前将代码中生成的动态位图存入字典,供后续XAML控件调用。

3. 属性变更回调完成绑定

当动态生成的XAML被XamlReader.Load解析时,Image控件的local:XamlImageStore.ImageId属性被设置,触发ImageIdChanged回调:

  • 将目标对象转换为Image控件
  • 根据新的ImageID从字典中取出对应的BitmapSource
  • 直接赋值给Image.Source,完成位图与控件的绑定。

二、扩展支持OpacityMask的实现方案

1. 扩展XamlImageStore类

新增MaskId附加属性,用于关联Image的OpacityMask对应的遮罩位图,并实现属性变更回调:

public class XamlImageStore
{
    // 保留原有的静态字典、ImageId属性及相关方法
    private static Dictionary<string, BitmapSource> images;

    static XamlImageStore()
    {
        images = new Dictionary<string, BitmapSource>();
    }

    internal static void AddImage(string imageID, BitmapSource image)
    {
        images[imageID] = image;
    }

    // 原ImageId属性相关代码
    public static string GetImageId(DependencyObject obj) => (string)obj.GetValue(ImageIdProperty);
    public static void SetImageId(DependencyObject obj, string value) => obj.SetValue(ImageIdProperty, value);
    public static readonly DependencyProperty ImageIdProperty =
        DependencyProperty.RegisterAttached("ImageId", typeof(string), typeof(XamlImageStore), new UIPropertyMetadata("0", ImageIdChanged));

    private static void ImageIdChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (obj is Image target && images.TryGetValue(e.NewValue.ToString(), out var bitmap))
        {
            target.Source = bitmap;
        }
    }

    // 新增MaskId附加属性
    public static string GetMaskId(DependencyObject obj) => (string)obj.GetValue(MaskIdProperty);
    public static void SetMaskId(DependencyObject obj, string value) => obj.SetValue(MaskIdProperty, value);

    public static readonly DependencyProperty MaskIdProperty =
        DependencyProperty.RegisterAttached("MaskId", typeof(string), typeof(XamlImageStore), new UIPropertyMetadata("0", MaskIdChanged));

    private static void MaskIdChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
    {
        if (obj is Image target && images.TryGetValue(e.NewValue.ToString(), out var maskBitmap))
        {
            // 创建ImageBrush并关联遮罩位图,赋值给OpacityMask
            target.OpacityMask = new ImageBrush(maskBitmap);
        }
    }
}

2. 修改动态XAML生成逻辑

在生成Image元素时,添加MaskId属性,关联预存的遮罩位图ID:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    // 主图ID与位图
    string customImageID = "ABCD";
    XamlImageStore.AddImage(customImageID, GetBitmap());

    // 遮罩ID与位图
    string maskImageID = "MASK_CIRCLE";
    XamlImageStore.AddImage(maskImageID, GetMaskBitmap());

    MemoryStream ms = new MemoryStream();
    XmlTextWriter xamlWriter = new XmlTextWriter(ms, Encoding.UTF8);

    xamlWriter.WriteStartElement("Canvas");
    xamlWriter.WriteAttributeString("xmlns", "http://schemas.microsoft.com/xps/2005/06");
    xamlWriter.WriteAttributeString("xmlns:local", "clr-namespace:DynamicXamlImage;assembly=DynamicXamlImage");

    xamlWriter.WriteStartElement("Image");
    xamlWriter.WriteAttributeString("local:XamlImageStore.ImageId", customImageID);
    // 新增遮罩ID属性
    xamlWriter.WriteAttributeString("local:XamlImageStore.MaskId", maskImageID);
    xamlWriter.WriteEndElement();

    xamlWriter.WriteEndElement();
    xamlWriter.Flush();
    ms.Position = 0;

    Canvas canvas = (Canvas)XamlReader.Load(ms);
    appGrid.Children.Add(canvas);
}

// 示例遮罩位图生成方法
private WriteableBitmap GetMaskBitmap()
{
    WriteableBitmap wb = new WriteableBitmap(20, 20, 96, 96, PixelFormats.Gray8, null);
    byte[] pixels = new byte[20 * 20];
    
    // 生成圆形遮罩:中心透明,边缘不透明
    for (int y = 0; y < 20; y++)
    {
        for (int x = 0; x < 20; x++)
        {
            double distance = Math.Sqrt(Math.Pow(x - 10, 2) + Math.Pow(y - 10, 2));
            pixels[y * 20 + x] = (byte)(distance > 8 ? 255 : 0);
        }
    }
    
    wb.WritePixels(new Int32Rect(0, 0, 20, 20), pixels, 20, 0);
    return wb;
}

3. 关键注意点

  • 遮罩位图推荐使用Gray8格式:灰度值直接对应透明度(0=完全透明,255=完全不透明)。
  • 如果需要区分主图和遮罩,可单独创建masks字典存储遮罩位图,避免与主图ID冲突。

内容的提问来源于stack exchange,提问作者Surubelnita

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 23:06:57