FlyingStreudel
FlyingStreudel

Reputation: 4464

WPF/XAML bind width of an element to a fraction of screen size

I am writing an application in C#/WPF and am trying to figure out how to databind the width of the grids column definitions to a fraction of the screen width. Is this possible? Essentially I want something like this:

Grid = 2x2
Row 1 Height = 2/3 of screen height
Row 2 Height = 1/3 of screen height
Row 1 Width = 2/3 of screen width
Row 2 Width = 1/3 of screen width

I think that this correctly binds the full width to a column definition:

<ColumnDefinition Width="{Binding ElementName=Window1, Path=Width}"/>

but what I don't know how to do is perform an operation on the value it gets through the databinding... is this even possible? I feel like this is something I should be able to code into the XAML and not have to implement programmatically but I have little experience with UI design :( I would want something like:

<ColumnDefinition Width="{Binding ElementName=Window1, Path=Width} * 2 / 3"/>

but that is invalid

Should I just be writing a function to re-layout UI elements whenever the screen resizes? I feel like that is redundant... or is there some easy way of doing this that I don't know about? Any input welcome! Thanks!

Upvotes: 4

Views: 3588

Answers (2)

HDL_CinC_Dragon
HDL_CinC_Dragon

Reputation: 303

I ran into a situation where I needed to bind the width of a control to a fraction of the height of another and I don't think there's a handy built-in way of dividing a bound property like that. I ended up making a converter that would divide a bound value by the converter parameter to solve this issue without resorting to code-behind and figured I'd share it in case it helps save someone else some time. I'm posting it here because this post was the first result when I was searching if there was a cleaner way.

Converter class:

[ValueConversion(typeof(double), typeof(double))]
public class DoubleDivisionConverter : System.Windows.Data.IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if(value == null) { return 0.0; }
        if(parameter == null) { return value; }

        double param;
        if(Double.TryParse(parameter.ToString(), out param))
        {
            if(param == 0) { return 0.0; }
            return (double)value / param;
        }
        else
        {
            throw new ArgumentException("Could not parse converter parameter as double.");
        }
    }

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {
        if(value == null) { return 0.0; }
        if(parameter == null) { return value; }

        double param;
        if(Double.TryParse(parameter.ToString(), out param))
        {
            if(param == 0) { return 0.0; }
            return (double)value * param;
        }
        else
        {
            throw new ArgumentException("Could not parse converter parameter as double.");
        }
    }
}

XAML: (Converter used on UniformGrid Width property)

<Window.Resources>
    <converters:DoubleDivisionConverter x:Key="DoubleDivisionConverter"/>
</Window.Resources>

<Grid x:Name="sampleGrid">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
        <ColumnDefinition Width="Auto"/>
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <Grid.Resources>
        <Style TargetType="TextBlock" BasedOn="{StaticResource {x:Type TextBlock}}">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
            <Setter Property="FontSize" Value="18"/>
            <Setter Property="FontWeight" Value="Bold"/>
        </Style>
    </Grid.Resources>

    <Border x:Name="rSample" Grid.Column="0" Grid.Row="0" Background="PaleVioletRed">
        <TextBlock>
            <TextBlock.Inlines>
                <Run Text="{Binding ElementName=rSample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=rSample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
            </TextBlock.Inlines>
        </TextBlock>
    </Border>

    <!-- Set the UniformGrids width to 1/3 the height of the parent grid using the converter -->
    <UniformGrid Grid.Column="1" Grid.Row="0" Columns="1"
    Width="{Binding ElementName=sampleGrid, Path=ActualHeight, Converter={StaticResource DoubleDivisionConverter}, ConverterParameter=3, Mode=OneWay}">
        <Border x:Name="gSample" Background="LightGreen">
            <TextBlock>
                <TextBlock.Inlines>
                    <Run Text="{Binding ElementName=gSample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=gSample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
                </TextBlock.Inlines>
            </TextBlock>
        </Border>
        <Border x:Name="ySample" Background="LightGoldenrodYellow">
            <TextBlock>
                <TextBlock.Inlines>
                    <Run Text="{Binding ElementName=ySample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=ySample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
                </TextBlock.Inlines>
            </TextBlock>
        </Border>
        <Border x:Name="bSample" Background="LightBlue">
            <TextBlock>
                <TextBlock.Inlines>
                    <Run Text="{Binding ElementName=bSample, Path=ActualWidth, StringFormat={}{0:N1}px, Mode=OneWay}"/><Run Text=", "/><Run Text="{Binding ElementName=bSample, Path=ActualHeight, StringFormat={}{0:N1}px, Mode=OneWay}"/>
                </TextBlock.Inlines>
            </TextBlock>
        </Border>
    </UniformGrid>
</Grid>

Result:

Result

Upvotes: 1

Quartermeister
Quartermeister

Reputation: 59169

It sounds like you just want to use star sizing:

<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="2*"/>
        <RowDefinition Height="1*"/>
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="2*"/>
        <ColumnDefinition Width="1*"/>
    </Grid.ColumnDefinitions>

This will always give the first row 2/3 of the height and the second row 1/3 of the height, and the first column 2/3 of the width and the second column 1/3 of the width. It will be based on the size of the Grid, but if the Grid is the only child of the Window then that will be the same as the size of the Window.

Upvotes: 5

Related Questions