WindowsAppSDK icon indicating copy to clipboard operation
WindowsAppSDK copied to clipboard

Error HRESULT E_FAIL has been returned from a call to a COM component at ABI.Microsoft.UI.Xaml.IFrameworkElementOverrides.Do_Abi_MeasureOverride_0(IntPtr thisPtr, Size availableSize, Size* result)

Open OculiViridi opened this issue 3 years ago • 9 comments

Describe the bug

I've created a WinUI CustomControl library. Then I've created a UserControl (named MainUI)that is composed by 3 different CustomControls of that library. Finally, since all those controls have Size properties, I've created a MySize DependencyProperty (with enum values like Size1, Size2, etc.) on the MainUI UserControl that is used to set proportionally the sizes on all child controls.

MainUI UserControl XAML

<StackPanel Orientation="Vertical">
    <local:MyTextBlock x:Name="MessageBox"
                       Message="{x:Bind Message}"
                       DisplayMode="{x:Bind DisplayMode}">
    </local:MyTextBlock>
    <local:TriangleCursor x:Name="TriangleCursor"></local:TriangleCursor>
    <local:ThreeDotsLoading x:Name="LoadingIndicator"></local:ThreeDotsLoading>
</StackPanel>

MainUI UserControl code behind

public static readonly DependencyProperty MySizeProperty = DependencyProperty.Register(
    nameof(MySize), typeof(MainUISize), typeof(MainUI), new PropertyMetadata(MainUISize.Size1, (d, e) => ((MainUI)d).MySizeChanged(d, e)));

public MainUISize MySize
{
    get => (MainUISize)GetValue(MySizeProperty);
    set => SetValue(MySizeProperty, value);
}

private void MySizeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
    if (e.NewValue != null)
    {
        switch ((MainUISize)e.NewValue)
        {
            default:
            case MainUISize.Size1:
                MessageBox.FontSize = (int)Sizes[MainUISize.Size1][nameof(MessageBox)];
                TriangleCursor.CursorSize = (PointCollection)Sizes[MainUISize.Size1][nameof(TriangleCursor)];
                LoadingIndicator.PointSize = (int)Sizes[MainUISize.Size1][nameof(LoadingIndicator)];
                break;
            case MainUISize.Size2:
			    // TODO...
                break;
            case MainUISize.Size3:
			    // TODO...
                break;
            case MainUISize.Size4:
			    // TODO...
                break;
        }
    }
}

MainWindow XAML

See the MySize property of MainUI control

<StackPanel Orientation="Vertical"
            HorizontalAlignment="Center"
            VerticalAlignment="Center">

    <custom:MainUI x:Name="MainUI"
                   Message="HELLO!"
                   DisplayMode="AllWords"
                   MySize="Size1">
    </custom:MainUI>

</StackPanel>

I get the Exception when the code reaches the OnApplyTemplate() method of the MessageBox (type MyTextBlock) custom control of my library:

protected override void OnApplyTemplate()
{
    base.OnApplyTemplate();
    MessageContainer = GetTemplateChild(nameof(MessageContainer)) as TextBlock;
}

Steps to reproduce the bug

  1. Create a CustomControl.
  2. Create a UserControl that contains the previously created CustomControl.
  3. Add a DependencyProperty on the UserControl to be used to change the size of something (i.e.: FontSize) on the contained CustomControl.
  4. Add a PropertyChangedCallback method to the DependencyProperty to manage the change of Size value.

Expected behavior

The sizes set from the code behind of the container control on all the child controls have to be applied without exceptions.

Screenshots

image

NuGet package version

1.1.4

Packaging type

Packaged (MSIX)

Windows version

Windows 11 version 21H2 (22000)

IDE

Visual Studio 2022

Additional context

Additional Version info

Windows 11 21H2 build 22000.978 Visual Studio 2022 v17.3.4 Microsoft.WindowsAppSDK v1.1.5

OculiViridi avatar Sep 16 '22 08:09 OculiViridi

It looks like an error was thrown from app code. Do you see any exceptions being fired form your app in a custom MeasureOverride implementation?

codendone avatar Sep 16 '22 17:09 codendone

@codendone I've no custom implementation of MeasureOverride. I've tried to catch the Exception but I wasn't able to do it.

OculiViridi avatar Sep 19 '22 09:09 OculiViridi

Okay, OnApplyTemplate will typically get called during Measure, so the failure you mentioned hitting in OnApplyTemplate is bubbling out as this Measure error.

The parameter to GetTemplateChild should be the x:Name of the child. Does that match your case? Is GetTemplateChild returning null?

codendone avatar Sep 19 '22 15:09 codendone

Okay, OnApplyTemplate will typically get called during Measure, so the failure you mentioned hitting in OnApplyTemplate is bubbling out as this Measure error.

The parameter to GetTemplateChild should be the x:Name of the child. Does that match your case? Is GetTemplateChild returning null?

I think I "fixed" somehow the issue by changing the XAML...

Anyway, now the code seems to work but if I set the MySize property of the MainUI control in MainWindow.xaml, from Size1 to Size2, I don't see any change in the size of the controls, that should be bigger, but instead always remains the same default values. It seems that the code doesn't actually affect the properties it sets. The values of the Sizes dictionary are set as follows:

Dictionary<string, object> size1Settings = new Dictionary<string, object>();
size1Settings.Add(nameof(MessageBox), 24);
size1Settings.Add(nameof(TriangleCursor), new PointCollection
{
    new Windows.Foundation.Point(0, 20), new Windows.Foundation.Point(10, 0), new Windows.Foundation.Point(20, 20)
});
size1Settings.Add(nameof(LoadingIndicator), 8);

Dictionary<string, object> size2Settings = new Dictionary<string, object>();
size2Settings.Add(nameof(MessageBox), 30);
size2Settings.Add(nameof(TriangleCursor), new PointCollection
{
    new Windows.Foundation.Point(0, 25), new Windows.Foundation.Point(12.5, 0), new Windows.Foundation.Point(25, 25)
});
size2Settings.Add(nameof(LoadingIndicator), 10);

Sizes.Add(MainUISize.Size1, size1Settings);
Sizes.Add(MainUISize.Size2, size2Settings);

Then those settings are applied as shown in the switch statement inside MySizeChanged event handler method.

OculiViridi avatar Sep 21 '22 07:09 OculiViridi

Do I understand correctly that your MySizeChanged gets called and successfully runs the correct case statement and sets the correct values, but you're not seeing any of those value updates affect how the UI is rendering?

The FontSize property looks like the only built-in XAML property. Does this property work if you set it in markup? If not, perhaps something else is overriding the value, such as in the style of your <local:MyTextBlock>?

codendone avatar Sep 22 '22 02:09 codendone

Do I understand correctly that your MySizeChanged gets called and successfully runs the correct case statement and sets the correct values, but you're not seeing any of those value updates affect how the UI is rendering?

Exactly.

The FontSize property looks like the only built-in XAML property. Does this property work if you set it in markup? If not, perhaps something else is overriding the value, such as in the style of your <local:MyTextBlock>?

No, it's not working even if I set the FontSize property from markup like this:

<StackPanel Orientation="Vertical">
    <local:MyTextBlock x:Name="MessageBox"
                       Message="{x:Bind Message}"
                       DisplayMode="{x:Bind DisplayMode}"
					   FontSize="30">
</StackPanel>

The only place where there's a value set for FontSize (24) for the MyTextBox CustomControl is in its definition markup, like this:

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyControls">

    <Style x:Key="MyTextBlockDefaultStyle"
           TargetType="local:MyTextBlock" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:MyTextBlock">
                    <StackPanel Orientation="Horizontal">
                        <Border BorderThickness="0,0,0,1.5" Border.BorderBrush="#222222" Margin="0,0,0,5">
                            <TextBlock x:Name="MessageContainer" 
                                       Text="{TemplateBinding Message}" 
                                       Foreground="#222222"
                                       FontSize="24"
                                       FontStretch="Expanded"
                                       FontWeight="SemiBold"
                                       HorizontalAlignment="Center"
                                       VerticalAlignment="Bottom">
                            </TextBlock>
                        </Border>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="local:MyTextBlock"
           BasedOn="{StaticResource MyTextBlockDefaultStyle}" />
</ResourceDictionary>

I'm assuming that the CustomControl XAML definition is needed to set a default style and so some default values for some properties. Then, in other XAML windows/pages where I insert my custom control, I should be able to set or change its properties both from code or with markup. Am I wrong?

In this case, the MyTextBlock control is set to have a default FontSize=24 and the same value is associated to MainUISize.Size1 enum in the dictionary. When I set MainUISize.Size2 the FontSize value is 30.

OculiViridi avatar Sep 22 '22 13:09 OculiViridi

Try changing the FontSize="24" to be FontSize="{TemplateBinding FontSize}". The hardcoded 24 on the <TextBlock> is preventing it from inheriting the FontSize property from the MyTextBlock control (since the FontSize property is one of the few inheriting properties in XAML). Simply removing that line should fix this case, but using a TemplateBinding will explicitly declare your intent to use the property from your custom control, and this is likely what you need for your custom CursorSize and PointSize properties.

codendone avatar Sep 23 '22 05:09 codendone

@codendone Issue solved for FontSize! Thanks! 🥳

For the other 2 custom controls the problem is a little bit different...

TriangleCursor

On the TriangleCursor control, the CursorSize property is of type PointCollection, as you can see from the above switch statement code. If I try to apply the same suggestion you gave me for the FontSize property, the application crash.

image

XAML

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MyControls">

    <Style x:Key="TriangleCursorDefaultStyle" 
           TargetType="local:TriangleCursor">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:TriangleCursor">
                    <StackPanel>
                        <Canvas Name="CursorContainer" 
                                Height="20" 
                                Width="20" 
                                HorizontalAlignment="Center">
                            <Polygon x:Name="Cursor"
                                     Points="{TemplateBinding CursorSize}"
                                     Fill="Green" />
                        </Canvas>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="local:TriangleCursor"
           BasedOn="{StaticResource TriangleCursorDefaultStyle}" />
</ResourceDictionary>

Code

public sealed class TriangleCursor : Control
{
    public static readonly DependencyProperty CursorSizeProperty = DependencyProperty.Register(
        nameof(CursorSize), typeof(PointCollection), typeof(MainUI), new PropertyMetadata(null));

    public PointCollection CursorSize
    {
        get => (PointCollection)GetValue(CursorSizeProperty);
        set => SetValue(CursorSizeProperty, value);
    }

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

LoadingIndicator

The LoadingIndicator control has 3 points (type Ellipse) and I want to set both the Width/Height of the point but also the Margin property for the last 2 points: 1 that has a left margin of twice the size of the point, the other is instead half the size.

XAML

<ResourceDictionary
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:My.Controls">

    <Style x:Key="ThreeDotsLoadingDefaultStyle" 
           TargetType="local:ThreeDotsLoading" >
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="local:ThreeDotsLoading">
                    <StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
                        <Ellipse Name="Dot1"
                                 Stroke="#FF0000"
                                 StrokeThickness="1"
                                 Opacity="0"
                                 Width="{TemplateBinding PointsSize}"
                                 Height="{TemplateBinding PointsSize}"/>
                        <Ellipse Name="Dot2"
                                 Fill="#FF0000"
                                 Opacity="0"
                                 Width="{TemplateBinding PointsSize}"
                                 Height="{TemplateBinding PointsSize}" 
                                 Margin="16,0,0,0"/>
                        <Ellipse Name="Dot3"
                                 Fill="#00FF00"
                                 Opacity="0"
                                 Width="{TemplateBinding PointsSize}"
                                 Height="{TemplateBinding PointsSize}" 
                                 Margin="4,0,0,0"/>
                    </StackPanel>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style TargetType="local:ThreeDotsLoading"
           BasedOn="{StaticResource ThreeDotsLoadingDefaultStyle}" />
</ResourceDictionary>

Code

public sealed class LoadingIndicator : Control
{
    public static readonly DependencyProperty PointsSizeProperty = DependencyProperty.Register(
        nameof(PointsSize), typeof(double), typeof(MainUI), new PropertyMetadata(null));

    public double PointsSize
    {
        get => (double)GetValue(PointsSizeProperty);
        set => SetValue(PointsSizeProperty, value);
    }

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

    protected override void OnApplyTemplate()
    {
        base.OnApplyTemplate();
    }
}

I'd like to derive both those margin values (that are of type Thickness) from the same PointSize property. Is it somehow possible?

OculiViridi avatar Sep 23 '22 08:09 OculiViridi

Your custom properties both specify typeof(MainUI) as the owner type rather than the control type. Try fixing those to specify TriangleCursor/LoadingIndicator as appropriate and I think that will fix the crash. In the debugger output currently you might be seeing an error like this:

Exception thrown at 0x76227172 (KernelBase.dll) in MyApp.exe: WinRT originate error - 0x80004005 : 'The property 'CursorSize' was not found in type 'MyApp.TriangleCursor'.'.

For the Margin property values, I think the best option is to declare additional properties on LoadingIndicator for the extra Margin values you need and update these computed values when the PointsSize property changes.

codendone avatar Sep 23 '22 16:09 codendone

@codendone Everything fixed! Thanks a lot! 👍🏻

OculiViridi avatar Sep 26 '22 11:09 OculiViridi

Excellent! I'm glad you got it all working.

codendone avatar Sep 26 '22 15:09 codendone