How to set WPF ScrollViewer height dynamically? - wpf

I have the following test sample:
<Window x:Class="WpfScrollTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="200" Width="200">
<Border>
<StackPanel>
<Label Width="Auto" Height="Auto" Content="Text to mess up the scrollview"/>
<ScrollViewer Height="{Binding RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Border}}, Path=ActualHeight}">
<StackPanel>
<Button MinWidth="100" MinHeight="100" Content="Button"/>
<Button MinWidth="100" MinHeight="100" Content="Button"/>
</StackPanel>
</ScrollViewer>
</StackPanel>
</Border>
</Window>
Which creates this:
My question is how do I set the ScrollViewer.Height dynamically while still being able to see the bottom of the scrollbar? In my sample, the Height of the ScrollViewer is too long because of the Label above it ..
I don't want to fix the Height of the ScrollViewerto a static value.

I would recommend to remove the outer StackPanel to a Grid, since Stackpanel wont respect the children size. And remove the ScrollViewer.Height binding. Now you just need to create two RowDefinition for the Grid and place the Label to Grid.Row=0 and ScrollViwer to Grid.Row=1.
Code is below. So my tip here is, use StackPanel/Canvas only if necessary and may be for the inner levels. Try to use Grid more to get very dynamic layouts.
<Border>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Width="Auto" Height="Auto" Content="Text to mess up the scrollview"/>
<ScrollViewer Grid.Row="1" >
<StackPanel>
<Button MinWidth="100" MinHeight="100" Content="Button"/>
<Button MinWidth="100" MinHeight="100" Content="Button"/>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>

Related

WPF - Making ONE control stretch vertically

I'm new to WPF/XAML and with a few hours I got a GUI that is presentable except for one thing. I got the controls to resize horizontally with the window but I cannot figure out what I'm missing to do the same vertically. I want only the DataGrid control to stretch vertically. The Datagrid control is not getting the hint. What am I missing?
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
MinHeight="480"
MinWidth="660"
Width="660"
Height="480"
Title="Windows Title">
<Grid Margin="0,0,-0.2,0.2">
<StackPanel Orientation="Vertical">
<DataGrid
x:Name="dataGrid"
Width="Auto"
Height="Auto"
MinHeight="300"
Grid.Row="0"
HorizontalAlignment="Stretch"
Margin="10,10,10,0"
VerticalAlignment="Stretch"
IsReadOnly="True"
TextBlock.FontSize="16"/>
<TextBox
x:Name="Interpretation"
Height="100"
MinHeight="100"
Width="Auto"
HorizontalAlignment="Stretch"
Margin="10,10,10,0"
IsReadOnly="True"
Text="Interpretation of Results"
TextAlignment="left"
TextBlock.FontSize="20"
TextWrapping="Wrap"/>
</StackPanel>
</Grid>
</Window>
SOLUTION with ADDED controls and comments
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="600"
MinHeight="600"
MinWidth="660"
Width="660"
Title="Windows Title">
<Grid>
<Grid.RowDefinitions>
<!-- Height="Auto" -> Fill space required by content -->
<!-- Height="*" -> Fill all space not taken up by other rows (The one that will stretch) -->
<RowDefinition Height="auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- using HorizontalAlignment="Stretch" or Width="Auto" in the controls is redundant -->
<!-- Don't forget to add Grid.Row="#" properties in each control/row below -->
<TextBox
MinHeight="25"
Grid.Row="0"
HorizontalAlignment="Stretch"
Margin="10,10,10,0"
Background="#FF98D6EB"
Foreground="White"
IsReadOnly="True"
Text="Results"
TextAlignment="Center"
TextBlock.FontSize="20"
TextWrapping="Wrap"/>
<DataGrid
x:Name="dataGrid"
MinHeight="200"
Grid.Row="1"
Margin="10,10,10,0"
IsReadOnly="True"
TextBlock.FontSize="16"/>
<TextBox
x:Name="Interpretation"
MinHeight="100"
Grid.Row="2"
Margin="10,10,10,0"
Background="#FF98D6EB"
IsReadOnly="True"
Text="Interpretation of Results"
TextAlignment="left"
TextBlock.FontSize="20"
TextWrapping="Wrap"/>
<!-- UniformGrid speads the buttons size evenly with resizing of the window -->
<!-- HorizontalAlignment="Stretch" is redundant -->
<!-- notice the Grid.Row="3" is a property of <UniformGrid> and not the controls within it-->
<UniformGrid
Height="100"
Grid.Row="3"
Columns="2"
Rows="1">
<Button
Name="btnContinue"
MinWidth="250"
Margin="10"
Content="Continue"
TextBlock.FontSize="50"
TextBlock.FontWeight="Bold"/>
<Button
Name="btnCancel"
MinWidth="250"
Margin="10"
Content="Cancel"
TextBlock.FontSize="50"
TextBlock.FontWeight="Bold"/>
</UniformGrid>
</Grid>
</Window>
A Grid will expand to fill its parent. You can use the root level grid in your window, if you won't be adding any more content.
To put two controls in a grid one above the other, define rows, and don't neglect to add the Grid.Row="..." attribute to the child controls to determine which grid rows they'll be in, or else they'll be superimposed on one another.
<Grid Margin="0,0,-0.2,0.2">
<Grid.RowDefinitions>
<!-- Height="*" -> Fill all space not taken up by other rows -->
<RowDefinition Height="*" />
<!-- Height="Auto" -> Fill space required by content -->
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid
Grid.Row="0"
x:Name="dataGrid"
Margin="10,10,10,0"
IsReadOnly="True"
TextBlock.FontSize="16"
/>
<TextBox
Grid.Row="1"
x:Name="Interpretation"
Height="100"
MinHeight="100"
Margin="10,10,10,0"
IsReadOnly="True"
Text="Interpretation of Results"
TextAlignment="left"
TextBlock.FontSize="20"
TextWrapping="Wrap"
/>
</Grid>
If you've got a series of auto-sized children that'll be adjacent, it can be simpler to make one row a StackPanel:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<DataGrid
Grid.Row="0"
...
/>
<StackPanel
Grid.Row="1"
Orientation="Vertical"
>
<Label>Stuff</Label>
<TextBox ... />
<Label>More Stuff</Label>
<TextBox ... />
</StackPanel>
</Grid>
You put it in a stackpanel.
Stackpanels only ever grow to the size their contents need, you should use Grid instead, grids and their cells will fill their container.

XAML/WPF - ScrollViewer which has StackPanel inside is not scrolling

I have the following StackPanel inside a ScrollViewer that shows User Control elements Whenever a specific event occurs:
Note: many UserControls might appear in the StackPanel that's why I added a Scrollviewer
<ScrollViewer
VerticalScrollBarVisibility="Auto"
Grid.Row="2"
CanContentScroll="True"
Grid.ColumnSpan="2">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding UserControls}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:UserControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
Although, the StackPanel is still going out of range and the scroll bars doesn't show and doesn't work!
I tried fixing the height of both the StackPanel and the ItemsControl but it does't seem to work either...
Window Layout containing the ScrollViewer:
<Grid Margin="0,15,0,0">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Label
Content="This is a Label"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5,5,0,0"
FontSize="15"
Grid.Row="0" Grid.ColumnSpan="2">
</Label>
<StackPanel Grid.Row="1" Orientation="Horizontal" Grid.ColumnSpan="2">
<ComboBox
ItemsSource="{Binding Something}"
Text="Confirm with..."
SelectedItem="{Binding Something}"/>
<Button
HorizontalAlignment="Left"
Margin="5"
Content="Add new UserControl"
Command="{Binding Path=AddUserControl}"/>
</StackPanel>
<ScrollViewer
VerticalScrollBarVisibility="Auto"
Grid.Row="2"
CanContentScroll="True"
HorizontalScrollBarVisibility="Auto">
<StackPanel Orientation="Vertical">
<ItemsControl ItemsSource="{Binding UserControls}" Height="300">
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:UserControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</ScrollViewer>
</Grid>
Here's my UserControl that is added to the StackPanel Inside the ScrollViewer:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<StackPanel
Orientation="Horizontal"
Grid.Row="0">
<Button
Name="DeleteFilter"
HorizontalAlignment="Left"
Margin="5"
Content="-"/>
<ComboBox
Margin="5"
IsEditable="False"
IsReadOnly="True"
Width="150"
ItemsSource="{Binding SomeObject}"
DisplayMemberPath="Name"
SelectedItem="{Binding SomeObjectProperty}"/>
<ComboBox
Margin="5"
IsEditable="False"
IsReadOnly="True"
Width="150"
ItemsSource="{Binding AnotherObject}"
DisplayMemberPath="Name"
SelectedItem="{Binding AnotherObjectProperty}"/>
<TextBox
x:Name="Value"
Text="{Binding TextBoxValueString}"
TextAlignment="Center"
Width="100"
Margin="5"
Visibility="{Binding TextBoxVisibility}"/>
</StackPanel>
</Grid>
I'm new to XAML and WPF.
Any Suggestions?
ScrollViewers and StackPanel don't work well together. This is because a StackPanel measures its child elements with infinite horizontal space if its Orientation property is set to Horizontal and infinite vertical space if it is set to Vertical. Please refer to my answer here for more information:
How to scroll the datagrid in stackpanel?
So you should replace the StackPanel with another Panel or remove it altogether:
<ScrollViewer
VerticalScrollBarVisibility="Auto"
Grid.Row="2"
CanContentScroll="True"
HorizontalScrollBarVisibility="Auto">
<ItemsControl ItemsSource="{Binding UserControls}" Height="300">
<ItemsControl.ItemTemplate>
<DataTemplate>
<views:UserControl/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
I was able to get it run with this setting:
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
Grid.RowSpan="10">
<StackPanel Orientation="Vertical"
Grid.RowSpan="6"
Name="SPanel"
Margin="0,0,-0.4,1.4"
CanVerticallyScroll="True">
<Border BorderBrush="Black"
BorderThickness="1"
Margin="3"
Grid.Row="0"
Name="ChartHolder0">
<dvc:Chart Cursor="Cross"
Background="#FFFFFCF2" />
</Border>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="3"
Grid.Row="0"
Name="ChartHolder0">
<dvc:Chart Cursor="Cross"
Background="#FFFFFCF2" />
</Border>
<Border BorderBrush="Black"
BorderThickness="1"
Margin="3"
Grid.Row="0"
Name="ChartHolder0">
<dvc:Chart Cursor="Cross"
Background="#FFFFFCF2">
</dvc:Chart>
</Border>
</StackPanel>
</ScrollViewer>
And this is what I get:
Was looking for a fix for this problem and Mishka's answer worked for me.
I don't have enough reputation to comment on an answer, but wanted to say that Background="White" fix from Mishka does work for me on Silverlight (didn't try WPF).
<ScrollViewer Background="White">
<StackPanel Margin="5" Background="White">
Works, if I only put Background on the StackPanel the 5 pixel Margin on the stackpanel doesn't scroll. If I don't put Background on either then both the 5 pixel margin and any margins on controls inside the stackpanel dont scroll.
You are missing a Background for either the StackPanel or ItemsControl(Your choise).
Default Background is Null.
With Background Null, the ScrollViewer doesn't get mouse events for the mouse wheel,
and doesn't scroll.

How to make a WPF window resizable if a Grid row contains a collapsed element

I have a problem with the design of a resizable WPF window.
The result shall be that the window elements are resized automatically while changing the width and height of the window by dragging with the mouse.
The base panel is a Dockpanel on which the first element is a ToolBar which is positioned at the top.
Then comes a Grid with two rows.
In each of the rows is a DockPanel with inner elements.
Some of these elements can be hidden by Visibilty.Collapsed (which would be made by properties in the final version).
For testing purposes i have two DockPanels after ToolBar and i have set the visibility of the first in the XAML file to Collapsed.
When i start the application the window seems to be correct.
But as i click the bottom of the window for changing the height by dragging, the second Dockpanel gets smaller and the place where the collapsed Dockpanel would have been gets visible.
What have i done wrong?
Here is my XAML:
<Window x:Class="WPFResizable.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFResizable"
mc:Ignorable="d"
Title="MainWindow"
SizeToContent="Height"
Width="525">
<DockPanel VerticalAlignment="Stretch" Background="AliceBlue">
<Button DockPanel.Dock="Top" Height="40" Content="ToolBar"/>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DockPanel Grid.Row="0" Grid.Column="0" VerticalAlignment="Stretch" Visibility="Collapsed">
<GroupBox Margin="5"
Header="Group 1">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TextBox Text="Test 1"
MinWidth="400"
MinHeight="100"
Margin="5"/>
</ScrollViewer>
</GroupBox>
</DockPanel>
<DockPanel Grid.Row="1" Grid.Column="0" VerticalAlignment="Stretch">
<GroupBox Margin="5"
Header="Group 2">
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Auto">
<TextBox Text="Test 2"
MinWidth="400"
MinHeight="100"
Margin="5"/>
</ScrollViewer>
</GroupBox>
</DockPanel>
</Grid>
</DockPanel>
</Window>
I think this will do what you want, if I correctly understand what you're trying to do.
VerticalAlignment="Stretch" is redundant on a Grid child, and if there's only one column, you don't need to specify Grid.Column on the children.
Your first grid row had Height="*", which resulted in it taking up half the height of the grid whether or not its content was visible. I've written a Style with a Trigger that changes that row's height to Auto when its content is not visible. This depends on there being exactly one element in the row, but since you've got a DockPanel there, you seem to be thinking along those lines already.
<Grid DockPanel.Dock="Bottom">
<Grid.RowDefinitions>
<RowDefinition>
<RowDefinition.Style>
<Style TargetType="RowDefinition">
<!--
Must set the default in a Setter instead of an attribute, or
dependency property precedence will override the trigger
below.
-->
<Setter Property="Height" Value="*" />
<Style.Triggers>
<DataTrigger
Binding="{Binding Visibility, ElementName=TopChildPanel}"
Value="Collapsed"
>
<Setter Property="Height" Value="Auto" />
</DataTrigger>
</Style.Triggers>
</Style>
</RowDefinition.Style>
</RowDefinition>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<DockPanel x:Name="TopChildPanel" Grid.Row="0" Visibility="Visible">
<GroupBox Margin="5"
Header="Group 1">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<TextBox Text="Test 1"
MinWidth="400"
MinHeight="100"
Margin="5"/>
</ScrollViewer>
</GroupBox>
</DockPanel>
<DockPanel Grid.Row="1">
<GroupBox
Margin="5"
Header="Group 2">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
>
<TextBox
Text="Test 2"
MinWidth="400"
MinHeight="100"
Margin="5"/>
</ScrollViewer>
</GroupBox>
</DockPanel>
</Grid>

My WPF UserControl must never get wider than the containing ButtonBar

I have bound at top the Width of the UserControl to the Width of the ButtonGrid at top.
It does not work. I want my UserControl always that wide as the width of the ButtonGrid. The problem is loading documents with a long name > Sum(Width of 3 buttons) makes the UserControl as wide as the document name. Now imagine having document names with 100 chars or longer and you add documents the UserContol will bump and jump...
<UserControl x:Class="TBM.View.DocumentListView"
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:Behaviours="clr-namespace:FunctionalFun.UI.Behaviours"
xmlns:Helper="clr-namespace:TBM.Helper"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="300"
Width="{Binding ElementName=ButtonGrid,Path=Width}"
>
<UserControl.Resources>
<Helper:BooleanToVisibilityConverter x:Key="boolToVisibilityConverter" />
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox
SelectionMode="Extended"
VirtualizingStackPanel.IsVirtualizing="True"
VirtualizingStackPanel.VirtualizationMode="Recycling"
Behaviours:MultiSelectorBehaviours.SynchronizedSelectedItems="{Binding SelectedDocumentViewModelList,Mode=TwoWay}"
Width="Auto"
Focusable="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Name="documentListBox"
BorderThickness="1"
ItemsSource="{Binding DocumentList}"
Visibility="{Binding ElementName=documentListBox,Path=HasItems, Converter={StaticResource boolToVisibilityConverter}}"
>
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!--<TextBlock Text="{Binding Path=Id}" />-->
<TextBlock Text="{Binding Path=DocumentName}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<UniformGrid x:Name="ButtonGrid"
Grid.Row="1"
Rows="1"
HorizontalAlignment="Stretch"
VerticalAlignment="Bottom"
>
<Button Command="{Binding Path=DeleteDocumentCommand}" HorizontalAlignment="Stretch" Content="Delete" />
<Button Command="{Binding Path=AddDocumentCommand}" HorizontalAlignment="Stretch" Content="Add" />
<Button Command="{Binding Path=OpenDocumentCommand}" HorizontalAlignment="Stretch" Content="Open" />
</UniformGrid>
</Grid>
</UserControl>
You could also set the width of the ParentGrid(containing the control) to a fixed size, en then horizontal alignment to Stretch
Try 2 stuff
Use horizontal alignment on user control to stretch
Set binding to MaxWidth same as Width binding

Bind the Height of the Listbox inside the StackPanel to StackPanel`s height

I want to bind the Height of the ListBox to the Height of the StackPanel so the ListBox stretches itself Vertically so the green area is not visible anymore.
When there is no item in the listbox its hidden.
When there is item > 1 the ListBox must be stretching itself to the add/del buttons so the add/del buttons are always at the bottom of the stackpanel(dont want to use dockpanel for that)
How can I do that? I get no binding erros?
<StackPanel x:Name="stack" Background="Green" DataContext="{Binding DocumentViewModelList/}" Orientation="Vertical" >
<ListBox SelectionMode="Single" VirtualizingStackPanel.IsVirtualizing="False"
SelectedItem="{Binding SelectedDocumentViewModel,Mode=TwoWay}"
Height="{Binding ElementName=stack,Path=Height}"
Width="Auto"
Focusable="True"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
ScrollViewer.VerticalScrollBarVisibility="Auto"
Grid.Row="1"
Name="documentListBox"
BorderThickness="1"
ItemsSource="{Binding DocumentList}"
Visibility="{Binding ElementName=documentListBox,Path=HasItems, Converter={StaticResource boolToVisibilityConverter}}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Path=Id}" />
<TextBlock Text="{Binding Path=Name}" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<!--<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}" />
</Style>
</ListBox.ItemContainerStyle>-->
</ListBox>
</StackPanel>
Simply use a Grid instead of a StackPanel:
<Grid x:Name="grid"
Background="Green"
DataContext="{Binding DocumentViewModelList}">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="0"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" ..... />
<UniformGrid Grid.Row="1"
Rows="1"
HorizontalAlignment="Center"
VerticalAlignment="Bottom">
<Button Content="Delete" />
<Button Content="Add" />
<Button Content="Open" />
</UniformGrid>
</Grid>
The ListBox simply takes up the entire space of the first row, while the UniformGrid takes up the bottom row with only the space it needs (and makes the buttons all the same size as a bonus).
No need for hard-coded width/height values (or any bindings for height/width), and no value converters needed.
To implement the selective height (if has items x else y) use a value converter... also, I think height will be NaN so try ActualHeight (Bad practice but might work)... use a tool like snoop to see the values!
Is their a specific reason you are using a stackpanel? I grid will work better (StackPanel only give the min space needed where a grid can give as much space as needed)?
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ListBox Grid.Row="1" />
<StackPanel Orientation="Horizontal" Grid.Row="1">
<!-- Buttons -->
</StackPanel>
</Grid>

Resources