My goal is to save a GridSplitter
position for later recall. The splitter is inside a Grid
control that has three columns defined thusly:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding GridPanelWidth, Mode=TwoWay}" />
<ColumnDefinition Width="3" /> <!--splitter itself is in this column-->
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
The property GridPanelWidth
is defined this way in the view model:
private GridLength _gridPanelWidth = new GridLength(1, GridUnitType.Star);
public GridLength GridPanelWidth
{
get { return _gridPanelWidth; }
set
{
if (_gridPanelWidth != value)
SetProperty(ref _gridPanelWidth, value, () => GridPanelWidth);
}
}
The problem I am having is that when the splitter is moved, the binding updates only the Double
(Value) component of the bound property, but not the GridUnitType
part of it.
Example: the property defaults to 1*
. User drags the splitter and the value becomes 354*
, instead of just 354
. On restoring the value, then, it's huge (354 times, not 354 pixels).
Why is this happening, and what would you do about it?
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="{Binding GridPanelWidth, Mode=TwoWay}" />
<ColumnDefinition Width="4" />
<!--splitter itself is in this column-->
<ColumnDefinition x:Name="RightColumn" Width="2*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Border
BorderBrush="Gray"
BorderThickness="1"
Grid.Column="0"
Grid.Row="0"
/>
<GridSplitter
Background="SteelBlue"
ResizeBehavior="PreviousAndNext"
ResizeDirection="Columns"
VerticalAlignment="Stretch"
HorizontalAlignment="Stretch"
ShowsPreview="False"
Grid.Column="1"
Grid.Row="0"
/>
<Border
BorderBrush="Gray"
BorderThickness="1"
Grid.Column="2"
Grid.Row="0"
/>
<StackPanel
Grid.Row="1"
Grid.ColumnSpan="3"
Grid.Column="0"
>
<TextBlock>
<Run>GridPanelWidth: </Run>
<Run Text="{Binding GridPanelWidth.Value, Mode=OneWay}" />
<Run Text="{Binding GridPanelWidth.GridUnitType, Mode=OneWay}" />
</TextBlock>
<TextBlock>
<Run>RightColumn.Width: </Run>
<Run Text="{Binding Width.Value, ElementName=RightColumn, Mode=OneWay}" />
<Run Text="{Binding Width.GridUnitType, ElementName=RightColumn, Mode=OneWay}" />
</TextBlock>
</StackPanel>
</Grid>
Screenshot 1:
Screenshot 2:
Screenshot 3:
Res ipsa loquitor, as far as I'm concerned, but just to be on the safe side:
Because the parent may be resized, the grid splitter changes the ratio between the two columns, while preserving the GridUnitType.Star
for each one so that when the parent is resized, the ratio will naturally remain constant. This preserves the intent of the initial Width values in the XAML.
Width.Value
for the left column turns out to be identical to the left Border's ActualWidth
, and the same holds true for the right column. You'll have to grab both and save the ratio.
I find Grid
/GridSplitter
a bit overengineered for everyday use when all I want is Yet Another Navigation Pane, so I recently wrote a SplitterControl
that has two content properties and sets up the grid and splitter, with styling, in the template. I hadn't gotten around to making the split ratio persistent, so I did that just now.
What I did was rather painful because the control is configurable, and the code isn't all that great, but I can share if you're interested.
The business end is simple:
When a column resizes, set a flag to block recursion and
PaneRatio = _PART_ContentColumn.Width.Value / _PART_NavColumn.Width.Value;
When PaneRatio
changes, if it wasn't set by the column size change handler
_PART_NavColumn.Width = new GridLength(1, GridUnitType.Star);
_PART_ContentColumn.Width = new GridLength(PaneRatio, GridUnitType.Star);
In practice, the navigator/content columns can be swapped, or they can be rows instead. Both of those are done by switching templates on a HeaderedContentControl
that's a child of the split control template.