WPF GridSplitter behavior when resizing its parent Window - wpf

I have defined my GridSplittler XAML like the following.
<Window>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
</Grid>
<Border Grid.Column="0" Background="Red" />
<GridSplitter Grid.Column="1" ResizeDirection="Columns" Width="3" Height="Auto" HorizontalAlighment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="0" Background="Green" />
</Window>
This will create two columns with a grid splitter between them and it will correctly resize the columns as the grid splitter is dragged left and right. Now if you resize the whole Window I would like the width of left red column to remain fixed and have the width of the right green column change (as the Window is resized). Its the same effect as when you resize the whole Visual Studio application and the Solution Explorer width remains fixed but the width of a code tab changes. Also the same as the SQL Server Management Studio; the Object Explorer width remains fixed but the SQL tabs width changes. I realize that these two examples use a more complicated docking control but I was hoping to achieve the same result with using the WPF GridSplitter.
Edit: Based on mathieu suggestions all the was necessary was to give the first column definition an initial width (shown using 200 below).
<Grid Name="GridName">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="Red" />
<GridSplitter Name="SplitterName" Grid.Column="1" ResizeDirection="Columns" Width="3" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="2" Background="Green" />
</Grid>
Now when the Window is re-sized it only changes the width of the green border and the width of the red border remains fixed. Thanks!

If you don't mind some code behind, you can achieve this behaviour by reacting to the DragDelta event of the splitter, and adjusting the width of the first column, by removing the Star width, and fixing the width according to the drag delta.
Xaml :
<Grid Name="GridName">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Background="Red" />
<GridSplitter Name="SplitterName" Grid.Column="1" ResizeDirection="Columns" Width="3" Height="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<Border Grid.Column="2" Background="Green" />
</Grid>
Code behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
SplitterName.DragDelta += SplitterNameDragDelta;
}
private void SplitterNameDragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
GridName.ColumnDefinitions[0].Width = new GridLength(GridName.ColumnDefinitions[0].ActualWidth + e.HorizontalChange);
}
}

I've used the snipet of mathieu - and modified it for code behind use.
In my case I need a dynamic number of Items resized by the GridSplitter.
I used the Thumb Control to have full control of the action
private Thumb GetNewThumbAsGridSplitter(int column)
{
var gs = new Thumb();
gs.SetValue(Grid.ColumnProperty, column);
// gs.SetValue(Grid.RowSpanProperty, 2);
// gs.SetValue(Grid.RowProperty, 1);
gs.Width = 5;
gs.MouseEnter += (o, i) =>
{
Mouse.OverrideCursor = Cursors.ScrollWE;
};
gs.MouseLeave += (o, i) =>
{
Mouse.OverrideCursor = Cursors.Arrow;
};
gs.DragDelta += (o, i) =>
{
var grid = (Grid)gs.Parent;
var previous = (YourControl)((grid.Children[column - 1]));
var next = (YourControl)(grid.Children[column + 1]);
if (next.MinWidth >= (next.ActualWidth - i.HorizontalChange))
{
return;
}
if (previous.MinWidth >= (previous.ActualWidth + i.HorizontalChange))
{
return;
}
previous.Width = previous.ActualWidth + i.HorizontalChange;
next.Width = next.ActualWidth - i.HorizontalChange;
};
return gs;
}

Related

Align center until there isn't enough space WPF

I have a particular layout behavior in mind, and I want to see if anyone can come up with a simple way of doing this before I try to write a custom panel or something. Take a look at this animation I manually put together:
There is an element of fixed width to the far left (the rectangle), and then a second element that is centered- not within the remaining space, but relative to the entire container. As the container shrinks, the centered element stays centered until the left element gets too close. Instead of staying directly in the middle, the "centered" element now stays as close to the center as possible without overlapping. This is the behavior I want.
Using a Grid, I can do something like this:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border Width="100" Background="Green"/>
<TextBlock HorizontalAlignment="Center" VerticalAlignment="Center" Grid.ColumnSpan="2" FontSize="24" Margin="2, 0">Centered Text</TextBlock>
</Grid>
Which puts everything in the right place, but the TextBlock will start overlapping with the Border instead of moving further right.
So any idea how I can get this to do what I want?
Give this a try:
Name of Window x:Name="window1"
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Border x:Name="border1" Width="100" Background="Green"/>
<TextBlock x:Name="textBlock1" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="24" Grid.ColumnSpan="2">Centered Text</TextBlock>
</Grid>
SizeChanged event
private void Window1_SizeChanged(object sender, SizeChangedEventArgs e) {
if(( ( window1.Width / 2 ) - ( textBlock1.ActualWidth / 2 ) - border1.Width - ( SystemParameters.ResizeFrameVerticalBorderWidth * 2 ) ) <= 0) {
Grid.SetColumn(textBlock1, 1);
textBlock1.HorizontalAlignment = HorizontalAlignment.Left;
} else {
Grid.SetColumn(textBlock1, 0);
textBlock1.HorizontalAlignment = HorizontalAlignment.Center;
}
}

WPF: Calculating the size of a Grid Column

I have a grid that contains three columns.
<Grid Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="8*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="2*" Name="ManualControlsSplit" />
</Grid.ColumnDefinitions>
The first column contains a Grid and a chart.
The second column contains a GridSplitter.
The third column contains a StackPanel which contains a number of TextBlocks, Buttons and Grids containing TextBlocks and Buttons. Text size is dynamic and based on translation resources.
I need to calculate the minimum width that the contents of the third column would ideally like to be able to be drawn into so that the contents are not clipped.
My knowledge of WPF is limited to what I can google so any help would be appreciated.
With layout in WPF, you can allow one if not more of the Grid.Columns to be <ColumnDefinition Width="Auto"/>. This essentially tells WPF to allow as much space as the contained controls want. The use of Auto cascades too, so in StackPanel you refer to, you can make that controls width Auto as well, likewise with the items it contains; if the stack panel merely contains a TextBlock (via some template or whatever), then you can also set this width to Auto and the width with set itself according to the contained text.
<Grid Background="AliceBlue">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
...
<StackPanel Grid.Column=2
Width="Auto">
<TextBlock Width="Auto"
Text="This is TextBlock"/>
...
</StackPanel>
...
</Grid>
In this case the Text of the TextBlock sets the width of the StackPanel, which in turn sets the width of the 3rd grid column.
I hope this helps.
My WPF XAML setup is as follows (bits removed to keep this simple):
<UserControl ....>
<Grid Background="AliceBlue" Name="TopGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="12*" Name="Graph" />
<ColumnDefinition Width="5" Name="GridSplitter" />
<ColumnDefinition Width="2*" Name="ManualControlsSplit" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="AliceBlue"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- A Graph and simple controls -->
</Grid>
<!-- GRID SPLITTER -->
<GridSplitter Grid.Column="1" Width="5"
HorizontalAlignment="Stretch" Name="Splitter" />
<Label Grid.Column="1" Content="⁞" Foreground="White"
Background="DarkGray"
VerticalAlignment="Center" FontSize="26" FontWeight="Bold"
IsHitTestVisible="False"/>
<!-- end GRID SPLITTER -->
<StackPanel Grid.Column="2" Grid.Row="0" Margin="5"
Name="TemperatureControls">
<!-- Load of Controls -->
</StackPanel>
</Grid>
</UserControl>
To calculate the desired width I use the following code:
// get my UserControl object
var manualControlView = userControl as HeatingController.Views.ManualControlView;
// Query the current width of THIRD column
double actualWidth = manualControlView.ManualControlsSplit.ActualWidth;
// Set up a BIG size (this has to be bigger size than the contents of the
// THIRD column would ever need)
Size size = new Size(400, manualControlView.TopGrid.ActualHeight);
// Ask WPF layout to calculate the Desired size.
manualControlView.TemperatureControls.Measure(size);
double width = manualControlView.TemperatureControls.DesiredSize.Width;
if (actualWidth <= width)
{
// Too small - do something
}
else
{
// big enough - do something else.
}
The variable 'width' now contains the value I wanted to calculate.

Grid column not collapsing to fit contents after width resize

I have a grid column that contains a treeview which can shrink to a width of 16 when a ToggleButton is pressed. The column is set to Width="Auto" which I assume is responsible for the shrinking behavior.
The problem is that to the right of the column, there is a grid splitter, so that the user can increase/decrease the width as they see fit. If the user does this, and then presses the ToggleButton, the contents of the column shrink to 16, but the column itself does not.
Does anyone know how I can ensure that the column shrinks in these scenarios? I'm guessing it has to do with the column width being changed from auto to a definite value, but I can't think of how to fix that once the contents shrink. Here's my code so far:
<Grid Name="ContentGrid" HorizontalAlignment="Stretch" Grid.Row="1" Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" x:Name="TreeColumn"/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*" MinWidth="400" />
</Grid.ColumnDefinitions>
<UserControl DockPanel.Dock="Left" Grid.Row="0" Grid.Column="0" Padding="0" x:Name="TreeControl" >
<local:TreeViewControl x:Name="mainTreeView" Height="Auto" />
</UserControl>
<GridSplitter Grid.Row="0" Grid.Column="1" Margin="0,0,0,0"
Width="4" Background="Transparent" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" />
<Grid Name="LandingPageGrid" Grid.Row="0" Grid.Column="2">
EDIT: I've tried adding a DataTrigger to the column's definition, but this hasn't worked.
<ColumnDefinition Width="Auto" x:Name="TreeColumn">
<ColumnDefinition.Style>
<Style>
<Style.Triggers>
<DataTrigger {Binding Path=IsChecked, ElementName=CollapseIcon}" Value="True">
<Setter Property="ColumnDefinition.Width" Value="Auto"/>
</DataTrigger>
</Style.Triggers>
</Style>
</ColumnDefinition.Style>
</ColumnDefinition>
So after much debugging, many fruitless attempts, I finally resorted to setting the errant widths in the code-behind to the TreeViewControl. It's not pretty, so if someone would like to suggest a better answer, please do so, and I'll happily mark your code as the solution.
private void CollapseIcon_Checked(object sender, RoutedEventArgs e)
{
TreeControl.Width = 16;
TreeControl.HorizontalAlignment = HorizontalAlignment.Left;
((FrameworkElement) TreeControl.Parent).Width = 16;
var parentColumn = (((Grid) ((FrameworkElement) TreeControl.Parent).Parent).ColumnDefinitions)[0];
parentColumn.Width = GridLength.Auto;
}
private void CollapseIcon_Unchecked(object sender, RoutedEventArgs e)
{
TreeControl.Width = Double.NaN;
TreeControl.HorizontalAlignment = HorizontalAlignment.Stretch;
((FrameworkElement) TreeControl.Parent).Width = Double.NaN;
var parentColumn = (((Grid)((FrameworkElement)TreeControl.Parent).Parent).ColumnDefinitions)[0];
parentColumn.Width = GridLength.Auto;
}

Stop Gridsplitter stretching content beyond window

Given the below XAML, how do I have the gridsplitter respect the MinHeight given to the 3rd row and have the content remain inside my window?
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition MinHeight="40" />
</Grid.RowDefinitions>
<Expander Grid.Row="0" ExpandDirection="Down" VerticalAlignment="Top">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0" MinHeight="100" Background="Black" />
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="LightBlue" ResizeBehavior="PreviousAndCurrent" />
</Grid>
</Expander>
<Expander Grid.Row="1" ExpandDirection="Down" VerticalAlignment="Top">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="40" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Border Grid.Row="0" MinHeight="100" Background="Black" />
<GridSplitter Grid.Row="1" Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Background="LightBlue" ResizeBehavior="PreviousAndCurrent" />
</Grid>
</Expander>
<Border DockPanel.Dock="Bottom" Grid.Row="2" Background="Lime" MinHeight="30" >
<TextBlock Text="{Binding RelativeSource={RelativeSource Mode=FindAncestor,AncestorType=DockPanel},Path=ActualHeight,StringFormat={}{0:f0}}" />
</Border>
</Grid>
The way your code is, this cannot be done mate. This is due to how the GridSplitter works.
A few points
A GridSplitter will always work on directly adjacent rows/columns
In reality, your MinHeight IS being respected, but so is the GridSplitter's request to grow being respected, which results in the Grid growing larger than your Window
When sized to Auto, a row/column will always resize according to its content, not bigger, and not smaller
Therefore if a GridSplitter is sandwiched between two * sized rows/columns, then it would implicitly respect your MinHeight, since in reality, it would not be touching it
You have a few solutions
Add another row in the 3rd position which is * sized, and have your border on row 3 with a RowSpan of 2 (so the 3rd row is the one being really resized, and your 4th row isn't touched. Though this will also have side-effects.
Handle a mixture of DragEnter and PreviewMouseMove events on the GridSplitter, keeping track of focus, and cancelling (e.Handled = true) the event when a certain size is reached.
This is what I can think of mate, hope I've been of some help.
I created a custom grid splitter class that will not allow the grid splitter to go off the edge of a window (either the bottom or the side).
Public Class CustomGridSplitter
Inherits GridSplitter
Public Enum SplitterDirectionEnum
Horizontal
Vertical
End Enum
Public Property SplitterDirection As SplitterDirectionEnum
Public Property MinimumDistanceFromEdge As Integer
Private _originPoint As Point
Private Sub customSplitter_MouseDown(sender As Object, e As MouseButtonEventArgs) Handles MyBase.MouseDown
_originPoint = e.GetPosition(Window.GetWindow(Me))
End Sub
Private Sub customSplitter_PreviewMouseMove(sender As Object, e As MouseEventArgs) Handles MyBase.PreviewMouseMove
If e.LeftButton = MouseButtonState.Pressed Then
Dim pwindow As Window = Window.GetWindow(Me)
Dim newPoint As Point = e.GetPosition(pwindow)
If SplitterDirection = SplitterDirectionEnum.Horizontal Then
If newPoint.Y >= _originPoint.Y Then
If newPoint.Y >= pwindow.ActualHeight - MinimumDistanceFromEdge Then
e.Handled = True
End If
Else
If newPoint.Y > pwindow.ActualHeight - (MinimumDistanceFromEdge + 2) Then
e.Handled = True
End If
End If
Else
If newPoint.X >= _originPoint.X Then
If newPoint.X >= pwindow.ActualWidth - MinimumDistanceFromEdge Then
e.Handled = True
End If
Else
If newPoint.X > pwindow.ActualWidth - (MinimumDistanceFromEdge + 2) Then
e.Handled = True
End If
End If
End If
_originPoint = newPoint
End If
End Sub
End Class
To use it in XAML:
<CustomGridSplitter SplitterDirection="Vertical" MinimumDistanceFromEdge="100" x:Name="splitterCenter" ResizeDirection="Columns" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" Width="2" Margin="2,0,2,0"/>
The custom properties to set are the "SplitterDirection" and "MinimumDistanceFromEdge". Everything works like the base grid splitter.
This uses mouse events to determine where in the window the user is dragging the splitter and handles the events if they get too close to the edge.
I found another solution to this problem, though in a much more simple case where I just had two columns inside a window that I wanted to resize.
The solution that I came up with (described in more detail here: https://stackoverflow.com/a/46924893/6481970) was to add event callbacks for when the grid was resized, when the GridSplitter moved, and when the window was resized (to handle the case where you resize the window to no longer fit the content because the grid doesn't automatically resize itself to fit the smaller window).
Here is some simplified code:
XAML:
<Grid x:Name="ResizeGrid" SizeChanged="ResizeGrid_SizeChanged">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="C0" Width="150" MinWidth="50" />
<ColumnDefinition Width="5" />
<ColumnDefinition x:Name="C2" Width="*" MinWidth="50" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0" Background="Green" />
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" DragCompleted="GridSplitter_DragCompleted" />
<Grid Grid.Column="2" Background="Red" />
</Grid>
C# Code Behind:
C0.MaxWidth = Math.Min(ResizeGrid.ActualWidth, ActualWidth) - (C2.MinWidth + 5);

How to have a click-able button in my combo-box ItemTemplate?

This is what i have so far:
<dxe:ComboBoxEdit Name="cboUserCustomReports"
Width="300" Height="Auto"
Margin="0,5,0,5"
ItemsSource="{Binding Path=UserReportProfileList,Mode=OneWay,UpdateSourceTrigger=PropertyChanged}"
EditValue="{Binding Path=UserReportProfileID,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"
ValueMember="UserReportProfileID"
DisplayMember="ReportName"
PopupClosed="cboUserCustomReports_PopupClosed">
<dxe:ComboBoxEdit.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100*"/>
<ColumnDefinition Width="20"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0"
Text="{Binding ReportName, Mode=Default}"
VerticalAlignment="Stretch" HorizontalAlignment="Left"/>
<Button Name="btnDelete"
Grid.Column="1"
Width="20" Height="20"
VerticalAlignment="Center" HorizontalAlignment="Right"
Click="btnDelete_Click">
<Button.Template>
<ControlTemplate>
<Image Source="/RMSCommon;component/Resources/Delete.ico"></Image>
</ControlTemplate>
</Button.Template>
</Button>
</Grid>
</DataTemplate>
</dxe:ComboBoxEdit.ItemTemplate>
</dxe:ComboBoxEdit>
First, i want the two columns to be stand alone. The user must be able to select or delete the item.
Second, i would like to make my button in the ItemTemplate to be click-able.
What do i need to add to get this behavior?
This is what it looks like at the moment:
Click
I assume, that your button is clickable, and you want to know how to process the click event. Right?
For the click-handler, add the following code:
private void btnDelete_Click(object sender, RoutedEventArgs e) {
FrameworkElement fe = sender as FrameworkElement;
if(null == fe){
return;
}
UserReportProfile userReportProfile = fe.DataContext as UserReportProfile;
if (null == userReportProfile) {
return;
}
// Do here your deletion-operation
}
I assumed that your item-class is named UserReportProfile. Otherwise, change the declared type accordingly.
Layout
For the alignment, add the following declaration to your ComboBox:
HorizontalContentAlignment="Stretch"
This gives your DataTemplate-Grid the full width and you can layout then your items as you desire.
<dxe:ComboBoxEdit Name="cboUserCustomReports"
HorizontalContentAlignment="Stretch"
Width="300" Height="Auto"
Margin="0,5,0,5"
...>
Your question is not clear enough. But I guess you want to vertically align the text and images in your combobox. If so, then all you need to do this:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
And I think your items are already clickable!

Resources