ScrollViewer in ItemsControl - ScrollBar not shown but working - c#

This is not your average "My ScrollViewer isn't working" question...
Assume a window with a grid. The sizes of column 0 and row 1 are set to Auto, column 1 and row 0 are set to *. (important)
In cell [0, 0] there is an ItemsControl with a Template with a StackPanel inside a ScrollViewer inside a Grid. The reason is simple: Show a scroll bar if not all items in the ItemsControl can be displayed. The visibility of the vertical scrollbar is set to Auto (important).
In Cell [1, 1] there is a Button that displays its width.
If the window is too small too display all items in the ItemsControl this will lead to the following:
The scroll bar will be there but it is not visible. It is working, because I can scroll using the mouse wheel. The reason seems to be that the grid column in which the ItemsControl is contained is not automatically extended to make space for the scrollbar.
If I change (nearly) any of the parameters, the scroll bar is displayed as expected and the second column is reduced in size. Can anyone explain this odd behavior?
Additional Info:
The following parameter changes will lead to the scrollbar becoming visible:
Changing size of column 0 to *
Changing size of column 1 to Auto
Changing size of row 1 to *
Removing the Button.
Moving the Button to [0, 1] or [1, 0]
Manually setting the width of the ItemsControl.
Setting the VerticalScrollBarVisibility of the ScrollViewer in the ItemsControl to Visible.
However, changing the button in [1, 1] to something else, e.g. another ItemsControl doesn't change the strange behavior, so it has nothing to with the button. Furthermore, changing the width of the Button to something that is smaller than the second column, also doesn't remove that behavior.
Complete sample code for reproduction:
<Window x:Class="WpfApplication4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="343" Width="253">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ItemsControl>
<ItemsControl.Template>
<ControlTemplate>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer VerticalScrollBarVisibility="Auto">
<StackPanel IsItemsHost="True" />
</ScrollViewer>
</Grid>
</ControlTemplate>
</ItemsControl.Template>
<ItemsControl.Items>
<Button Content="Column1" Height="500" />
</ItemsControl.Items>
</ItemsControl>
<Button Content="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
Grid.Column="1" Grid.Row="1" />
</Grid>
</Window>

Looks like it might be a known bug in WPF. This question deals with a ListBox's ScrollViewer, but I think the principal is the same.
As an alternative, you could add something behind the ScrollViewer that has it's width bound to the ScrollViewer's ActualWidth, which will force the column to draw the correct size
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<ScrollViewer x:Name="Test" Grid.Row="0" Grid.Column="0"
VerticalScrollBarVisibility="Auto">
<Button Content="Column1" Height="500" />
</ScrollViewer>
<Grid Grid.Column="0" Grid.Row="0"
Width="{Binding ElementName=Test, Path=ActualWidth}" />
<Button Content="{Binding ActualWidth, RelativeSource={RelativeSource Self}}"
Grid.Column="1" Grid.Row="1" />
</Grid>

Related

Xaml Responsive DataGrid with Scroll Bar

I have a grid with a Datagrid attached. I am having issues created a datagrid that resizes as the window resizes. I have set MinHeight and MinWidth, but it just seems to create a static Height and Width. I also have scroll bar set to be visible, but no scroll bar appears?
The closest I've come to responsive design is setting my Height="2300" and Width="2700", but the scroll bar still doesn't appear. And the table I plan to populate my Datagrid with will likely be much bigger than these dimensions, and need scrolling.
I have tried using techniques from other SE questions, but nothing seems to work how I'd expect.
<!--Grid View Assett Info - Populate Table from DB -->
<Grid x:Name="grid_AssetView" VerticalAlignment="Top" Margin="10,236,10,10" Style="{StaticResource Grid_Shadow}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid VerticalAlignment="Top"
Margin="0,0,0,0"
Grid.Column="0" Grid.Row="0"
ScrollViewer.CanContentScroll ="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
MinWidth="772" MinHeight="230"/>
</Grid>
My Style Sheets
<Style x:Key="Grid_Shadow" TargetType="Grid">
<Setter Property="Background" Value="#FFF9FBFD"/>
<Setter Property="BitmapEffect">
<Setter.Value>
<DropShadowBitmapEffect
Color="Black"
Direction="320"
ShadowDepth="10"
Softness="50"
Opacity="0.1">
</DropShadowBitmapEffect>
</Setter.Value>
</Setter>
</Style>
u have to set the ColumnDefinition Width="*" instead of "Auto" as follows
<!--Grid View Assett Info - Populate Table from DB -->
<Grid x:Name="grid_AssetView" VerticalAlignment="Top" Margin="10,236,10,10" Style="{StaticResource Grid_Shadow}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<DataGrid VerticalAlignment="Top"
Margin="0,0,0,0"
Grid.Column="0" Grid.Row="0"
ScrollViewer.CanContentScroll ="True"
ScrollViewer.VerticalScrollBarVisibility="Auto"
ScrollViewer.HorizontalScrollBarVisibility="Auto"
MinHeight="230"/>
</Grid>
then the datagrid will resize automatically. I also removed MinWidth to show that resizing works without problems.
Also the scrollbars will be visible if there are contents.
I hope it helps.

WPF Expander - Horizontal Alignment set to Right, but jumps to left on when expanded

I've got a WPF Expander that is right aligned. That bit shows correctly on the screen, but when you expand it the expander and the text "show/hide historic data" jumps to the left of the screen. Close the expander and it returns to the correct (right) alignment.
I've tried various approaches but am unable to find why this is happening or more importantly how to keep an expanded expander right aligned
<Grid Grid.Row="1"
HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" >
<TextBlock Style="{StaticResource SubHeader}" Text="Historic Data Sets" />
<Rectangle Style="{StaticResource HeaderLine}"/>
</StackPanel>
<Expander Header="show/hide historic data"
Grid.Column="1"
IsEnabled="True"
VerticalAlignment="Center"
HorizontalAlignment="Right"
ExpandDirection="Down">
<!-- some other stuff in the second row such as grid... -->
</Expander>
YOu can take control of the Header by explictly marking it as a TextBlock and then change itÅ› HorizontalAlignment property:
<Expander.Header>
<TextBlock
Width="200"
HorizontalAlignment="Right"
Text="Show information.."></TextBlock>
</Expander.Header>

In WPF, TreeView is having a different outline shadowing that StackPanel with border. How to fix that?

I have a window with a TreeView with a BorderThickness of 1 and a StackPanel inside a Border element with a BorderThickness of 1.
As you can see in the following picture, the Border gets a shadowing effect but the Treeview doesn't. How to fix this (if possible remove the shadow effect)?
general view
zoom view
I tried setting BorderThickness of TreeView to 0 and put TreeView in a Border , but it didn't work.
XAML summary:
<Grid Name="MainGrid" Margin="5,5,5,5">
<Grid.RowDefinitions>
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions >
<ColumnDefinition Width="1*" />
<ColumnDefinition Width="5" />
<ColumnDefinition Width="2*" />
</Grid.ColumnDefinitions>
<TreeView ItemsSource="{Binding FinalResult}"
SelectedItemChanged="TreeView_SelectedItemChanged"
BorderThickness="1"
>
<TreeView.Resources>
</TreeView.Resources>
</TreeView>
<GridSplitter Grid.Column="1" Width="5" HorizontalAlignment="Stretch" />
<Border BorderBrush="#ff828790" BorderThickness="1" Grid.Column="2">
<StackPanel>
</StackPanel>
</Border>
</Grid>
I think that you might benefit from using the UIElement.SnapsToDevicePixels Property. From the linked page:
Gets or sets a value that determines whether rendering for this element should use device-specific pixel settings during rendering.
If you set this property to true on your blurry control, it should align it directly with the actual pixels to remove the blurriness:
<Border BorderBrush="#ff828790" BorderThickness="1" Grid.Column="2"
SnapsToDevicePixels="True">
...
</Border>

understanding WPF Layout

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>

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