How do I databind a ColumnDefinition's Width or RowDefinition's Height?
Asked Answered
J

4

41

Under the View-Model-ViewModel pattern for WPF, I am trying to databind the Heights and Widths of various definitions for grid controls, so I can store the values the user sets them to after using a GridSplitter. However, the normal pattern doesn't seem to work for these particular properties.

Note: I'm posting this as a reference question that I'm posting as Google failed me and I had to work this out myself. My own answer to follow.

Johnathanjohnathon answered 29/9, 2008 at 8:18 Comment(0)
J
24

There were a number of gotchas I discovered:

  1. Although it may appear like a double in XAML, the actual value for a *Definition's Height or Width is a 'GridLength' struct.
  2. All the properties of GridLength are readonly, you have to create a new one each time you change it.
  3. Unlike every other property in WPF, Width and Height don't default their databinding mode to 'TwoWay', you have to manually set this.

Thusly, I used the following code:

private GridLength myHorizontalInputRegionSize = new GridLength(0, GridUnitType.Auto)
public GridLength HorizontalInputRegionSize
{
    get
    {
        // If not yet set, get the starting value from the DataModel
        if (myHorizontalInputRegionSize.IsAuto)
            myHorizontalInputRegionSize = new GridLength(ConnectionTabDefaultUIOptions.HorizontalInputRegionSize, GridUnitType.Pixel);
        return myHorizontalInputRegionSize;
    }
    set
    {
        myHorizontalInputRegionSize = value;
        if (ConnectionTabDefaultUIOptions.HorizontalInputRegionSize != myHorizontalInputRegionSize.Value)
        {
            // Set the value in the DataModel
            ConnectionTabDefaultUIOptions.HorizontalInputRegionSize = value.Value;
        }
        OnPropertyChanged("HorizontalInputRegionSize");
    }
}

And the XAML:

<Grid.RowDefinitions>
    <RowDefinition Height="*" MinHeight="100" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="{Binding Path=HorizontalInputRegionSize,Mode=TwoWay}" MinHeight="50" />
</Grid.RowDefinitions>
Johnathanjohnathon answered 29/9, 2008 at 8:25 Comment(4)
GridLength is specific to WPF, therefore doesn't belong in the ViewModel layer.Bismuthic
I think the belief "no WPF in ViewModel" is wrong, since WPF has a lot of constructs regarding MVVM pattern, such as the very databinding itself. You could say "GridLength is specific to the VIEW", instead (unless it is part of the domain problem, such as in data-visualization applications whose very purpuse is to layout and display things). But sure it is controversial...Outclass
@Outclass "GridLength is specific to the VIEW" is more accurate. unless your business logic is solely or partly about UI layout(design)Lasser
sorry Idisagree.. i would use attached properties.. give one daya to fullfill example where i can put my source code?Gourmand
B
53

Create a IValueConverter as follows:

public class GridLengthConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        double val = (double)value;
        GridLength gridLength = new GridLength(val);

        return gridLength;
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        GridLength val = (GridLength)value;

        return val.Value;
    }
}

You can then utilize the converter in your Binding:

<UserControl.Resources>
    <local:GridLengthConverter x:Key="gridLengthConverter" />
</UserControl.Resources>
...
<ColumnDefinition Width="{Binding Path=LeftPanelWidth, 
                                  Mode=TwoWay,
                                  Converter={StaticResource gridLengthConverter}}" />
Bismuthic answered 9/9, 2011 at 7:32 Comment(3)
If you seek a solution to very similar problem, you can find it here: #7661467Dodgem
Wpf already has a GridLengthConverter class. Would it be possible to use it as is?Outclass
@Outclass Unfortunately, GridLengthConverter implements ITypeConverter rather than IValueConverter, and so cannot be used as a WPF binding conversion.Finely
J
24

There were a number of gotchas I discovered:

  1. Although it may appear like a double in XAML, the actual value for a *Definition's Height or Width is a 'GridLength' struct.
  2. All the properties of GridLength are readonly, you have to create a new one each time you change it.
  3. Unlike every other property in WPF, Width and Height don't default their databinding mode to 'TwoWay', you have to manually set this.

Thusly, I used the following code:

private GridLength myHorizontalInputRegionSize = new GridLength(0, GridUnitType.Auto)
public GridLength HorizontalInputRegionSize
{
    get
    {
        // If not yet set, get the starting value from the DataModel
        if (myHorizontalInputRegionSize.IsAuto)
            myHorizontalInputRegionSize = new GridLength(ConnectionTabDefaultUIOptions.HorizontalInputRegionSize, GridUnitType.Pixel);
        return myHorizontalInputRegionSize;
    }
    set
    {
        myHorizontalInputRegionSize = value;
        if (ConnectionTabDefaultUIOptions.HorizontalInputRegionSize != myHorizontalInputRegionSize.Value)
        {
            // Set the value in the DataModel
            ConnectionTabDefaultUIOptions.HorizontalInputRegionSize = value.Value;
        }
        OnPropertyChanged("HorizontalInputRegionSize");
    }
}

And the XAML:

<Grid.RowDefinitions>
    <RowDefinition Height="*" MinHeight="100" />
    <RowDefinition Height="Auto" />
    <RowDefinition Height="{Binding Path=HorizontalInputRegionSize,Mode=TwoWay}" MinHeight="50" />
</Grid.RowDefinitions>
Johnathanjohnathon answered 29/9, 2008 at 8:25 Comment(4)
GridLength is specific to WPF, therefore doesn't belong in the ViewModel layer.Bismuthic
I think the belief "no WPF in ViewModel" is wrong, since WPF has a lot of constructs regarding MVVM pattern, such as the very databinding itself. You could say "GridLength is specific to the VIEW", instead (unless it is part of the domain problem, such as in data-visualization applications whose very purpuse is to layout and display things). But sure it is controversial...Outclass
@Outclass "GridLength is specific to the VIEW" is more accurate. unless your business logic is solely or partly about UI layout(design)Lasser
sorry Idisagree.. i would use attached properties.. give one daya to fullfill example where i can put my source code?Gourmand
E
11

The easiest solution is to simply use string settings for these properties so that WPF will automatically support them using GridLengthConverter without any extra work.

Ebert answered 6/5, 2015 at 17:17 Comment(1)
Really awesome. The String Properties are the best MVVM pattern for this issue.Multiversity
B
4

Another possibility, since you brought up converting between GridLength and int, is to create an IValueConverter and use it when binding to Width. IValueConverters also handle two-way binding because they have both ConvertTo() and ConvertBack() methods.

Bifrost answered 29/9, 2008 at 21:33 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.