wpf layout help - wpf

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.

Related

WPF XAML treeview in a stack panel - no scrolling?

so I have a TreeView control in my XAML. It works fine. If I extend the treeview to be larger than the user control it resides in, I get a scroll bar, which is good. However, inside this user control I want some other things. So I put the treeview in a stack panel with some other things, and this time I don't get the scroll bar if the treeview expands to be larger than the user control it's in.
Is this something other people have come across, and is there a fix for it?
Embed your stackpanel inside a ScrollViewer: stackoverflow.com/a/6250287/7517676. You also might have to explicitly set VerticalScrollBarVisibility and HorizontalScrollBarVisibility, depending on your need.
Here's a code sample:
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel ... />
</ScrollViewer>
Based on this answer a StackPanel isn't the right container for a TreeView, but a Grid is. So this will enable scrolling inside the TreeView, by mouse and scrollbar:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Label>Some descriptive label.</Label>
<TreeView Grid.Row="1" ItemsSource="{Binding SomeSource, Mode=OneWay}">
<TreeView.ItemTemplate>
<HierarchicalDataTemplate DataType="{x:Type local:MyNodeType}" ItemsSource="{Binding Children}">
<Label Content="{Binding NodeName}"/>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
</Grid>

How do you make a vertically scrolling scrollviewer inside a tabControl?

I want to make it so that my TabControl is vertically scrollable, but I can't seem to do it. The following sample acts as though there was no scrollviewer at all. I even tried putting the TabControl inside the scrollviewer, or putting it all in a grid and constraining the height of the grid, but nothing works.
<DataTemplate x:Key="tabControlTemplate">
<TabControl ItemsSource="{Binding guiItems}" DisplayMemberPath="Title" Height="Auto" Template="{StaticResource mainTabControlTemplateEx}">
<TabControl.ContentTemplate>
<DataTemplate>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto" CanContentScroll="True">
<StackPanel Margin="2" HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<ItemsControl ItemsSource="{Binding guiItems }" ItemTemplateSelector="{DynamicResource templateSelector}"/>
</StackPanel>
</ScrollViewer>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DataTemplate>
The actual problem is not clear from the question.
You are not getting to see scrollviewer and the content inside is clipped? PLease confirm if that is the case.
If the problem is you are getting to see entire content taking up all the available space, and you would like to control that using scroll viewer, then you would need to set 'MaxHeight' property on Scroll Viewer. This would limit the height of your DataTemplate and would make verticall scroll bar visible if the inner content goes beyond the MaxHeight.
Hope that helps.

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.

Restricting size of child control in a panel to visible area

I can never seem to wrap my head around the differences between the DockPanel, StackPanel and Grid in terms of restricting their children content. This XAML:
<DockPanel>
<Menu/>
<ToolBarTray/>
<ScrollViewer>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TabControl>
<TabItem Header="Data" Name="tabData">
<DockPanel>
<Border Name="extraDataBorder" DockPanel.Dock="Top"
Style="{StaticResource ConsistentBorder}" Margin="10">
</Border>
<igDP:XamDataGrid Grid.Row="1"
Margin="10,10,10,0"
ScrollViewer.HorizontalScrollBarVisibility="Visible"
ScrollViewer.VerticalScrollBarVisibility="Visible" >
</igDP:XamDataGrid>
</DockPanel>
</TabItem>
</TabControl>
</Grid>
</ScrollViewer>
</DockPanel>
Produces a grid that is not constrained within the visible area of the screen as shown in the figure below:
I do not want the grid to extent beyond the visible area of the screen. Instead, I want it clipped so that users can see the horizontal scroll bar and not have to scroll down to see the horizontal scroll bar. I have tried using a Stack and DockPanel in place of the grid but get the exact same effect. How do I fix this?
TIA.
EDIT:
I am editing the original XAML to include the additional elements that caused this issue.
You can bind it to its parent's width and height as one of the many solutions.

WPF Adjust Grid Height according to listview content

I have a listview with several rows, the problem is that even if the vertical scrollbar is visible, it doesn't work, I explain, it seems that because the grid height is Auto, then there is no maximum height and all the content is displayed, even if part of it is not visible, the only part-solution I've found is to set pixel height to the grid, like 600, and then you can scroll the content of the listview, but obviously this is not a very good solution since I would like the height be defined by it's container, I have tried to bind the height property to ActualHeight of the TabItem, but that doesn't work either, I'm desperate so if anyone knows how to solve this problem I would highly appreciate it, thanks in advance.
Below is basically the code I'm using.
<TabItem Header="Orders">
<Grid Height="Auto">
<ScrollViewer>
<ListView ScrollViewer.VerticalScrollBarVisibility="Visible"
ScrollViewer.CanContentScroll="True" >
</ListView>
</ScrollViewer>
</Grid>
</TabItem>
I think the problem is your Height="Auto" is growing the Grid to display all the rows.
Set your Grid Row Height and Width to * with means fill container (the tab)
<Grid.RowDefinitions>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Listview Grid.Row="0" Grid.Column="0" />
I believe the problem could be you have a scroll viewer surrounding a control (the ListView) that has a built in scroll viewer. Try removing the outer ScrollViewer.
in fact the listview has a built in scroll viewer so I removed it, however the problem persist in the same manner, now I have the following:
<TabItem Header="Orders">
<Grid Height="Auto" Margin="0,0,0,0">
<ListView ItemsSource="{Binding}" HorizontalAlignment="Stretch"
x:Name="listViewOrdenes"
ScrollViewer.VerticalScrollBarVisibility="Visible">
</ListView>
</Grid>
</TabItem>

Resources