How to handle ImageFailed event in custom control in WinUI3?

aluzi liu 486 Reputation points
2023-05-13T19:09:45.36+00:00

I create a "custom control" in WinUI3 project:

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

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

        DependencyProperty PortraitUrlProperty = DependencyProperty.Register(nameof(PortraitUrl),typeof(ImageSource),typeof(BINPersonPicture2),new PropertyMetadata(default(string), new PropertyChangedCallback(OnPortraitUrlChanged)));
        DependencyProperty PersonNameProperty = DependencyProperty.Register(nameof(PersonName), typeof(string), typeof(BINPersonPicture2), new PropertyMetadata(default(string), new PropertyChangedCallback(OnPersonNameChanged)));

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

        private static void OnPortraitUrlChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BINPersonPicture2 labelControl = d as BINPersonPicture2; //null checks omitted
            if(e.NewValue is BitmapImage bm)
            {
                Debug.WriteLine($"bm.PixelWidth:{bm.PixelWidth}");
                //If image load failed, it seems that image width is zero, but how to apply on default image on it?
                //I try this but it will cause stack overflow...
                if (bm.PixelWidth == 0)
                {                    
                    labelControl.PortraitUrl = new BitmapImage(new Uri("ms-appx:///Assets/portrait.jpg"));
                }
                else
                {
                    labelControl.PortraitUrl = bm;
                }
            }
        }

        private static void OnPersonNameChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            BINPersonPicture2 labelControl = d as BINPersonPicture2; //null checks omitted
            String s = e.NewValue as String; //null checks omitted
            labelControl.PersonName = s;
        }
    }

The xaml is:

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

I want to replace the picture to default when the PortraitUrl image load failed(or hide the Ellipse control)

I tried set PortraitUrl property in OnPortraitUrlChanged, but it will cause crash(System.StackOverflowException)....

.NET
.NET
Microsoft Technologies based on the .NET software framework.
3,980 questions
Windows App SDK
Windows App SDK
A set of Microsoft open-source libraries, frameworks, components, and tools to be used in apps to access Windows platform functionality on many versions of Windows. Previously known as Project Reunion.
801 questions
0 comments No comments
{count} votes

Accepted answer
  1. Xiaopo Yang - MSFT 12,726 Reputation points Microsoft Vendor
    2023-05-18T07:33:19.8566667+00:00

    Hello @aluzi liu,

    The solution is provided by my senior which refers to the issue Question: [C++/WinRT, Winui3]How to add handlers to a templated child of a templated control?.

    As Control.GetTemplateChild(String) Method said,

    You call GetTemplateChild to get references to objects in a controls Template after it's instantiated. The ControlTemplate is instantiated in the OnApplyTemplate method. You can use the GetTemplateChild method inside your OnApplyTemplate override and keep a reference to the objects you need.

    // Copyright (c) Microsoft Corporation and Contributors.
    // Licensed under the MIT License.
    
        public sealed class BINPersonPicture2 : Control
        {
           ...
    
            protected override void OnApplyTemplate()
            {
                var imageBrush = this.GetTemplateChild("portraitBrush") as ImageBrush;
                imageBrush.ImageOpened += ImageBrush_ImageOpened;
                imageBrush.ImageFailed += ImageBrush_ImageFailed;
                imageBrush.ImageSource = PortraitUrl;
            }
    
            private void ImageBrush_ImageFailed(object sender, ExceptionRoutedEventArgs e)
            {
                //throw new NotImplementedException();
            }
    
            private void ImageBrush_ImageOpened(object sender, RoutedEventArgs e)
            {
                //throw new NotImplementedException();
            }
        }
    

    User's image

    Hope the code will work for you.


2 additional answers

Sort by: Most helpful
  1. Xiaopo Yang - MSFT 12,726 Reputation points Microsoft Vendor
    2023-05-15T02:22:11.85+00:00

    According to my test, labelControl.PortraitUrl = new BitmapImage(new Uri("ms-appx:///Assets/portrait.jpg")); triggers OnPortraitUrlChanged which calls labelControl.PortraitUrl = new BitmapImage(new Uri("ms-appx:///Assets/portrait.jpg"));. bm.PixelWidth == 0 is always true and then a System.StackOverflowException exception is thrown.

    0 comments No comments

  2. Xiaopo Yang - MSFT 12,726 Reputation points Microsoft Vendor
    2023-05-15T07:14:47.49+00:00

    As someone reminded me, you can use ImageBrush.ImageFailed Event to get the image failed to load notification.

    And after discussing, bm.PixelWidth == 0 cannot be used to determine the image failed to load error.

    User's image

    enter image description here


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.