ScrollViewer only scrollable when contents minHeight falls below - wpf

Lets take this code as basis:
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Button
x:Name="_buttonAdd"
Grid.Row="0"
Click="_buttonAdd_Click"
Content="Daten hinzufügen" />
<Button
x:Name="_buttonDel"
Grid.Row="1"
Click="_buttonDel_Click"
Content="Daten löschen" />
<DataGrid
x:Name="_dataGrid"
Grid.Row="2"
MinHeight="200"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutoGenerateColumns="True"
ItemsSource="{Binding Path=MitarbeiterList}"
VerticalScrollBarVisibility="Auto">
</DataGrid>
</Grid>
</ScrollViewer>
I want the ScrollViewer only to scroll, if the MinHeight of the DataGrid falls below 200.
On the other side I want the ScrollViewer not to scroll, if the MinHeight exceeds or in
other words: I want the DataGrid to stretch vertically to the visible area and shows its
own Scrollbar if necessary.
I hope you guys can solve my problem.
thanks in advance.

Thanks for your answer Wonko. Removing the scrollviewer isn't the key, but I can understand your thoughts. I finally did it my way, with a multibinding on MaxHeight:
<DataGrid
x:Name="_dataGrid"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutoGenerateColumns="True"
ItemsSource="{Binding Path=MitarbeiterList}"
VerticalScrollBarVisibility="Auto">
<DataGrid.MaxHeight>
<Multibinding Converter="{StaticResource MaxHeightConverter}">
<Binding Path="ActualHeight"
ElementName="_hostingWindow" />
<Binding Path="DataGridLocationPoint" />
</Multibinding>
</DataGrid.MaxHeight>
</DataGrid>
The MaxHeightConverter just subtract the ActualHeight of the window with the Y-Coordinate of the DataGrid-Location: So this sets the MaxHeight of the DataGrid always to the remaining available area of the window. The DataGridLocationPoint is set when the size of the window is changed. Like this:
public void dataGrid_SizeChanged(...)
{
GeneralTransform transform = dataGrid.TransformToAncestor(this);
Point DataGridLocationPoint = transform.Transform(new Point(0, 0));
}
(Sorry the code may not run, because I wrote it out of my Brain)

As you've written the code, the Grid will always have a minimum height of 300: 50 each for the first two rows, plus 200 for the DataGrid (as set by its MinHeight property). If the container of this Grid (or, actually, the ScrollViewer you've declared) becomes less than that, the ScrollViewer will show its vertical ScrollBar.
Personally, unless your container is going to be less than the size of the Buttons plus some of the DataGrid, I would simply remove the outer ScrollViewer. I can't tell for sure, but I believe that this will give you the functionality you desire.
The Buttons will always be visible, and the DataGrid will fill the remaining space. If the DataGrid needs more room than is visible, its ScrollBar will automatically show up.
One other thing I would probably do is to put the buttons next to each other horizontally - this will not only clean up the UI, but also save you some real estate. You could even make the row autosize, and size the buttons accordingly.
In summary, it might look something like this (I'd probably make a Style for the buttons as well, but you get the idea):
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button
x:Name="_buttonAdd"
Grid.Column="0"
Grid.Row="0"
HorizontalAlignment="Left"
Margin="5"
Click="_buttonAdd_Click"
Content="Daten hinzufügen" />
<Button
x:Name="_buttonDel"
Grid.Column="1"
Grid.Row="0"
HorizontalAlignment="Right"
Margin="5"
Click="_buttonDel_Click"
Content="Daten löschen" />
<DataGrid
x:Name="_dataGrid"
Grid.Column="0"
Grid.Row="1"
Grid.ColumnSpan="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
AutoGenerateColumns="True"
ItemsSource="{Binding Path=MitarbeiterList}"
VerticalScrollBarVisibility="Auto">
</DataGrid>
</Grid>

Related

Loading 2 DataGrids + StackPanel with ScrollViewer Taking too long, Alternatives?

I have 3 content items, two DataGrid and a StackPanel that all need to scroll together in a WPF application. Everything works OK with ScrollViewer as long as I only have a small dataset, but when I get to 500 rows with 150 columns, it lags for about 8 seconds before drawing the screen. After reading this: Unreasonable WPF DataGrid Loading Time I disabled the ScrollViewer and it loaded in just 2.5 seconds.
<ScrollViewer CanContentScroll="True"
HorizontalScrollBarVisibility="Visible"
VerticalScrollBarVisibility="Visible">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="20" />
<RowDefinition Height="22" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid x:Name="DataGridViewHeader" CellEditEnding="ColumnNameUpdate" Grid.Row="0" HeadersVisibility="None" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch" ItemsSource="{Binding}" CurrentCellChanged="DataGridCellChange" Grid.Column="0" DataGridCell.Selected="DataGrid_GotFocus" />
<StackPanel Orientation="Horizontal" VerticalAlignment="Top" Name="ComboBoxPanel" Grid.Row="1" Margin="0,0,0,0" HorizontalAlignment="Left" >
</StackPanel>
<DataGrid x:Name="DataGridView" Grid.Row="2" HeadersVisibility="None" HorizontalAlignment="Stretch" Margin="0,0,0,0" VerticalAlignment="Stretch" ItemsSource="{Binding}" CurrentCellChanged="DataGridCellChange" Grid.Column="0"/>
</Grid>
</ScrollViewer>
The trouble is, that the ScrollViewer offers the following layout, which allows me to rename columns at the top, use a dropdown on the second row to select DataType of that column and then shows the data. With 150 columns, they have to all stay lined up, but performance is taking a huge hit.
While I realize that ScrollViewer is an easy solution, is there a better way when taking performance into account?
remove ScrollViewer and add
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode = "recycling"
EnableColumnVirtualization = "true"
EnableRowVirtualization = "true"
VirtualizingPanel.IsVirtualizingWhenGrouping="True"
MaxWidth="2560" MaxHeight="1600"

Listbox with gridsplitter resize itself instead of showing a scrollbar

In my application I need to have a listbox to display at the bottom of the screen. The listbox can be displayed of not (via a menu entry), and must be resizable in height. I placed it in a grid and used a gridsplitter to do the resize part, which works as intended.
My problem is, if no manual resize before, once log begins to appear in the listbox, this listbox does not show a scrollbar but instead in begins growing and takes more space. Once I trigger a resize using the gridsplitter, everything works as intended. What can I do to stop this ?
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ContentControl Grid.Row="0"
Grid.Column="1" />
<GridSplitter Grid.Row="1"
Height="5"
HorizontalAlignment="Stretch"
Visibility="{Binding ShowLogWindow,
Converter={StaticResource Bool2Vis}}" />
<ListBox Grid.Row="2"
VerticalAlignment="Stretch"
ItemsSource="{Binding Toolbox.LogEntries}"
MinHeight="50"
Visibility="{Binding ShowLogWindow,
Converter={StaticResource Bool2Vis}}" />
</Grid>
Set the Height of the third RowDefinition (or the ListBox itself) to 50 or whatever fixed height you want it to have.
Auto means size to content, which means that the height of the last row will grow as the ListBox grows. This is not what you want apparently.

How to resize UserControl

I have a UserControl in my WPF application. The UserControl has a listbox and could be any size, and underneath are some buttons.
When I place my UserControl in one of my Views, I would like it resize vertically as the parent resizes. BUT, I'd like the ListBox to decrease in heigh, not the entire control. This would mean on smaller screens, the buttons remain on screen but they don't see much content in the ListBox. I can use a MinHeight to ensure the buttons and at least 1 item in the listbox is visible!
I found similar question such as WPF user control does not resize with main window but my code doesn't have any height or width
My UserControl
<Grid>
<StackPanel>
<ListBox ItemsSource="{Binding MessageList" ></ListBox>
<WrapPanel HorizontalAlignment="Center">
<Button Content="Expand" HorizontalAlignment="Left" Margin="0,0,40,0"/>
</WrapPanel>
</StackPanel>
</Grid>
And the View
<Grid>
<StackPanel>
<uc:RecentList MessageList="{Binding Messages}" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
</StackPanel>
</Grid>
It doesn't matter how I resize my View, the UserControl appears to stay at the exact same size.
I tried the ViewBoxbut this was scaling everything!
I also tried (in the UserControl)
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox ItemsSource="{Binding MessageList, RelativeSource={RelativeSource AncestorType=UserControl}}" Grid.Row="0"></ListBox>
<WrapPanel Grid.Row="1" HorizontalAlignment="Center">
<Button Content="Expand" HorizontalAlignment="Left" Margin="0,0,40,0"/>
</WrapPanel>
</Grid>
But no difference.
StackPanel does not stretch its child elements in the direction of its Orientation.
So remove all StackPanels and define two rows in the Grid in the UserControl:
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0" ItemsSource="{Binding MessageList ...}"/>
<WrapPanel Grid.Row="1" HorizontalAlignment="Center">
<Button Content="Expand" HorizontalAlignment="Left" Margin="0,0,40,0"/>
</WrapPanel>
</Grid>
In main view:
<Grid>
<uc:RecentList ... />
</Grid>
Take a look at the Panels Overview article on MSDN.

Infinite control with ScrollBar nested inside a ScrollViewer

Could someone explain to me why the following XAML does not work the way I expect it to and if there are any workarounds for it?
I Expect the TextBox to respect the Min- and MaxHeight properties of the RowDefinition it uses. Instead it's MaxHeight is used to mask the available space, but at the sametime it's content is growing behind the mask... Ehhhh?!
The behavior I expect is the same as when you give the RowDefinition a non-infinite Height, 2 scrollbars. One ScrollBar for the TextBox and one for the rest of the screen.
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition MinHeight="100" MaxHeight="200" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<Button Content="Top" />
<TextBox Grid.Row="1" AcceptsReturn="True" xml:space="preserve" VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
***Enter a lot of text including enters here!***
</TextBox>
<Button Content="Bottom" Grid.Row="2" />
</Grid>
</ScrollViewer>
Hope someone can help me with this problem.
Thanks,
Wim
Setting min and max height on the TextBox allows the scrollbars to appear correctly.
<TextBox Grid.Row="1"
AcceptsReturn="True"
xml:space="preserve"
MinHeight="100"
MaxHeight="200"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
This is indeed some strange behaviour.
Seems like a combination of the Grid itself having no size constraints (being inside a ScrollViewer and having no Height set) and the RowDefinition having no Height set to an absolute Value doesn't correctly constrain the TextBox.
If you absolutely need this constellation and don't want to depend on anything else you can do this:
<RowDefinition MinHeight="100" MaxHeight="200" Height="{Binding ActualHeight, RelativeSource={RelativeSource Self}}"/>

How can I prevent the grid splitter from resizing a column outside of the window bounds?

I have the XAML shown below (for example). If you drag the grid splitter as far as it goes to the left, and keep dragging the mouse, the right-hand column will grow in size outside the bounds of the window - obviously not what I want.
The problem is that I can't set a hard MaxWidth on the right-hand column because the user can resize the window, thus increasing the available space for that column.
So far I think I need to bind the MaxWidth of the right-hand column to something like the window's client area minus the MinWidth of the left plus the width of the splitter column. But I'd like to avoid that if possible. Thoughts?
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="450"
Height="300">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" MinWidth="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="200" MinWidth="200" />
</Grid.ColumnDefinitions>
<Grid Grid.Column="0">
<Button>Monkey</Button>
</Grid>
<GridSplitter Grid.Column="1" Width="7" ResizeBehavior="PreviousAndNext" />
<Grid Grid.Column="2" Margin="4">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<GroupBox Grid.Row="0" Header="Spaghetti" Margin="0, 0, 0, 5">
<ComboBox HorizontalAlignment="Stretch" VerticalAlignment="Stretch">Noodles</ComboBox>
</GroupBox>
<Expander Grid.Row="1" Header="Batman" IsExpanded="True" Margin="0, 0, 0, 5">
<Button HorizontalAlignment="Stretch" VerticalAlignment="Stretch">Batman!</Button>
</Expander>
</Grid>
</Grid>
</Window>
I'm having the same problem. Maybe this will be a partial answer. I bound the MaxWidth of the column I'm expanding to the ActualWidth of its grid. The goal being that the splitter never exceed the size of its grid. The binding works correctly but it's not achieving the goal because once my grid splitter gets to the edge of the grid, the grid starts resizing larger as I drag the splitter. If we can make the grid not resize with the splitter, this should work.

Resources