WPF , Getting verticalstretch working as expected! - wpf

I am trying to make the vertical stretch to work as expected i WPF, but for some reason it only takes the space it needs, and not the space available.
First, I am using WPF with C#, and Prism.
In the Shell.xaml (main xaml for the application) I have a grid with 2 columns and one row. The idea is to have a side panel and a main app area. The main app area grid is set to Auto Width and Auto Height. This is working as expected, and it does scale to fit the whole application in Height and Width.
Then I am using prism to insert the view (as a UserControl component) into the main app area. The UserControl is also set to Auto Width and Height, and by looking at the default settings in Expression Blend, the HorizontalAlignment and the VerticalAlignment are set to stretch!
However, the prism loaded UserControl only stretches in Width and not in height! By giving this a background color for visual feedback, I can see that it only takes the vertical space as needed, and not the whole area available!
What could be the solution for this? I have tried going trough all settings an manually overriding them to Width and Height Auto, Horizontal and Vertical Alignment to Stretch, but nothing seems to work as expected!
Some code:
(Shell.xaml)
<Window Height="1140" Width="1450">
<Grid Margin="0" Background="White">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="250" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Background="#FFEEEEEE" Grid.Column="0" Grid.Row="0">
</Grid>
<ItemsControl Width="Auto" Height="Auto" Grid.Row="0" Grid.Column="1" Name="MainRegion" cal:RegionManager.RegionName="MainRegion" Padding="10" Margin="10, 0, 0, 0" Background="#FFFFFFD5" />
</Grid>
</Grid>
</Window>
(view that should inherit the parent height):
<UserControl Width="Auto" Height="Auto" Background="Red" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<!-- I want this to take the full width and height of the parent -->
</UserControl>
So is this a limitaion in the way the views are loaded into the main xaml, or is this a UserControl limitation, or something else that I do not understand?
Just to clarify; I do see the background color of ItemsControl defined in Shell.xaml stretching both Horizontal and Vertical, but not the background of the view loaded into ItemsControl.
Note that I have removed some of the xaml to make the point easier to understand!
Thanks!

The default for ItemsControl.ItemsPanelTemplate is StackPanel, which compresses all of its children vertically giving the symptoms you describe.
The solution code-zoop gave changes this to <Grid>. This will work well as long as there is can only be one view in the region. But if the region has more than one view they will all be laid on top of each other instead of beside each other.
If you want a region that can handle multiple views but allow views to stretch to the full size of the region, use a DockPanel instead:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
This will put the first view in a panel on the left side, the next one beside it, and so on until the last view which will fill all the remaining space.
If you prefer to have the multiple views stack vertically like they did with StackPanel, you'll have to set that up in ItemContainerStyle:
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<DockPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="DockPanel.Dock" Value="Top" />
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>

I found a solution. I have to override the ItemsPanel Template to use a grid (or similar container):
<ItemsControl>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
I have also in this case removed some of the xaml to make it more readble!
Thanks

Have you tried setting HorizontalContentAlignment and VerticalContentAlignment to Stretch in the ItemsControl?

Related

Force expander to take all available space

I got something like:
1) <grid> with some rows -> 2) <stackPanel> -> 3) Expander -> 4) Expander content...
..and I can't force expander content to be stretched as long and as wide as possible by currently free space in the given grid.row (#1). Whatever I try, expander keeps using to take minimum necessary space to renders its content. But if I do something like MinHeight=500, then it's OK.
P.S. I had an idea to hack it. Just to create transparent line, stretched through the row, and then bind expander height to the actual height.. but it seems ugly to me. Maybe someone knows another way?
<Grid Name="GlobalPanel" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="0.7*" />
<RowDefinition Height="0.3*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Vertical">
<Expander Name="MainExpander"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<!-- HERE EXPANDERS CONTENT WHICH I WANT TO BE STRETCHED -->
</Expander>
</StackPanel>
</Grid>

How to make scrollviewer work with Height set to Auto in WPF?

I have learned that if the height of a grid row, where the ScrollViewer resides, is set as Auto, the vertical scroll bar will not take effect since the actual size of the ScrollViewer can be larger than the height in sight. So in order to make the scroll bar work, I should set the height to either a fixed number or star height
However, I now have this requirement, that I have two different views reside in two grid rows, and I have a toggle button to switch between these two views: when one view is shown, the other one is hidden/disappeared. So I have defined two rows, both heights are set as Auto. And I bind the visibility of the view in each row to a boolean property from my ViewModel (one is converted from True to Visible and the other from True to Collapsed. The idea is when one view's visibility is Collapsed, the height of the grid row/view will be changed to 0 automatically.
The view show/hidden is working fine. However, in one view I have a ScrollViewer, which as I mentioned doesn't work when the row height is set as Auto. Can anybody tell me how I can fulfill such requirement while still having the ScrollViewer working automatically`? I guess I can set the height in code-behind. But since I am using MVVM, it would require extra communication/notification. Is there a more straightforward way to do that?
In MVVM, the way that worked for me was to bind the height of the ScrollViewer to the ActualHeight of the parent control (which is always of type UIElement).
ActualHeight is a read-only property which is only set after the control has been drawn onto the screen. It may change if the window is resized.
<StackPanel>
<ScrollViewer Height="{Binding Path=ActualHeight,
RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}">
<TextBlock Text=Hello"/>
</ScrollViewer>
</StackPanel>
But what if the parent control has an infinite height?
If the parent control has an infinite height, then we have a bigger problem. We have to keep setting the height of all parents, until we hit a control with a non-infinite height.
Snoop is absolutely invaluable for this:
If the "Height" for any XAML element is 0 or NaN, you can set it to something using one of:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=UIElement}}"
VerticalAlignment="Stretch"
Height="Auto"
Hint: Use VerticalAlignment="Stretch" if you are a child of a Grid with a <RowDefinition Height="*">, and the Binding RelativeSource... elsewhere if that doesn't work.
If you're interested, here is all of my previous attempts to fix this issue:
Appendix A: Previous Attempt 1
Can also use this:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
Appendix B: Previous Attempt 2
Useful info: see Auto Height in combination with MaxHeight.
If nothing seems to work, it's probably because the ActualHeight of the parent is either 0 (so nothing is visible) or huge (so the scrollviewer never needs to appear). This is more of a problem if there are deeply nested grids, with a scrollviewer right at the bottom.
Use Snoop to find the ActualHeight of the parent StackPanel. In properties, filter by the word "Actual", which brings back ActualHeight and ActualWidth.
If ActualHeight is zero, give it a minimum height using MinHeight, so we can at least see something.
If ActualHeight is so huge that it goes off the edge of the screen (i.e. 16,000), give it a reasonable maximum height using MaxHeight, so the scrollbars will appear.
Once the scrollbars are appearing, then we can clean it up further:
Bind the Height of the StackPanel or Grid to the ActualHeight of the parent.
Finally, put a ScrollViewer inside this StackPanel.
Appendix C: Previous Attempt 3
It turns out that this can sometimes fail:
Height="{Binding Path=ActualHeight, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType=StackPanel}}"
The reason? It the binding fails, the height will be zero and nothing will be seen. The binding can fail if we are binding to an element which is not accessible. The binding will fail if we are going up the visual tree, then down to a leaf node (e.g. up to the parent grid, then down to the ActualHeight of a row attached to that grid). This is why binding to the ActualWidth of a RowDefinition simply won't work.
Appendix D: Previous Attempt 4
I ended up getting this working by making sure that Height=Auto for all of the parent elements from us to the first <Grid> element in the UserControl.
Change Height from Auto to *, if you can.
Example:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="525">
<StackPanel Orientation="Horizontal" Background="LightGray">
<Grid Width="100">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll1">
<Border Height="300" Background="Red" />
</ScrollViewer>
<TextBlock Text="{Binding ElementName=_scroll1, Path=ActualHeight}" Grid.Row="1"/>
</Grid>
<Grid Width="100">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto" x:Name="_scroll2">
<Border Height="300" Background="Green" />
</ScrollViewer>
<TextBlock Text="{Binding ElementName=_scroll2, Path=ActualHeight}" Grid.Row="1"/>
</Grid>
</StackPanel>
</Window>
I've had similar problem, taking me hours to figure out the solution. What solved it was using a Dockpanel as parent container instead of a StackPanel. Just specify all children to dock to top if the functionality should be similar to vertical stackpanel. Consider using LastChildFill="False" in the Dock XAML which is'n default.
So instead of:
<StackPanel Orientation="Horizontal">
<Textbox>SomeTextBox</Textbox>
<Scrollviewer/>
</StackPanel>
Try:
<DockPanel LastChildFill="False">
<Textbox DockPanel.Dock="Top">SomeTextBox</Textbox>
<Scrollviewer DockPanel.Dock="Top"/>
</DockPanel>
You can either set a fix height on your ScrollViewer but then you have to consider that the second row of your grid will have that height too since row's first child will be the ScrollViewer and row's height is auto, or you bind the height of ScrollViewer to another control in your layout. We don't know how your layout looks alike.
At the end if you don't like neither of both just set the row's height to * as swiszcz suggested or hack wpf write your own custom panel that will be able to layout everything possible in every parallel universe or something like that. :)
What I discover is that you have to put your ScrollViewer within a container that has Height=Auto or you get his parent Heigh Actual Size and apply it to that container.
In my case I have UserControl like
<Grid Margin="0,0,0,0" Padding="0,2,0,0">
<ScrollViewer Height="Auto" ZoomMode="Disabled" IsVerticalScrollChainingEnabled="True" VerticalAlignment="Top"
HorizontalScrollMode="Enabled" HorizontalScrollBarVisibility="Disabled"
VerticalScrollMode="Enabled" VerticalScrollBarVisibility="Visible">
<ListView ItemsSource="{x:Bind PersonalDB.View, Mode=OneWay}" x:Name="DeviceList"
ScrollViewer.VerticalScrollBarVisibility="Hidden"
ItemTemplate="{StaticResource ContactListViewTemplate}"
SelectionMode="Single"
ShowsScrollingPlaceholders="False"
Grid.Row="1"
Grid.ColumnSpan="2"
VerticalAlignment="Stretch"
BorderThickness="0,0,0,0"
BorderBrush="DimGray">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<ItemsStackPanel AreStickyGroupHeadersEnabled="False" />
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate x:DataType="local1:GroupInfoList">
<TextBlock Text="{x:Bind Key}"
Style="{ThemeResource TitleTextBlockStyle}"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
</ScrollViewer>
</Grid>
And I add it dinamically to ContentControl which is within a Page.
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Margin="0,0,12,0">
<Grid.RowDefinitions>
<RowDefinition Height="70" />
<RowDefinition Height="*" MinHeight="200" />
</Grid.RowDefinitions>
<Grid Grid.Row="1" VerticalAlignment="Stretch" HorizontalAlignment="Stretch" >
<ContentControl x:Name="UIControlContainer" />
</Grid>
</Grid>
Notice that Heigh of the Row is *
When I populate ContentControl I use this code in Loaded event
UIControlContainer.Content = new UIDeviceSelection() {
VerticalAlignment = VerticalAlignment.Stretch,
HorizontalAlignment = HorizontalAlignment.Stretch,
Height = UIControlContainer.ActualHeight,
Width = UIControlContainer.ActualWidth
};
And also when ContentControl changes its size you have to update size of the UserControl.
UIControlContainer.SizeChanged += UIControlContainer_SizeChanged;
private void UIControlContainer_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (UIControlContainer.Content != null)
{
if (UIControlContainer.Content is UserControl)
{
(UIControlContainer.Content as UserControl).Height = UIControlContainer.ActualHeight;
(UIControlContainer.Content as UserControl).Width = UIControlContainer.ActualWidth;
}
}
}
Enjoy!
P.S. Acctually I did it for UWP.

wpf layout help

I have the following xaml which resides in a wpf user control -
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
<RowDefinition Height="30" />
</Grid.RowDefinitions>
<TextBox
x:Name="MyTxt"
TextWrapping="WrapWithOverflow"
Grid.Row="0"
/>
<ListView
x:Name="MyList"
ItemsSource="{Binding}"
Grid.Row="1"
/>
<Label
Grid.Row="2"
/>
</Grid>
This control is nested within a grid in a view. I would like to have the text box be a set height at the top of the grid, the label at the bottom showing as a fixed height at the bottom of the grid. I want the list view to fill the rest of the screen area.
The problem that I am having is the listview does not size correctly. If I have too many records that show up in it, it extends beyond the window and no scroll bars are available to scroll down. I therefore cannot get to the bottom to see the vertical scroll bar if the data stretches off to the right of the screen.
I was able to set the listview to a fixed height and that worked, but I would like it to be more dynamic and resize with the window if possible.
Does anyone have any tips that might get the sizing correct?
Thanks for any thoughts.
EDIT - Here is the xaml for the containing grid in the mainwindow view. this was adapted from the article by Josh Smith here
<Grid>
<Border
Style="{StaticResource MainBorderStyle}"
>
<HeaderedContentControl
Content="{Binding Path=Workspaces}"
ContentTemplate="{StaticResource WorkspacesTemplate}"
/>
</Border>
</Grid>
I do have the scrollviewer properties set as mentioned in some of the answers below.
Here is the datatemplate for the workspace
<DataTemplate x:Key="WorkspacesTemplate">
<TabControl
IsSynchronizedWithCurrentItem="True"
ItemsSource="{Binding}"
ItemTemplate="{StaticResource ClosableTabItemTemplate}"
Margin="4"
/>
</DataTemplate>
Can you just add these properties to the listview?
ScrollViewer.CanContentScroll = "True"
ScrollViewer.VerticalScrollBarVisibility="Visible" <!-- or "Auto" -->
Everything else looks ok to me. You have the 3 rows, 2 of which are absolute, the other stretching. You also have the listview in the 2nd row, so it should stretch with it.
if that doesn't work, try wrapping the ListView in a scrollviewer
<ScrollViewer>
<ListView/>
</ScrollViewer>
What is the VerticalAlignment of a ListBox by default? You might need to set the vertical alignment to Stretch.
<ListView
x:Name="MyList"
ItemsSource="{Binding}"
Grid.Row="1"
VerticalAlignment="Stretch"
/>
I was able to get it working. If I change the containing grid in the main window to use a ContentControl instead of a HeaderedcontentControl, it works as expected.
Thank for any help.

Why doesn't the contentpresenter fill the grid cell?

Ok I have a contentpresenter inside a grid cell:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition></ColumnDefinition>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="0" Grid.Column="0">
<RadioButton GroupName="a" IsChecked="{Binding Path=SpecifyRatedValues, Mode=TwoWay}">Specify</RadioButton>
<RadioButton GroupName="b" IsChecked="{Binding Path=SpecifyRatedValues, Converter={StaticResource invertBoolean}}">Auto generate</RadioButton>
</WrapPanel>
<Border BorderBrush="Black" BorderThickness="3" Grid.Row="1" Grid.Column="0">
<ContentPresenter Content="{Binding Path=RatedValues}"></ContentPresenter>
</Border>
</Grid>
The contentpresenter finds which UI element to use by the datatemplate defined under resources:
<DataTemplate DataType="{x:Type ViewModels:RatedValuesViewModel}">
<Views:RatedValuesView />
</DataTemplate>
Now, everything works as expected, except for one thing: the View which is placed inside the contentpresenter at runtime does not expand to fill the entire cell. It leaves a big margin on the left and right side.
How can I make the view inside the contentpresenter fill the entire available area?
HorizontalAlign="Stretch" and VerticalAlign="Stretch" are important; however, you also have to remember how Grids work. Grid units are either in absolute pixels, "Auto", which means they size to fit their contents (i.e. minimum size to show everything), or "stars" which means fill up all available space.
Set your second RowDefinition's height to "*". You may also want to set easily-distinguishable border brushes and thicknesses on your grid. Sometimes, it's easy to think X isn't filling up all the available space, when it's really X's container, or X's container's container that isn't filling up the space. Use bright primary colors and large thicknesses (3 or so) and you can tell quickly who's not filling things up.
Chaiguy got it! The View had an explict Width and Height which constrained the view when placed in the cell. Thanks :-)
You must set HorizontalAlign=Stretch, VerticalAlign=Stretch to both Border and Content Presenter to fill the space in grid. and make width=auto.

Bind the height of a grid row to it's contents in WPF

I have a grid with a few rows. In the top row, I have an ItemsControl that is bound dynamically to a collection and uses a DataTemplateSelector and ItemsPanelTemplate (with a single horizontally arranged WrapPanel). Here's a stripped-down version of what I have so far:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40" />
<RowDefinition Height="2" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<GridSplitter Background="#666" Grid.Row="1" Height="Auto" Width="Auto" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" />
<ItemsControl x:Name="items" Background="#DDD" Grid.Row="0" ItemTemplateSelector="{StaticResource itemTemplateSelector}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</Grid>
Instead of setting that first row's height to 40, I'd like to set it dynamically based on the minimum height that the ItemsControl must be to fit all of its contents. Since the ItemsControl's height is being constrained by the Grid Row's height, I can't figure out which element I should be binding and to what element's property I should be binding it to.
It would make sense to bind the Grid Row's Height to the "desired height" of the ItemsControl. So, I found the DesiredSize property and bound my RowDefinition's Height to the ItemsControl's DesiredSize.Height. That works when it first loads, but does not update as I resize the control (remember, I'm using a WrapPanel as my ItemsPanelTemplate, so as I resize the window, the height of the ItemsControl should be changing).
Does anyone know if this kind of situation is even supported by the binding framework, or would I need to add event handler code to accomplish this?
Thanks.
Did you try setting height to Auto to achieve what you want:
<RowDefinition Height="Auto"/>
(or am i thinking too simple here.. ?)

Resources