WPF GridSplitter with Three Columns: How to Fix Unintended Column Shrinking When Resizing (Relative Sizing Issue)
The WPF Grid control is a powerful layout tool, allowing developers to create flexible, responsive UIs with rows and columns. When combined with GridSplitter, users can dynamically resize columns (or rows) at runtime, enhancing interactivity. However, a common frustration arises when working with three or more columns: after resizing with GridSplitter, columns may shrink unexpectedly, especially when the window itself is resized. This issue stems from how WPF handles "relative sizing" (star sizing) and the default behavior of GridSplitter.
In this blog, we’ll demystify why unintended column shrinking occurs, break down the root cause, and provide a step-by-step solution to ensure columns maintain their relative proportions—even after resizing with GridSplitter and adjusting the window size.
Table of Contents#
- Understanding the Problem: Unintended Column Shrinking
- Root Cause: How WPF Grid and GridSplitter Handle Sizing
- Step-by-Step Solution: Fixing Relative Sizing
- Advanced Scenarios: Handling Edge Cases
- Conclusion
- References
Understanding the Problem: Unintended Column Shrinking#
Let’s start with a typical scenario. Suppose you define a Grid with three columns, each using star sizing (Width="*") to distribute space equally. You add GridSplitter controls between the columns to let users resize them. Initially, everything works: columns split the available space evenly, and dragging a GridSplitter resizes adjacent columns.
The problem occurs when you resize the window after using the GridSplitter. For example:
- Start with columns sized
1*,1*,1*(equal width). - Drag the first
GridSplitterto make the first column wider. - Resize the window to be larger or smaller.
Instead of scaling proportionally, the columns may shrink unexpectedly, with some columns becoming too narrow (or even invisible). This breaks the user’s intended layout and feels unpolished.
Example: Problematic XAML#
Here’s a minimal example that reproduces the issue:
<Grid x:Name="mainGrid">
<!-- Three columns with equal star sizing -->
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" /> <!-- Column 0 -->
<ColumnDefinition Width="*" /> <!-- Column 1 -->
<ColumnDefinition Width="*" /> <!-- Column 2 -->
</Grid.ColumnDefinitions>
<!-- GridSplitter between Column 0 and 1 -->
<GridSplitter Grid.Column="0" Width="5" HorizontalAlignment="Right"
VerticalAlignment="Stretch" Background="Gray"/>
<!-- GridSplitter between Column 1 and 2 -->
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Right"
VerticalAlignment="Stretch" Background="Gray"/>
<!-- Sample content for visibility -->
<Border Grid.Column="0" Background="LightBlue" Child="Column 0"/>
<Border Grid.Column="1" Background="LightGreen" Child="Column 1"/>
<Border Grid.Column="2" Background="LightPink" Child="Column 2"/>
</Grid>What happens: After dragging a GridSplitter, resize the window. Columns will not maintain their relative proportions—some may shrink drastically.
Root Cause: How WPF Grid and GridSplitter Handle Sizing#
To fix the issue, we need to understand two key concepts: star sizing and GridSplitter’s default behavior.
Star Sizing (*) in WPF Grid#
Star sizing (Width="*") distributes available space proportionally among columns (or rows). For example:
1*,1*,1*: Each column gets 1/3 of the total space.2*,1*,1*: The first column gets 2/4 (50%), others 1/4 (25% each).
Crucially, star sizing is dynamic: if the window resizes, columns scale proportionally to their star values.
GridSplitter’s Default Behavior#
GridSplitter lets users drag to resize columns. By default, when you drag a GridSplitter, it:
- Adjusts the width of the adjacent columns (e.g., left and right columns for a horizontal splitter).
- Overrides star sizing by setting the columns’
Widthto absolute pixel values (e.g.,150instead of*).
This is the root of the problem! After dragging, columns no longer use star sizing—they have fixed pixel widths. When the window resizes, these fixed widths don’t scale, leading to unintended shrinking or overflow.
Step-by-Step Solution: Fixing Relative Sizing#
To maintain relative sizing even after using GridSplitter, we need to:
- Prevent columns from shrinking below a minimum width.
- Ensure columns use star sizing after resizing with
GridSplitter, so they scale proportionally when the window resizes.
Step 1: Define the Grid with Three Columns#
Start with a Grid and three columns. Use star sizing initially (Width="*") for equal distribution.
Step 2: Add GridSplitters for Resizing#
Add GridSplitter controls between columns. Set ResizeDirection="Columns" (default for horizontal splitters) and ResizeBehavior="PreviousAndNext" to ensure the splitter adjusts both adjacent columns.
Step 3: Prevent Excessive Shrinking with MinWidth#
Set MinWidth on columns to define a lower bound. This prevents columns from shrinking to invisibility when resized.
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="col0" Width="*" MinWidth="50"/> <!-- Min 50px -->
<ColumnDefinition x:Name="col1" Width="*" MinWidth="50"/>
<ColumnDefinition x:Name="col2" Width="*" MinWidth="50"/>
</Grid.ColumnDefinitions>Step 4: Maintain Relative Sizing with DragCompleted Event#
The critical fix: After the user finishes dragging a GridSplitter, convert the columns’ absolute pixel widths back to star ratios. This ensures they scale proportionally when the window resizes.
How It Works:#
- When the user drags a
GridSplitter, columns get absolute pixel widths (e.g.,180,120,100). - We calculate the ratio of each column’s width to the total grid width.
- Update the columns’
Widthto star values based on these ratios (e.g.,1.8*,1.2*,1*instead of fixed pixels).
Code Implementation#
XAML: Add DragCompleted event handlers to the GridSplitters:
<Grid x:Name="mainGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="col0" Width="*" MinWidth="50"/>
<ColumnDefinition x:Name="col1" Width="*" MinWidth="50"/>
<ColumnDefinition x:Name="col2" Width="*" MinWidth="50"/>
</Grid.ColumnDefinitions>
<!-- GridSplitter 1 (between Column 0 and 1) -->
<GridSplitter Grid.Column="0" Width="5" HorizontalAlignment="Right"
VerticalAlignment="Stretch" Background="Gray"
DragCompleted="GridSplitter_DragCompleted"/>
<!-- GridSplitter 2 (between Column 1 and 2) -->
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Right"
VerticalAlignment="Stretch" Background="Gray"
DragCompleted="GridSplitter_DragCompleted"/>
<!-- Content -->
<Border Grid.Column="0" Background="LightBlue" Child="Column 0"/>
<Border Grid.Column="1" Background="LightGreen" Child="Column 1"/>
<Border Grid.Column="2" Background="LightPink" Child="Column 2"/>
</Grid>Code-Behind: Handle DragCompleted to reset star ratios:
private void GridSplitter_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e)
{
// Get the total width of the grid (excluding padding/margins)
double totalGridWidth = mainGrid.ActualWidth;
// Avoid division by zero if the grid isn't rendered yet
if (totalGridWidth <= 0) return;
// Get current pixel widths of the three columns
double col0Width = col0.ActualWidth;
double col1Width = col1.ActualWidth;
double col2Width = col2.ActualWidth;
// Calculate ratios (column width / total width)
double ratio0 = col0Width / totalGridWidth;
double ratio1 = col1Width / totalGridWidth;
double ratio2 = col2Width / totalGridWidth;
// Scale ratios to avoid tiny star values (e.g., 0.05*). Use 100 as a base.
double scaleFactor = 100;
col0.Width = new GridLength(ratio0 * scaleFactor, GridUnitType.Star);
col1.Width = new GridLength(ratio1 * scaleFactor, GridUnitType.Star);
col2.Width = new GridLength(ratio2 * scaleFactor, GridUnitType.Star);
}How It Fixes the Problem#
- After dragging: The
DragCompletedevent calculates the new width ratios and sets the columns back to star sizing (e.g.,40*,30*,30*instead of160px,120px,120px). - Window resize: With star sizing restored, columns scale proportionally to their new ratios, maintaining the user’s intended layout.
Advanced Scenarios: Handling Edge Cases#
Case 1: Very Small Columns#
If a user shrinks a column to its MinWidth, its ratio may be tiny (e.g., 0.05*). To avoid this, cap the minimum ratio:
// In DragCompleted: Ensure ratios don't drop below 0.1 (10% of total)
double minRatio = 0.1;
ratio0 = Math.Max(ratio0, minRatio);
ratio1 = Math.Max(ratio1, minRatio);
ratio2 = Math.Max(ratio2, minRatio);
// Normalize ratios to sum to 1 (optional)
double sum = ratio0 + ratio1 + ratio2;
ratio0 /= sum;
ratio1 /= sum;
ratio2 /= sum;Case 2: MVVM Pattern#
For MVVM apps, avoid code-behind by binding Width to a viewmodel property and using a command to handle DragCompleted.
Conclusion#
Unintended column shrinking with GridSplitter in WPF is caused by fixed pixel widths overriding star sizing. By combining MinWidth (to prevent extreme shrinking) and dynamically resetting star ratios in the DragCompleted event, we ensure columns maintain relative proportions—even after resizing the window.
This approach balances user interactivity with responsive layout behavior, creating a polished WPF UI.