Infinite control with ScrollBar nested inside a ScrollViewer - wpf

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}}"/>

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.

Grief with WPF UserControl with ListBox and TextBox Can't get textwrapping to work

I am trying to produce a simple User Control that allows a user to enter notes and review the notes he or she has taken. For now, I have a ListBox to show previous notes and a TextBox to enter a new note. The ListBox also uses TextBoxs to show the individual notes. The logic is all working. Now I'd just like the control to look a little nicer. I am not seeing text wrapping when one of the notes is long. Nor am I seeing text wrapping on the TextBox where I enter a new note. I have tried to research this and have found the warnings about using StackPanels. Hence no stack panels here! Here is my XAML
<UserControl.Resources>
<DataTemplate x:Key="DefaultTemplate">
<Grid x:Name="GridItem" >
<Grid.ColumnDefinitions>
<ColumnDefinition />
</Grid.ColumnDefinitions>
<TextBox x:Name="NoteText" Grid.Column="0" Margin="0,5,5,0" Text="{Binding Path=NoteText}" TextWrapping="Wrap" />
</Grid>
</DataTemplate>
</UserControl.Resources>
<Grid Name="LayoutRoot">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBox Grid.Row="0" Text="{Binding Path=NewNoteText, UpdateSourceTrigger=PropertyChanged}" LostFocus="TextBox_LostFocus">
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=AddNote}" Key="Enter"/>
</TextBox.InputBindings>
</TextBox>
<ListBox Grid.Row="1" ItemsSource="{Binding Path=Notes}" Margin="5" ItemTemplate="{DynamicResource DefaultTemplate}" SelectionChanged="NotesControl_SelectionChanged">
</ListBox>
</Grid>
I have tried specifying Width="*" in the UserResources. That made no difference. I don't really want to specify a maximum width for either TextBox if I can help it.
And here is how I use it in the main test form:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" ></RowDefinition>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition ></RowDefinition>
</Grid.RowDefinitions>
<ctrlLib:RecordingListControl Grid.Row="0" Name="_ctrlRecordings"
PatientIdAndSessionNumber="{Binding PatientIdAndSessionNumberMain, Mode=TwoWay}"
SelectedItem="{Binding SelectedItemMain, Mode=OneWayToSource}" />
<Label Grid.Row="1" Content="{Binding SelectedItemMain.RecordingName}" />
<ctrlLib:NotesControl Grid.Row="2" Name="_ctrlNotes" PatientIdAndSessionNumber="{Binding PatientIdAndSessionNumberMain, Mode=TwoWay}" />
</Grid>
Any ideas? I get the general idea that nothing is telling the TextBox that it's width is constrained so no TextWrapping is invoked, but I'm at a loss as to how to tel the TextBox that there is a width constraint. I've seen all sorts of posts about not using "Auto" and using "*" but nothing seems to work!
Looking ahead, I'm not really happy with how this looks period. I'm currently getting a line drawn around every note item which I don't like. Also, I'd like the textbox where you enter the new note to look like part of the listbox and appear at the bottom.
Thanks,
Dave

UserControl Expand Vertically when Window is expanded

I'm having trouble getting my UserControl to expand vertically when my window is expanded.
My UserControl currently sits inside a ItemsControl which is stretching correctly by setting the VerticalAlignment="Stretch" property on the ItemsControl.
I add the following UserControl to the ItemsControl:
<UserControl MinWidth="930">
<Grid VerticalAlignment="Stretch" Background="Red">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200" />
<ColumnDefinition Width="730*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="400" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DockPanel Grid.Column="1" Grid.ColumnSpan="1" Grid.RowSpan="2" Grid.Row="0" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Background="Pink" LastChildFill="True">
<ItemsControl Name="itemPanelOverview" Grid.Column="1" Background="Black" Margin="0"/>
</DockPanel>
</Grid>
</UserControl>
The UserControl is called in an ItemsControl inside of a TabControl like so:
<TabItem>
<TabItem.Header>
HEADER EG
</TabItem.Header>
<ItemsControl Name="contentItems" Grid.ColumnSpan="2" Grid.Column="1" Grid.Row="1" VerticalAlignment="Stretch" Background="Blue">
<Grid Height="35" Background="{DynamicResource GrayMenuGradient}" >
<Image Source="..." Stretch="None" />
<TextBlock Text="{Binding WelcomeMessage}" />
</Grid>
</ItemsControl>
</TabItem>
It appears that the ItemsControl (contentItems) is stretching as expected, as I can see the blue background stretching correctly.
I haven't set the height for this UserControl anywhere other than the Row Definitions. Is there something I'm missing?
There are at least two aspects at play here:
The first is that when you have items in an ItemsControl, each item is actually inside an ItemContainer, so it is the container that you want to stretch.
You can design the container by declaring an ItemContainerTemplate for your ItemsControl: http://msdn.microsoft.com/en-us/library/system.windows.controls.itemcontainertemplate.aspx
The second consideration is the ItemsPanelTemplate, which determines into what type of panel the items are placed. The ability of the items in the ItemsControl to fill up the available space is going to depend on the type of container as much as on the type of ItemContainer. For example, if you use a StackPanel for the ItemsPanelTemplate, it won't fill up available space because StackPanel grows and shrinks according to its contents. A DockPanel could potentially work, but only the last child would grow to fill available space. Perhaps a UniformGrid could do the trick.

ScrollViewer only scrollable when contents minHeight falls below

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>

Resources