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

WinUI3中窗口含多个自定义控件时部分控件无法正常工作

自定义WinUI3控件多实例显示异常问题分析与解决

问题场景

在WinUI3项目中实现了自定义控件BINPersonPicture2,控件包含通过Fill属性显示图片的Ellipse和显示名称的TextBlock。当窗口中仅使用一个该控件时功能正常,但添加两个及以上控件时,仅最后一个控件能正常显示图片,其余控件图片区域为空。

控件样式XAML代码

<Style TargetType="local:BINPersonPicture2" >
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:BINPersonPicture2">
                <Grid 
                    Background="{TemplateBinding Background}"
                    BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                    <Ellipse>
                        <Ellipse.Fill>
                            <ImageBrush x:Name="portraitBrush" ImageSource="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=PortraitUrl}" Stretch="UniformToFill" />
                        </Ellipse.Fill>
                    </Ellipse>
                    <TextBlock Text="{TemplateBinding PersonName}" FontSize="14" HorizontalAlignment="Center" VerticalAlignment="Center"/>
                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

控件C#代码(原错误版本)

public sealed class BINPersonPicture2 : Control
{
    bool errorPlaceholderSetted = false;
    public ImageSource PortraitUrl
    {
        get => (ImageSource)GetValue(PortraitUrlProperty);
        set => SetValue(PortraitUrlProperty, value);
    }

    public string PersonName
    {
        get => (string)GetValue(PersonNameProperty);
        set => SetValue(PersonNameProperty, value);
    }

    public ImageSource ErrorPlaceholder
    {
        get => (ImageSource)GetValue(ErrorPlaceholderProperty);
        set => SetValue(ErrorPlaceholderProperty, value);
    }
    // 错误:依赖属性定义为实例字段
    DependencyProperty PortraitUrlProperty = DependencyProperty.Register(nameof(PortraitUrl), typeof(ImageSource), typeof(BINPersonPicture2), new PropertyMetadata(default(ImageSource), new PropertyChangedCallback(OnPortraitUrlChanged)));
    DependencyProperty PersonNameProperty = DependencyProperty.Register(nameof(PersonName), typeof(string), typeof(BINPersonPicture2), new PropertyMetadata(default(string), new PropertyChangedCallback(OnPersonNameChanged)));
    DependencyProperty ErrorPlaceholderProperty = DependencyProperty.Register(nameof(ErrorPlaceholder), typeof(ImageSource), typeof(BINPersonPicture2), new PropertyMetadata(default(ImageSource), new PropertyChangedCallback(OnErrorPlaceholderChanged)));

    public BINPersonPicture2()
    {
        this.DefaultStyleKey = typeof(BINPersonPicture2);
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        Debug.WriteLine("OnApplyTemplate running..........");            
        if (GetTemplateChild("portraitBrush") is ImageBrush ThisBrush)
        {
            ThisBrush.ImageFailed += (s, e) =>
            {
                if (!errorPlaceholderSetted)
                {
                    errorPlaceholderSetted = true;
                    if (ErrorPlaceholder != null)
                        ThisBrush.ImageSource = ErrorPlaceholder;
                }
            };
        }
    }

    private static void OnPortraitUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("OnPortraitUrlChanged running....................");
    }

    private static void OnPersonNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }

    private static void OnErrorPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }
}

控件使用代码

<local:BINPersonPicture2 Background="MediumSeaGreen" PortraitUrl="{x:Bind TestImageUrl}" Width="100" Height="100" HorizontalAlignment="Right" VerticalAlignment="Bottom"
                         PersonName="Lily" ErrorPlaceholder="/Assets/portrait.jpg"/>
<local:BINPersonPicture2 Background="Orange" PortraitUrl="{x:Bind TestImageUrl2}" Width="100" Height="100" HorizontalAlignment="Left" VerticalAlignment="Bottom"
                         PersonName="Tom" ErrorPlaceholder="/Assets/portrait.jpg"/>

窗口绑定数据代码

public string TestImageUrl { get; set; }= "https://images.pexels.com/photos/347926/pexels-photo-347926.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1";
public string TestImageUrl2 { get; set; } = "https://images.pexels.com/photos/672101/pexels-photo-672101.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1";

异常显示效果

控件显示异常

原因分析

核心问题是依赖属性的定义不符合WinUI规范:

  • WinUI/WPF中,依赖属性必须定义为静态只读字段,因为依赖属性系统需要全局唯一的标识符来管理每个属性的元数据和绑定关系。
  • 原代码中将PortraitUrlProperty等三个依赖属性定义为实例字段,导致每个控件实例都会重新注册一次依赖属性,后注册的属性会覆盖之前的注册信息,最终只有最后一个控件的属性绑定能正常生效。

解决方案

将三个依赖属性的定义修改为静态只读字段,修正后的控件C#代码如下:

public sealed class BINPersonPicture2 : Control
{
    bool errorPlaceholderSetted = false;

    public ImageSource PortraitUrl
    {
        get => (ImageSource)GetValue(PortraitUrlProperty);
        set => SetValue(PortraitUrlProperty, value);
    }

    public string PersonName
    {
        get => (string)GetValue(PersonNameProperty);
        set => SetValue(PersonNameProperty, value);
    }

    public ImageSource ErrorPlaceholder
    {
        get => (ImageSource)GetValue(ErrorPlaceholderProperty);
        set => SetValue(ErrorPlaceholderProperty, value);
    }

    // 修正为静态只读字段,符合依赖属性规范
    public static readonly DependencyProperty PortraitUrlProperty = 
        DependencyProperty.Register(nameof(PortraitUrl), typeof(ImageSource), typeof(BINPersonPicture2), 
            new PropertyMetadata(default(ImageSource), new PropertyChangedCallback(OnPortraitUrlChanged)));

    public static readonly DependencyProperty PersonNameProperty = 
        DependencyProperty.Register(nameof(PersonName), typeof(string), typeof(BINPersonPicture2), 
            new PropertyMetadata(default(string), new PropertyChangedCallback(OnPersonNameChanged)));

    public static readonly DependencyProperty ErrorPlaceholderProperty = 
        DependencyProperty.Register(nameof(ErrorPlaceholder), typeof(ImageSource), typeof(BINPersonPicture2), 
            new PropertyMetadata(default(ImageSource), new PropertyChangedCallback(OnErrorPlaceholderChanged)));

    public BINPersonPicture2()
    {
        this.DefaultStyleKey = typeof(BINPersonPicture2);
    }

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
        Debug.WriteLine("OnApplyTemplate running..........");            
        if (GetTemplateChild("portraitBrush") is ImageBrush ThisBrush)
        {
            ThisBrush.ImageFailed += (s, e) =>
            {
                if (!errorPlaceholderSetted)
                {
                    errorPlaceholderSetted = true;
                    if (ErrorPlaceholder != null)
                        ThisBrush.ImageSource = ErrorPlaceholder;
                }
            };
        }
    }

    private static void OnPortraitUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Debug.WriteLine("OnPortraitUrlChanged running....................");
    }

    private static void OnPersonNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }

    private static void OnErrorPlaceholderChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
    }
}

修改后,每个控件实例都会使用同一个全局注册的依赖属性,绑定关系不会被覆盖,多个控件实例即可正常显示。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 15:27:56