badnun872
badnun872

Reputation: 307

Autosize a Button in a Grid Cell while keeping ratio in XAML WPF

I am aligning the controls (Buttons) of my UI using a Grid.

It is important, that the size of of the buttons will fill the cell as much as possible AND the ratio is kept, so that my buttons remain a round circle.

I have chosen a Grid, as controlling the button size by the Grid cell on a window resize size seems like a good choice to avoid coding.

I am now struggling with keeping the buttons ratio though. The only way that worked so far, was wrapping my Button in a Viewbox with Stretch="Uniform". This is not a viable way tho, as the buttons content will be stretched/zoomed too and thus making the text way too big, see screenshot

I guess another solution might be using an image or svg for the round button instead of a background with border radius, as these will be transformed in a way to keep their ratio automatically? If I did that, i'd have to find a way to add text on top though. Another possibility might be to make sure table cells always have the same height/width? Seems like thats not easy to do tho.

My preferable solution, if possible, would be to keep using background with a border radius.

...
    x:Key="RoundHoverButtonStyle"
    BasedOn="{StaticResource StandardButtonStyle}"
    TargetType="{x:Type Button}">
    <Style.Resources>
        <Style TargetType="Border">
            <Setter Property="CornerRadius" Value="100" />
        </Style>
    </Style.Resources>
...
<Grid Grid.Column="0" ShowGridLines="True">
    <Grid.RowDefinitions>
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>
    <Viewbox Stretch="Uniform">
        <Button
            x:Name="Button0"
            Grid.Row="1"
            Grid.Column="0"
            HorizontalAlignment="Center"
            VerticalAlignment="Center"
            Background="Black"
            Content="{Binding Path=ButtonConfigModel.ButtonDict[Button0].DisplayName}"
            Foreground="White"
            Style="{StaticResource RoundHoverButtonStyle}"
            Tag="{Binding Path=ButtonConfigModel.ButtonDict[Button0].VirtualKey}"
            Visibility="{Binding Path=ButtonConfigModel.ButtonDict[Button0].IsEnabled, Converter={StaticResource BoolToVis}}" />
    </Viewbox>
...

Upvotes: 1

Views: 1111

Answers (1)

Jeff
Jeff

Reputation: 654

I think what will work for you is working with a MultiValue converter that is then associated with the button's Height and Width. And the Button's Height and Width will be Multi-Bound to a surrounding DockPanel. This sounds confusing - and it is a bit. But I think the below will clarify. I am sure there is more than one way to do this but this works for me:

First, in your .cs file for your window, add this MultiValueConverter - I place mine at the end of the file.

[ValueConversion(typeof(double), typeof(double))]
public class HeightWidthConverter : IMultiValueConverter
{
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        double width = (double) values[0];
        double height = (double) values[1];

        return height - width <= 0 ? height : width;
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
    {
        return null;
    }
}

Note that this value converter is simplified - you should add some checks to make sure the correct information is provided to prevent getting an Exception like InvalidCast or some other Exception.

Then, in the Window.Resources section you will need to add a reference to this value converter:

<local:HeightWidthConverter x:Key="HeightWidthConverter"/>

This assumes that you have have a xmlns attribute called local that references your window's namespace. It would look similar to this:

xmlns:local="clr-namespace:MyApp"

lastly, each button would get wrapped by a DockPanel and the button's height and width will get multi-bound to the actual height and actual width of the DockPanel like this:

<Grid
        Grid.Column="1"
        Grid.Row="1"
        HorizontalAlignment="Stretch"
        VerticalAlignment="Stretch"
        >

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="*" />
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="*"/>


    </Grid.RowDefinitions>

    <DockPanel x:Name="dp11"
                Grid.Column="1"
                Grid.Row="1"    
                HorizontalAlignment="Stretch"
                VerticalAlignment="Stretch"
            >
        <Button x:Name="Button11"
                Content="Hello"
                Background="Black"
                Foreground="White"
                FontSize="10"
                Style="{DynamicResource RoundHoverButtonStyle}">

            <Button.Width>
                <MultiBinding Converter="{StaticResource HeightWidthConverter}">
                    <Binding ElementName="dp11" Path="ActualHeight" />
                    <Binding ElementName="dp11" Path="ActualWidth" />
                </MultiBinding>
            </Button.Width>
            <Button.Height>
                <MultiBinding Converter="{StaticResource HeightWidthConverter}">
                    <Binding ElementName="dp11" Path="ActualHeight" />
                    <Binding ElementName="dp11" Path="ActualWidth" />
                </MultiBinding>
            </Button.Height>
        </Button></DockPanel>

    <Grid.Resources>
        <Style x:Key="RoundHoverButtonStyle"
                TargetType="Button"
                >
            <Style.Resources>
                <Style TargetType="Border">
                    <Setter Property="CornerRadius" Value="100" />
                </Style>
            </Style.Resources>

        </Style>
    </Grid.Resources>
</Grid>

The trick here is that the MultiConverter gets both the actual width and actual height of the DockPanel. It then determines which is smaller and returns that value. And, since it does the same calculation for both the Button's height and width, the button will have equal height and width values. In addition, since the DockPanel re-sizes with the surrounding grid (assuming that ability is implemented), the Button will also automatically resize.

Using this technique, each button will have the same height & width (i.e. be round).

The reason to wrap the button in a DockPanel is this will get rendered / sized before the button and therefore the button will have size values to work with (this is a very large simplification by the way). Also, I have found that a DockPanel does a better job of filling itself within the available space than a StackPanel.

Upvotes: 1

Related Questions