understanding WPF Layout - c#

I got a ListView (wrapped in a ScrollViewer), which resizes itself if the elements inseretd exceed the visible area until the parents Max Height is reached.
The ListView is embedded the following way.
<Window ... SizeToContent="Height">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<ScrollViewer x:Name="MyScrollViewer" Grid.Row="0" Grid.Column="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ListView Name="MyListView"
ItemsSource="{Binding Path=RetrievalStringResults, Mode=OneWay}"
IsSynchronizedWithCurrentItem="True" />
</ScrollViewer>
<DockPanel Grid.Row="0" Grid.Column="1">
...
</DockPanel>
<Expander Grid.Row="1" Grid.ColumnSpan="2">
<DockPanel Height="150" HorizontalAlignment="Stretch">
<TextBox DockPanel.Dock="Top" />
</DockPanel>
</Expander>
<DockPanel Grid.Row="2" Grid.ColumnSpan="2">
... some buttons
</DockPanel>
</Grid>
</Window>
I used SizeToContent because I got a text box on the bottom wrapped in an Expander, which shall expanded on demand, and actually therefore my main window needs to resize. This actually works fine.
The problem is though the ListView's Height isn't the max height on start-up wherefore I got that "auto-resize" effect.
How can I set the ListView's hight to the max of the parents height to avoid this effect?
Another, more general issue. I think avoiding static layout parameters (static values for hight/width) is nice, but I got the feeling I'm loosing some control over my UI controls.
I recognized, resizeing my main window manually in height, it "jumps" by the 150 height of the DockPanel wrapped by the Expander on the bottom, anyway to avoid this?
What is the best pratice in dynamic UI Layout? I explored DockPanel beeing more dynamically in sizing to the surounding contorls than a StackPanel. But I guess thats not everything.

I think your issue is with your Grid definitions
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*"/>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
...
</Grid>
This means that the top row is going to be twice the size of rows 2 and 3, so the top row will only take up 50% of your space while your bottom two rows each take up 25% space.
If you want the top row to take up all available space, make sure it is the only * size row, and set the other rows to Auto so they will take up whatever space they need.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
...
</Grid>

Related

WPF how to get control and TextBlock to align in center in stretched grid

I have the following scenario:
<Grid x:Uid="Grid_3" Grid.Row="0" Margin="5" Focusable="False" Visibility="Visible" Background="DarkGray" Opacity="0.4" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition x:Uid="RowDefinition_8" Height="Auto"/>
<RowDefinition x:Uid="RowDefinition_9" Height="Auto"/>
</Grid.RowDefinitions>
<controls:LoadingPanel Grid.Row="0" IsLoading="True"
HorizontalLoadingIndicatorAlignment="Center"
VerticalLoadingIndicatorAlignment="Center"
/>
<TextBlock x:Uid="TextBlock_4" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Commit In Process..." />
</Grid>
</Grid>
I want the LoadingPanel and the TextBlock to be aligned in the center of the Grid which I have set to be stretched vertically and horizontally.
Note that the grid is already inside another grid. At the moment both places themselves at the top.
So, I'm guessing the issue you're seeing is that the controls are not centred vertically but instead sit at the top of the grid?
To resolve this (while keeping the controls below one another) simply add a relative sized row above and below the two rows for the controls like so:
<Grid x:Uid="Grid_3" Grid.Row="0" Margin="5" Focusable="False" Visibility="Visible" Background="DarkGray" Opacity="0.4" VerticalAlignment="Stretch" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="0.5*"/>
<RowDefinition x:Uid="RowDefinition_8" Height="Auto"/>
<RowDefinition x:Uid="RowDefinition_9" Height="Auto"/>
<RowDefinition Height="0.5*"/>
</Grid.RowDefinitions>
<controls:LoadingPanel Grid.Row="1" IsLoading="True"
HorizontalLoadingIndicatorAlignment="Center"
VerticalLoadingIndicatorAlignment="Center"
/>
<TextBlock x:Uid="TextBlock_4" Grid.Row="2" HorizontalAlignment="Center" VerticalAlignment="Center" Text="Commit In Process..." />
</Grid>
This should sort out the issue.
Just to be able to give an answer to this topic, I am reposting the comment from above, to allow #DSF to accept it.
Apparently the issue was related to the fact that the grid had two rows, and each control was on it`s own row. Discarding the rows solved his issue.
To fix the order of visibility, as you want, use Panel.ZIndex="2" on the LoadingPanel and Panel.ZIndex="1" on the TextBlock, like this

StackPanel height exceed parent Grid height

I have situation like this:
<UserControl>
<Grid x:Name="fullGrid" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="innerGrid" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel x:Name="leftSide" VerticalAlignment="Stretch">
<ScrollViewer VerticalAlignment="Stretch">
<ItemsControl/ VerticalAlignment="Stretch">
</ScrollViewer>
</StackPanel>
</Grid>
</Grid>
</UserControl>
Problem is that leftSide stackPanel height is higher than it's parent: innerGrid height.
I was debugging it in Snoop and it seems that StackPanel just ignore it's VerticalAlignment property.
I would like to avoid setting Heigh={Binding ElementName=xxx, Path=ActualHeight} because I have some additional Margins inside, and it break the view.
How can I handle that?
ScrollViewer fills its parent. StackPanel wants to size to its children, and so tells them they have as much space as they want (and then "shrinks to fit").
So, StackPanel tells ScrollViewer it can have all the space in the world, which it happily takes. There is no way to stop this besides doing a binding as you describe or setting an absolute height.
So the simple solution is: remove the StackPanel. Then the ScrollViewer will take up the space the Grid assigns it.

Trouble with Grid and StackPanel - Bindings

OK, I'm having issues properly using a ScrollViewer (containing an ItemsControl) inside a StackPanel.
The thing is the scrolling works, however :
No matter how much the user scrolls down, the bottom lines of the tables are not visible
When scrolling is... "released", the table pops back up...
<ScrollViewer Height="Auto">
<ItemsControl ItemsSource="{Binding Items, Mode=TwoWay}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid VerticalAlignment="Top" Margin="0,0,0,0" x:Name="ResTable">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.10*"/>
<ColumnDefinition Width="0.30*" />
<ColumnDefinition Width="0.30*"/>
<ColumnDefinition Width="0.30*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock Grid.Column="0" Text="{Binding [num]}" HorizontalAlignment="Center" />
<TextBlock Grid.Column="1" Text="{Binding [A]}" HorizontalAlignment="Center" />
<TextBlock Grid.Column="2" Text="{Binding [B]}" HorizontalAlignment="Center" />
<TextBlock Grid.Column="3" Text="{Binding [C]}" HorizontalAlignment="Center" />
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Any ideas?
P.S.
I've had a look at some of the other answers to similar issues, but none seems to be working...
My main idea is to display a table, with a fixed header, and scrollable contents (populated via Bindings) - ok, and there are also a couple of things on the top of the page (apart from the table I mean)
I guess the issue is that you should probably use parent element like Grid, not StackPanel, because StackPanel has its drawbacks when resizing child items with scrolls and so on.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Put headers in Grid.Row="0" -->
<!-- Put Scrollviewer in Grid.Row="1" -->
</Grid>
Also Height="Auto" attribute can be removed from ScrollViewer, you might want to use VerticalAlignment="Stretch" for item to take all the available space. I hope this is what you are trying to achieve.

C# Change backgroundcolor specific row

I've created a new project from the Grid App (XAML) template (C# Windows Store).
So far I've changed nothing in the template, but I would like to change the backgroundcolor from a specific row in the grid.
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
I would like to change the backgroundcolor from row 0 (which contains the page title).
Any ideas?? Thanks in advance!
This row consits of:
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Grid.Column="1" IsHitTestVisible="false" Style="{StaticResource PageHeaderTextStyle}"/>
</Grid>
You can't set the background color on the Grid.Row itself, instead set the Background property on whatever occupies this row.
For example
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid Background="Red" Grid.Row="0">
<TextBlock>Test</TextBlock>
</Grid>
</Grid>
EDIT:
Updated for Silverlight; TextBlock doesn't have Background so you have to put the control in another Border or grid container which does have background. Code updated to reflect this.
How about inserting border where you need it
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Border Background="Red" Grid.ColumnSpan="1"></Border>
<TextBlock>Test</TextBlock>
<Border Background="blue" Grid.Row="1" Grid.ColumnSpan="1"></Border>
<TextBlock Grid.Row="1">Test2</TextBlock>
</Grid>
note that you can especify the columns span in case of include more columns

How do I include a custom row at the end of a DataGrid in Silverlight?

I have a DataGrid in my Silverlight application and it works nicely, adding a row or removing a row as I manipulate the ItemsSource collection. However, I want there to be an additional row, or control that always appears after the last data row.
I can get the additional control to appear after the last row using a ControlTemplate and setting the RowsPresenter row to Auto height, but this means the rows never scroll when the render area gets too small. However, if I change the RowsPresenter row height to Star, the rows scroll but the additional control appears pinned to the bottom of the data grid rather than to the bottom of the last row.
Is there a way I can have the Star height behavior on the RowsPresenter while still having my control appear the way I want?
My current thinking is that I need to somehow use the LoadingRow event to find the position of the last row and use a Canvas or similar to place my control in the appropriate location.
Thoughts?
Thanks in advance for the help.
Update
I also asked a question (and ultimately answered) about pinning one control below another, which could be used to fix this issue if you don't want the custom row to scroll with the rest of the rows (such as in my case, where I wanted another datagrid header row to show totals and float over the other rows).
How do I pin one control below another in Silverlight?
I solved my problem last night in a flurry of inspiration. I notice that no one else has voted for this question so this answer may not be helpful to anyone, but just in case.
First of all, I combined my custom row control and RowsPresenter in a grid of two rows, each row sized to Auto. I then placed the grid inside a ScrollViewer and then sized the scroll viewer row to Star sizing. I did not add the VerticalScrollbar template part into my template as this only scrolls the RowsPresenter.
This gave me the exact behaviour I was looking for where a row is added and the custom row remains pinned to the bottom of the last data row. When the rows and custom row overflow off the end of the visible area, the scrollbar appears to allow scrolling while keeping the headers fixed in place.
Job done. I hope someone finds this helpful. Below is my ControlTemplate XAML.
<ControlTemplate TargetType="swcd:DataGrid" x:Key="DataGridTemplate">
<Border
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid Name="Root" Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<swcdp:DataGridColumnHeader Name="TopLeftCornerHeader" Grid.Column="0"/>
<swcdp:DataGridColumnHeadersPresenter Name="ColumnHeadersPresenter" Grid.Column="1"/>
<swcdp:DataGridColumnHeader Name="TopRightCornerHeader" Grid.Column="2"/>
<ScrollViewer
Grid.Row="1"
Grid.Column="1"
Grid.ColumnSpan="1"
Padding="0,0,0,0"
BorderThickness="0,0,0,0"
VerticalScrollBarVisibility="Auto">
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<swcdp:DataGridRowsPresenter Name="RowsPresenter" Grid.Row="0" />
<Border
Margin="1,1,1,1"
Padding="2,2,2,2"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Grid.Row="1">
<Grid Background="{TemplateBinding Background}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<TextBlock
Grid.Row="0"
TextAlignment="Left"
TextWrapping="NoWrap"
Text="Add a new item using the lists below:" />
<mystuff:MySelectionControl
HorizontalContentAlignment="Stretch"
Grid.Row="1"
SelectionChanged="OnSelectionChanged"/>
</Grid>
</Border>
</Grid>
</ScrollViewer>
<Rectangle Name="BottomLeftCorner" Grid.Row="3" Grid.ColumnSpan="2" />
<Grid Grid.Column="1" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Rectangle Name="FrozenColumnScrollBarSpacer" />
<ScrollBar Name="HorizontalScrollbar" Grid.Column="1" Orientation="Horizontal" Height="18" />
</Grid>
<Rectangle Name="BottomRightCorner" Grid.Column="2" Grid.Row="3" />
</Grid>
</Border>
</ControlTemplate>
Not sure if this helps for Silverlight, but I added a totals row to a WPF DataGrid by adding and invisible column, called IsTotal. I was able to get this row to always appear at the buttom of the grid using custom grouping / sorting. The grouping / sort order was configured to use this column as the primary sort, with a fix direction. Seems to work well.
First, create a Grid for the DataGrid and the pinned control:
<Grid Grid.Row="0" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sdk:DataGrid Grid.Row="0" ItemsSource="{Binding YOUR_COLLECTION}" />
<TextBlock Grid.Row="1" Text="Hello World" /> <!-- The pinned control. -->
</Grid>
The trick is VerticalAlignment="Top" - when the DataGrid is smaller than the available height, it will move to the top of the available space and the pinned control will appear under it.
Then, put this Grid into a container that stretches vertically, for example in a row of another Grid with Star height:
<Grid x:Name="LayoutRoot">
<Grid.RowDefinitions>
<!-- RowDefition for the Grid with the DataGrid with the pinned control. -->
<!-- If you want to have some other controls, -->
<!-- add other RowDefinitions and put these controls there. -->
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<!-- The internal Grid for the DataGrid & the pinned control. -->
<Grid Grid.Row="0" VerticalAlignment="Top">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<sdk:DataGrid Grid.Row="0" ItemsSource="{Binding YOUR_COLLECTION}" />
<TextBlock Grid.Row="1" Text="Hello World" /> <!-- The pinned control. -->
</Grid>
</Grid>
Instead of the root Grid you may have any other container that stretches vertically, the important thing is that it tries to fill all the available space for it.

Categories