Gridsplitter: limit row heights to window size - c#

I have just started to experiment with gridsplitters and have stumbled across a very weird behaviour and I cannot seem to fix it. Apparently the others have similar issues (according to google), but there were no helpful comments.
I have a grid with 2 rows. On start up the bottom one has a Hight of 250. The top row takes the rest with *. When I resize the rows with the splitter the behaviour is as expected for the top row. But when I drag the splitter upwards and past the program window, the content of the bottom row will drop out of the window (=move downwards until it is gone). I'd expect that I cannot make each row larger than the parent container.
<Grid x:Name="grid_main" ScrollViewer.VerticalScrollBarVisibility="Disabled" >
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition MinHeight="250" Height="250"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<GridSplitter x:Name="splitter"
ResizeDirection="Rows" Grid.Column="0" Grid.ColumnSpan="1" Grid.Row="0" Width="Auto"
Height="5" HorizontalAlignment="Stretch" VerticalAlignment="Bottom" Margin="0" ResizeBehavior="BasedOnAlignment" />
<Grid Grid.Column="0" Grid.Row="0">
</Grid>
<Grid Grid.Column="0" Grid.Row="1">
</Grid>
</Grid>
This is my code. The content of both rows is again hosted in grids and has been removed for the sake of simplicity. Again: Resizing for the top row works fine. But the bottom row can be resized to infinity. It works as expected if I hard-code a MaxHeight. But that has to depend on the window size.

Try changing your second RowDefinition to the following:
<RowDefinition MinHeight="250" MaxHeight="{Binding ElementName=grid_main, Path=ActualHeight}" Height="250"/>
This will ensure that the row height will not exceed the window size.

Richard's solution didn't work for me (the ActualHeight of the grid expanded past the window size along with the row height).
Using Snoop, I found that the ActualHeight of an ancestor ContentPresenter wasn't increasing. Thus the following bottom row definition worked for me, although I was still seeing issues if I set MinHeight on either the top or bottom rows:
<RowDefinition Height="430"
MaxHeight="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type ContentPresenter}}, Path=ActualHeight}"/>

This problem only occurs because the second row has an absolute Height. If you actually want an absolute Height value try Richards's solution. Otherwise, just use a relative Height (e.g. 2*) on the second row as well.

Grigory Zhadko's answer is good to me.
I had a problem with this code
gridBase.RowDefinitions[1].Height = (GridLength)gridLengthConverter.ConvertFrom("1");
gridBase.RowDefinitions[3].Height = (GridLength)gridLengthConverter.ConvertFrom("1");
and I fixed it like this
gridBase.RowDefinitions[1].Height = new GridLength(0.1, GridUnitType.Star);
gridBase.RowDefinitions[3].Height = new GridLength(0.1, GridUnitType.Star);
and the grid converter works perfectly

Related

Resizable WPF listbox inside Stack Panel?

I'm trying to create a listBox inside a WPF control which (1) has a minimum height of 80 and (2) which the user can resize to larger vertical height, if desired to see more of the list items. I also want the control, when nested inside a vertical stackPanel, to expand in height as the user expands the list. I've been playing around with gridSplitter. My XAML is below:
<UserControl x:Class="MyControl.FieldControl_SelectFrom"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:MyControl"
mc:Ignorable="d"
d:DesignWidth="342">
<Grid Background="{x:Static SystemColors.ControlBrush}">
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" x:Name="textBlock_fieldName" HorizontalAlignment="Left" Margin="20,4,0,0" TextWrapping="Wrap" Text="FieldName" VerticalAlignment="Top" FontWeight="Bold"/>
<ListBox Grid.Row="1" x:Name="selectorBox" MinHeight="80" Height="auto" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" Padding="0,0,0,0" Margin="20,4,38,4" VerticalContentAlignment="Center" />
<GridSplitter Grid.Row="2" Height="2" VerticalAlignment="Center"></GridSplitter>
<TextBlock Grid.Row="3" x:Name="textBlock_fieldInfo" HorizontalAlignment="Left" Margin="20,0,0,6" TextWrapping="Wrap" VerticalAlignment="Bottom" FontStyle="Italic" Foreground="Gray">Field Info</TextBlock>
</Grid>
</UserControl>
When I run the project, the listbox is expanded to show the entire contents - with no ability to resize.
What am I missing?
The default resize direction of the GridSplitter is GridResizeDirection.Auto. If you look at the documentation for GridResizeDirection.Auto about the rules that govern behavior of Auto, you'll notice that in your case the GridSplitter -- based on the defined rules as explained in the documentation -- is actually attempting to resize between columns, not rows as you want it to be.
You can make the GridSplitter work in row mode in basically two ways. One is to set the ResizeDirection of the GridSplitter explicitly to GridResizeDirection.Rows. This, however, would not fully solve the problem with the code given in your question.
Because, without specifying the width of the splitter or letting it stretch horizontally (neither of which you have done), the splitter remains a point-like or short vertical line-like element with a width of zero that's practically impossible to hit. So, basically you could tell the splitter to stretch horizontally, which will let the default Auto resize direction operate in Rows mode (and therefore does not require to set the ResizeDirection property explicitly) as well as give the splitter a size that can actually be hit/grabbed with the mouse pointer.

How to make a certain section scrollable

I have got the following XAML:
<StackPanel HorizontalAlignment="Center">
<Image Name="logo" Source="logo.png" Margin="0,0,0,50"/>
<ScrollViewer>
<Dashboard_Control:AlignableWrapPanel x:Name="wrapPanel"/>
</ScrollViewer>
<TextBlock FontWeight="Bold" HorizontalAlignment="Center" x:Name="txtBottomText"></TextBlock>
</StackPanel>
I would like that the wrapPanel is scrollable only, so that the txtBottomText control will always be at the bottom as you scroll, and the logo image control will always be at the top - essentially only allowing the wrapPanel to be scrollable.
I have tried adding a ScrollViewer as shown above, however it never shows. I even tried adding a property to always have the vertical scrollbar, however it appears without letting me scroll (the scrollbar is disabled).
I suspect that this is because my wrapPanel's content is dynamically generated at run-time like so:
wrapPanel.Children.Add(content);
Any ideas what I can do to fix this?
It's not because of your wrapPanel's content. but because you're using a StackPanel to contain everything.
StackPanels grow indefinitely in the direction determined by their Orientation property. By default, that's vertically.
In your case, that makes the ScrollViewer "think" it has enough available space to stretch itself to accomodate its content, instead of activating the scroll bars. So it simply gets bigger as the WrapPanel inside gets bigger, pushing the TextBlock down.
To avoid this, you need to use a different Panel that is able to properly assign the available space to each control. Like a Grid.
<Grid HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Image Name="logo" Source="logo.png" Margin="0,0,0,50"/>
<ScrollViewer Grid.Row="1">
<Dashboard_Control:AlignableWrapPanel x:Name="wrapPanel"/>
</ScrollViewer>
<TextBlock Grid.Row="2" FontWeight="Bold" HorizontalAlignment="Center" x:Name="txtBottomText"></TextBlock>
</Grid>
I usually say that StackPanels tend to be overused :P They're practical and easy to use, but they have a bunch of quirks and limitations that make them not suitable for many situations, like this one.
EDIT: Make sure, also, that the Grid is not contained inside another vertical StackPanel, a Grid row with Height set to Auto, or something like that.
If the Height of your WrapPanel exceeds the height of the control or window where you have put the controls, the Textblock below the Wrap Panel inside the Stack Panel is put after the Wrap panel and so it is below the scroll area.
To be able to leave the Textblock always visible you have two means:
1) limit the height of your Wrap panel
2) Use a container like a Grid with 3 rows instead of the stack panel and put the Row Heights of the Grid respectively to
Auto, *, Auto so that the image on top and the textblock on bottom use the space of their content and the Scroll panel uses all the space remaining
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Image Grid.Row ="0" Source="myimage.jpg" />
<ScrollViewer Grid.Row="1">
<WrapPanel Height="1200" Width="600">
<TextBlock>Ciao sono io</TextBlock>
</WrapPanel>
</ScrollViewer>
<TextBox Grid.Row="2" TextWrapping="Wrap" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Text="IO c'ero" />
</Grid>
You have to set a fixed heigh to your scrollviewer - if not, the scrollviewer takes as much space as he needs, in order to show the complete content of its children.
Solutions:
Set the Height property of your scollviewer
Use a grid instead of a stackpanel an set the first and third rowheight to auto

ContentControl inside Grid does not resize correctly

I have a 3 column grid in which I have:
a ContentControl, which has a content that does not fit and is clipped.
a GridSplitter
a third column with a min width
Here is the code :
<Window x:Class="TestGridWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" MinWidth="900" Width="900">
<Grid Grid.Column="2" x:Name="Grid2">
<Grid.ColumnDefinitions>
<ColumnDefinition x:Name="Panel1Col" Width="2000*"></ColumnDefinition>
<ColumnDefinition Width="Auto"></ColumnDefinition>
<ColumnDefinition x:Name="Panel2Col" Width="*" MinWidth="200"></ColumnDefinition>
</Grid.ColumnDefinitions>
<ContentControl x:Name="ContentControl" Grid.Column="0" BorderThickness="5">
<Rectangle Width="1800" Height="500" HorizontalAlignment="Left" VerticalAlignment="Top" Fill="Green"></Rectangle>
</ContentControl>
<GridSplitter Grid.Column="1" Width="5"></GridSplitter>
<Border Grid.Column="2" BorderBrush="Blue" BorderThickness="5"></Border>
</Grid>
</Window>
With a fixed minimum width for the column, the first column shrinks a little when I reduce the window, but the Grid gets greater than the window and the third column is clipped.
Why does it do that? How can I solve this?
Your Grid configuration :
The 3rd column has star width and minimum width of 200.
The 1st columns has 2000 star width. That means 2000 times wider
than 3rd column which has minimum width 200.
The window it self, has default width of only 900
Above configuration appears to be problematic. There won't be enough space to render all the columns properly (it is require window width of at least 200 + (2000*200) + 5).
The end result, WPF renders this configuration as :
The 1st column takes almost all Grid's width.
The 3rd column still rendered with 200 width, but only slightest part of it (1/2000 of width of 1st column) is inside the Grid, the rest is rendered outside the Grid at the right.
Rendered layout :
But how to fix it exactly, depends on what you are trying to achieve (the most makes sense is to reduce width of the 1st column). I hope that this explanation helps you decide better on how to fix the layout.

Cannot get grid splitter to work the way I would like within 3 column grid in WPF

I have the following grid using WPF and a few third party controls (Telerik):
<Grid x:Name="grid2" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="150"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*" MinWidth='80'/>
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Grid.RowDefinitions>
<RowDefinition Height="26"/>
<RowDefinition/>
</Grid.RowDefinitions>
<telerik:RadBreadcrumb Grid.ColumnSpan="3" Header="RadBreadcrumb"/>
<telerik:RadTreeView Grid.Row="1"/>
</Grid>
<GridSplitter Grid.Column="1" HorizontalAlignment="Center" Width="5" Height="auto"/>
<telerik:RadTransitionControl x:Name="Trans" Grid.Column="2" Width="auto"/>
</Grid>
Currently I am trying to make it so that the grid in column 0 (on the left side) will not re-size while re-sizing the window. I was able to achieve this effect by setting the width of column "0" to either a static 150 or auto. However, when I did this, the grid splitter no longer respected the minimum width of column 2 and allowed it to drag off the screen. By setting both column 0 and 2 to a width of star (the way I have it in the code now) the min widths are respected (the columns can't shrink past the 150 and 80 defined) but they don't re-size properly (the left column re-sizes in proportion to the right column). I would like to somehow get both the resize and the gridsplitter to work at the same time. Let me know if I wasn't clear enough or if more input is needed.
So, this isn't exactly what I was looking for, but it's doing the job for me:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto" MinWidth="150" MaxWidth="1000"/>
<ColumnDefinition Width="5"/>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
I set the MaxWidth of my left hand column so that you could no longer drag the splitter past the point of no return on the right side. This solution also allows me to set the first column's width to auto (which makes the re-sizing work the way I intended as well). Unless you have a monitor smaller than 1000px wide (which in the environment I'm working in won't happen) this solution will work just using XAML. Again, I'm a little disappointed with this solution but its at least working in a simplistic manner.

Wrap panel where items in a row share height

i want to add squares to a panel and have them wrapped like the wrap panel.
I then want to make each square horizontally resizable individually, but when it is resized vertically, i need it to affect all the items in it's row.
Basically, I'd want all items in a row to always share the same height, but give the user a method of choosing this height (of course, each row can have its own height, and when squares wrapped to a new row they will need to inherit the new height).
Btw, those "squares" are just user controls or data template applied to a listbox items source. Can i use the same binding on a wrap panel, ad maybe i need to choose a different solution?
Thank you
you can try and put each "rectangle" in a Grid with one row and one column, and then use SharedSizeGroup on the RowDefinition. Be sure to also put Grid.IsSharedSizeScope="True" on the container:
<WrapPanel Grid.IsSharedSizeScope="True">
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="Group1" />
</Grid.RowDefinitions>
<Button Height="40" Content="Hello" />
</Grid>
<Grid>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="Group1" />
</Grid.RowDefinitions>
<Button Content="Hello2" />
</Grid>
</WrapPanel>

Categories

Resources