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,517 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.
737 questions
0 comments No comments
{count} votes

Accepted answer
  1. Xiaopo Yang - MSFT 11,746 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 11,746 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 11,746 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