windows-community-toolkit

CommunityToolkit - Problem centering Image inside in a SettingsCard


(note - reposting from my question on the CommunityToolkit github repo which doesn't seem very active - see https://github.com/CommunityToolkit/Windows/discussions/459 for original)


I'm currently struggling with getting an Image to centre inside a SettingsCard, so I'm hoping someone can give me some pointers...

I'm kind of new to the Community Toolkit controls, and tbh I'm kind of new to WinUI / Xaml in general so I may be trying to do something daft - let me know if there's better way of doing what I'm doing.

Desired Layout

This is what I'm hoping to do... I have a SettingsExpander with a nested SettingsCard that contains an Image control. I'd like the Image to be centred horizontally within the SettingsCard:

enter image description here

(arrowed lines just for illustration - not part of the UI)

Actual Layout

Here's the Xaml I'm using, which is as close as I've been able to get to my desired layout.

<UserControl
    ... snip ...
    xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
    >

    <controls:SettingsGroup>

        <tkcontrols:SettingsExpander
            x:Uid="MouseUtils_MouseJump_Appearance"
            HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
            IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}"
            IsExpanded="False">
            <tkcontrols:SettingsExpander.Items>

                <tkcontrols:SettingsCard
                    HorizontalAlignment="Center">
                    <Image Source="{x:Bind Path=ViewModel.MouseJumpPreviewImage, Mode=OneWay}" />
                </tkcontrols:SettingsCard>

            </tkcontrols:SettingsExpander.Items>
        </tkcontrols:SettingsExpander>

    </controls:SettingsGroup>

</UserControl>

However, this rendered as:

enter image description here

Problems

The problems I have are:

This UserControl is part of a much bigger project I'm contributing to, so it's possible there's some global configuration settings being applied that I'm unaware of - I can give links to source if needed.

Any suggestions on what to try to fix this, or an alternative approach to achieve the same thing would be welcome...


Solution

  • I managed to put a workaround together - it's not pretty but it works.

    The first part was to wrap the image in a Grid and StackPanel to allow it to centre property inside the "content" area of the SettingsCard:

    <UserControl
        ... snip ...
        xmlns:tkcontrols="using:CommunityToolkit.WinUI.Controls"
        >
    
        <controls:SettingsGroup>
    
            <tkcontrols:SettingsExpander
                x:Uid="MouseUtils_MouseJump_Appearance"
                HeaderIcon="{ui:FontIcon Glyph=&#xEB3C;}"
                IsEnabled="{x:Bind ViewModel.IsMouseJumpEnabled, Mode=OneWay}"
                IsExpanded="False">
                <tkcontrols:SettingsExpander.Items>
    
                    <tkcontrols:SettingsCard
                        x:Name="MouseUtils_MouseJump_PreviewImage"
                        MinHeight="300"
                        MaxHeight="300"
                        Loaded="PreviewImage_Loaded">
                        <Grid
                            MinHeight="283"
                            MaxHeight="283"
                            HorizontalAlignment="Stretch"
                            VerticalAlignment="Stretch">
                            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
                                <Image Source="{x:Bind Path=ViewModel.MouseJumpPreviewImage, Mode=OneWay}" Stretch="None" />
                            </StackPanel>
                        </Grid>
    
                </tkcontrols:SettingsExpander.Items>
            </tkcontrols:SettingsExpander>
    
        </controls:SettingsGroup>
    
    </UserControl>
    

    The second part was to inspect the source code for the CommunityToolkit library and find the names of the private / internal controls so I could use get references to them and hide them - this expands the "content" area to fill the entire SettingsCard:

            private void PreviewImage_Loaded(object sender, RoutedEventArgs e)
            {
                bool TryFindFrameworkElement(SettingsCard settingsCard, string partName, out FrameworkElement result)
                {
                    result = settingsCard.FindDescendants()
                        .OfType<FrameworkElement>()
                        .FirstOrDefault(
                            x => x.Name == partName);
                    return result is not null;
                }
    
                /*
                    apply a variation of the "Left" VisualState for SettingsCards
                    to center the preview image in the true center of the card
                    see https://github.com/CommunityToolkit/Windows/blob/9c7642ff35eaaa51a404f9bcd04b10c7cf851921/components/SettingsControls/src/SettingsCard/SettingsCard.xaml#L334-L347
                */
    
                var settingsCard = (SettingsCard)sender;
    
                var partNames = new List<string>
                {
                    "PART_HeaderIconPresenterHolder",
                    "PART_DescriptionPresenter",
                    "PART_HeaderPresenter",
                    "PART_ActionIconPresenter",
                };
                foreach (var partName in partNames)
                {
                    if (!TryFindFrameworkElement(settingsCard, partName, out var element))
                    {
                        continue;
                    }
    
                    element.Visibility = Visibility.Collapsed;
                }
    
                if (TryFindFrameworkElement(settingsCard, "PART_ContentPresenter", out var content))
                {
                    Grid.SetRow(content, 1);
                    Grid.SetColumn(content, 1);
                    content.HorizontalAlignment = HorizontalAlignment.Center;
                }
            }
    

    This results in the following, with the image properly centred in the otherwise-empty SettingsCard:

    enter image description here

    There's a danger this might break if the internals of the SettingsCard library change, but it works for the time being, and I couldn't see a way to do it using just the public interface...