Issue with WPF WrapPanel inside a ScrollViewer - wpf

There are a number of similar questions on SO but so far I have not been able to resolve my problem using them.
I have a bunch of Controls inside a WrapPanel and the WrapPanel is inside a ScrollViewer. The ScrollViewer is inside a Grid.
I am trying to get all the <Border> controls in the WrapPanel to have an Orientation of 'Vertical' (so that they flow down and when there is no more space left vertically they wrap horizontally) with a HorizontalScrollBar that appears when there is no more space left Horizontally.
My code so far is as follows:
<Grid x:Name="configGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" Width="{Binding ElementName=configGrid, Path=ActualWidth}" Height="{Binding ElementName=configGrid, Path=ActualHeight}">
<WrapPanel HorizontalAlignment="Left" Orientation="Vertical" x:Name="ConfigWrapPanel" Width="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ScrollViewer}}, Path=ActualWidth}">
<Border BorderBrush="White" BorderThickness="1,1,1,1" CornerRadius="2" Margin="10">
<Expander IsExpanded="True" BorderThickness="0" Header="General">
// some controls here
</Expander>
</Border>
<Border BorderBrush="White" BorderThickness="1,1,1,1" CornerRadius="2" Margin="10">
<Expander IsExpanded="True" BorderThickness="0" Header="Another Block">
// some controls here
</Expander>
</Border>
// many more <border> blocks here....
</WrapPanel>
</ScrollViewer>
</Grid>
This almost works as expected, the various content flows vertically and when there is not enough room at the bottom it moves up and right and starts at the top again. But I never get any horizontal scrollbars and the controls just disappear off the right of the screen.
I'm sure this is something really simple I'm missing but I can't quite figure it out.
As a bit of further info, the various Border controls and sub elements are all of dynamic width and height (which is why I opted for a vertical orientation WrapPanel rather than Horizontal)
Any help would be greatly appreciated.

You have to remove the Width from your WrapPanel.
That width wants to stretch to infinity, which prevents the ScrollViewer from corrent measuring the boundaries of the WrapPanel resulting in never showing the ScrollBar.
Code below shows a working example:
<Grid x:Name="configGrid">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<ScrollViewer Grid.Row="0" VerticalScrollBarVisibility="Disabled" HorizontalScrollBarVisibility="Auto" Height="{Binding ElementName=configGrid, Path=ActualHeight}">
<WrapPanel HorizontalAlignment="Left" Orientation="Vertical" x:Name="ConfigWrapPanel" >
<Border BorderBrush="White" BorderThickness="1,1,1,1" CornerRadius="2" Margin="10">
<Expander IsExpanded="True" BorderThickness="0" Header="General">
// some controls here
</Expander>
</Border>
<Border BorderBrush="White" BorderThickness="1,1,1,1" CornerRadius="2" Margin="10">
<Expander IsExpanded="True" BorderThickness="0" Header="Another Block">
// some controls here
</Expander>
</Border>
</WrapPanel>
</ScrollViewer>
</Grid>

Related

Make textbox width automatically changable in Stackpanel which is in Border, WPF

I wanted to put Button in TextBox, but as I found it's not possible so I decided to do something like this:
Xaml for this looks as shown below:
<Border Grid.Row="4" Grid.Column="2" Margin="10,0,10,0"
BorderBrush="Gray" BorderThickness="1">
<StackPanel Orientation="Horizontal" Margin="0,0,0,0">
<TextBox BorderBrush="LightGray" BorderThickness="1" Text="{Binding WaybillNumber}"
Width="245"/>
<Button Content="*" Width="15" BorderThickness="1"/>
</StackPanel>
</Border>
The problem I have is that, when I resize my window (decreasing width) my Button dissapears:
I want it to behave as DateTimePicker does. I have tried many ways to make TextBox width auto adjustable ( * in width isn't correct input, auto decreases width of TextBox, I also tried to define style resource in StackPanel resources for TextBox width, but it doesn't work too), but Haven't found correct way yet.
Replace the StackPanel with a Grid:
<Border Margin="10,0,10,0" BorderBrush="Gray" BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBox BorderBrush="LightGray" BorderThickness="1" Text="{Binding WaybillNumber}" />
<Button Grid.Column="1" Content="*" Width="15" BorderThickness="1"/>
</Grid>
</Border>
use Grid instead of StackPanel. Setting fixed size (Width/Height) is not a good idea for adaptive layout. Grid will allow TextBox to stretch.
one option is to have separate columns for TextBox and Button:
<Border Grid.Row="4" Grid.Column="2" Margin="10,0,10,0"
BorderBrush="Gray" BorderThickness="1">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBox BorderBrush="LightGray" BorderThickness="1" Text="{Binding WaybillNumber}" />
<Button Content="*" Grid.Column="1" Width="15" BorderThickness="1"/>
</Grid>
</Border>
alternatively place them in the same cell and let Button overlap TextBox (it will look like as a part of TextBox but can hide part of a long text):
<Border Grid.Row="4" Grid.Column="2" Margin="10,0,10,0"
BorderBrush="Gray" BorderThickness="1">
<Grid>
<TextBox BorderBrush="LightGray" BorderThickness="1" Text="{Binding WaybillNumber}" />
<Button Content="*" HorizontalAlignment="Right" Margin="2" Width="15" BorderThickness="1"/>
</Grid>
</Border>

Why are letters scaling to different sizes in comparable view boxes within WPF?

I am attempting to create an onscreen keyboard using a Grid for key layout. Each key consists of a Border with a TextBlock containing a letter. To make the letters scale I have wrapped each TextBlock in a ViewBox, for example;
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid Grid.Row="0" Grid.Column="0">
<Border BorderThickness="1" BorderBrush="Gray">
<Viewbox>
<TextBlock Text="a" />
</Viewbox>
</Border>
</Grid>
<Grid Grid.Row="0" Grid.Column="1">
<Border BorderThickness="1" BorderBrush="Gray">
<Viewbox>
<TextBlock Text="b" />
</Viewbox>
</Border>
</Grid>
<Grid Grid.Row="0" Grid.Column="2">
<Border BorderThickness="1" BorderBrush="Gray">
<Viewbox>
<TextBlock Text="c" />
</Viewbox>
</Border>
</Grid>
</Grid>
The problem is when you shrink the control by resizing the window horizontally (i.e. squash the borders together horizontally). As each letter has a slightly different width and height, the amount of zooming/scaling applied by each viewbox is not exactly the same. This results in the letters being rendered at different vertical heights, i.e. the "b" will be on a horizontal plane above the "a" and "c", which looks a little wrong.
The only work around I can think of (which works) relies on fixing the widths of each textblock, e.g. setting Width="10". This, however, feels unsatisfactory as it requires knowledge of the font which will be used to display each letter and an assumption about the maximum width. A middle ground would be to achieve this automatically the largest possible letter/glyph in each viewbox by including a hidden letter in each textblock;
<Grid Grid.Row="0" Grid.Column="0">
<Border BorderThickness="1" BorderBrush="Gray">
<Viewbox>
<Grid>
<TextBlock Text="a" />
<TextBlock Text="X" Visibility="Hidden" />
</Grid>
</Viewbox>
</Border>
</Grid>
I don't like that solution though and would love a reliable way to ensure all textblocks are the same size and so scale acceptably, without hard coding values or assumptions about the font.
Any ideas?
Thanks.
Using a uniform grid and binding to a primary textblock you get a pretty decent scaling:
<UniformGrid Rows="3" Columns="10">
<Viewbox>
<Border>
<TextBlock TextAlignment="Center"
Width="{Binding ActualWidth, ElementName=textBlock, Mode=OneWay}"
Height="{Binding ActualHeight, ElementName=textBlock, Mode=OneWay}">
Q
</TextBlock>
</Border>
</Viewbox>
<Viewbox>
<Border>
<TextBlock x:Name="textBlock" TextAlignment="Center">
W
</TextBlock>
</Border>
</Viewbox>
<Viewbox>
<Border>
<TextBlock Width="{Binding ActualWidth, ElementName=textBlock, Mode=OneWay}"
Height="{Binding ActualHeight, ElementName=textBlock, Mode=OneWay}"
TextAlignment="Center">
E
</TextBlock>
</Border>
</Viewbox>
<Viewbox>
<Border>
<TextBlock Width="{Binding ActualWidth, ElementName=textBlock, Mode=OneWay}"
Height="{Binding ActualHeight, ElementName=textBlock, Mode=OneWay}"
TextAlignment="Center">
R
</TextBlock>
</Border>
</Viewbox>
</UniformGrid>
You'll see each control is bound to the W key - assuming that is the biggest. If you are unsure, you can add a different element as hidden and bind to that - as you implied in your question. The important thing is that the grid sets the size of the main control element.
This MSDN question is answered correctly; http://social.msdn.microsoft.com/Forums/vstudio/en-US/c052fa89-4788-4d85-b266-fdd5c637a0ff/sharing-viewbox-zoom-level-between-items?forum=wpf
The solution relies on leveraging the SharedSizeGroup behaviour on a grid to ensure that the viewbox of every key is the same size as every other viewbox, like so;
<Viewbox>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition SharedSizeGroup="col"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition SharedSizeGroup="row"/>
</Grid.RowDefinitions>
<TextBlock Text="a" />
</Grid>
</Viewbox>
Other solutions involving hard coding the width/height of the viewbox, or binding to a common viewbox and filling it with the largest possible glyph work, but are not perfect solutions. The above solution makes no assumptions and relies on built in WPF measure/arrange logic to produce the desired outcome.

WPF Scrollviewer not working with dynamic height

Trying to add a Scrollviewer to a TextBlock so that users can scroll down the contents, which are often rather longer than the available screen real-estate.
Apologies for what's probably a dumb question: I can see there's lots of topics about this, and that the problem is usually a fixed height somewhere, but I'm struggling to see which element is causing the problem in my XAML:
<Popup StaysOpen="True" Placement="Center" IsOpen="{Binding SummaryOpen}" PlacementTarget="{Binding ElementName=Areas}">
<Border Background="LightGray" BorderBrush="Black" Padding="5" BorderThickness="1">
<Grid Width="500">
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="350" />
<RowDefinition Height="40" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<Label Content="{Binding Name}" />
<Label Content=": " />
<Label Content="{Binding Description}" />
</StackPanel>
<Border Grid.Row="1" BorderBrush="Black" BorderThickness="1">
<StackPanel Background="White" Margin="-1,1,1,-1">
<!-- this is the rogue element -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock Text="{Binding Summary}" TextWrapping="Wrap" />
</ScrollViewer>
</StackPanel>
</Border>
</Grid>
</Border>
</Popup>
The ScrollViewer appears, but never contains an actual scroll bar, regardless of how much content there is in the TextBlock.
If someone could explain where the problem is and how you fix it, I'd be most appreciative.
The rogue element is actually the parent StackPanel -- that panel isn't "fixed height" per se, but it doesn't work as a parent of a ScrollViewer. The reason is that it reports its available height as infinite, so the child ScrollViewer thinks it can extend as far as its children require, and so it doesn't need to scroll.
It looks like you could just as easily use a Border, or a Grid, either of which will limit their height to the parent height and thus fix the issue:
<Border Grid.Row="1" BorderBrush="Black" BorderThickness="1">
<Border Background="White" Margin="-1,1,1,-1">
<!-- this is the rogue element -->
<ScrollViewer VerticalScrollBarVisibility="Auto">
<TextBlock Text="{Binding Summary}" TextWrapping="Wrap" />
</ScrollViewer>
</Border>
</Border>

Border Styling applied to all the child elements recursively

I am new to WPF and need your help in resolving my styling issue.
I have applied border styling to GRID as below
<Border CornerRadius="5" BorderBrush="Gainsboro" BorderThickness="1,1,0,0" Name="border1" Margin="90,54,20,50" >
<Border BorderBrush="Gray" CornerRadius="5" BorderThickness="0,0,1,1" >
<Border.Effect>
<DropShadowEffect BlurRadius="10" Direction="-50" ShadowDepth="7" />
</Border.Effect>
<Border.Child>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="356*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="446*" />
</Grid.ColumnDefinitions>
<TextBox Name="TB1" Style="{StaticResource CustomTextBoxStyle}" Grid.Column="1" Margin="46,79,400,277" Grid.Row="1" />
<ComboBox Height="24" Name="comboBox1" Width="110" Grid.Column="1" Margin="304,86,232,276" Grid.Row="1" />
</Grid>
</Border.Child>
</Border>
</Border>
Then I have placed text box and combo box in the grid with custom styling.
The problem is parent GRID's border style is applied to child TEXTBOX along with its own custom style properties.
Could you please help me out in this?
Thanks
Bharat
As per MSDN doucment -
When a BitmapEffect is applied to a layout container, such as
DockPanel or Canvas, the effect is applied to the visual tree of the
element or visual, including all of its child elements.
But, there is a workaround as described here and here to have another border with same position but without the effect, that would resolve the problem -
<Grid>
<Border Margin="10" BorderBrush="Red" BorderThickness="1">
<Border.Effect>
<DropShadowEffect Color="Gray"/>
</Border.Effect>
</Border>
<Border Margin="10">
<!-- controls -->
</Border>
</Grid>

Validation Error Template Adorner is cut insight a ScrollViewer

i have the following problem. as far a i put my Textboxes in a scrollviewer my ValidationError Adorner get cut by the ScrollViewer. i found some answers to Adorner and ScrollViewer which say i need to retemplate my scrollviewer and add an adornerdecorator. but this makes no sense to me and it doesnt help either.
as far as i know should the Validation Adorner rendered in the nearest AdornerDecorator. the ScrollViewer by default has no AdornerDecorator. so does anybody know why my ValidationAdorner get cut?
I also looking for a solution :)
EDIT: it seems the ScrollContentPresenter which comes from the ScrollViewer Template cause the problem, because it has a AdornerLayer by default. Any ideas how to solve my issue?
EDIT2:
is there a way to create a new ScrollConntentPresenter Template without a Adornerlayer?
or is there a way to remove a the Adornerlayer from the VisualTree?
or can i force the Adornerlayer to render in a" higher/most top" AdornerLayer?
or can i have Scrolling Content without a ScrollViewer?
here is my xaml:
<UserControl>
<AdornerDecorator>
<Grid x:Name="RootControl">
<Grid.RowDefinitions>
<RowDefinition Height="auto" MinHeight="50"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="main" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Border x:Name="InputBorder" Grid.Column="0">
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
<Grid x:Name="InputContainer" HorizontalAlignment="Stretch">
<Grid.RowDefinitions>
...some rows...
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
...some columns...
</Grid.ColumnDefinitions>
<TextBox .../><!--this Validation Adorner get cut by scrollviewer-->
</Grid>
</ScrollViewer>
</Border>
</Grid>
</Grid>
</AdornerDecorator>
</UserControl>
here is my Validation Template:
<ControlTemplate x:Key="ValidationTemplate" >
<DockPanel>
<AdornedElementPlaceholder Name="MyAdornedElement" />
<Grid>
<Border Background="{StaticResource BrushError}" Margin="3,0,0,0" x:Name="ErrorControl" BorderBrush="White" BorderThickness="1">
<TextBlock Margin="10,3,5,2"
Text="{Binding ElementName=MyAdornedElement,Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
Visibility="{Binding ElementName=MyAdornedElement,Path=AdornedElement.Visibility}"
Foreground="White" FontWeight="Bold">
</TextBlock>
</Border>
<Path x:Name="path" Margin="3,0,0,0" Data="M 0,10 L 10,0 " Fill="{StaticResource BrushError}"
StrokeThickness="2" Stroke="White"
/>
</Grid>
</DockPanel>
</ControlTemplate>
the behavior is intended. A ScrollViewer is able to hide some of it's contents (ie the content lying in the extent). Showing an Adorner for such content that doesn't get clipped, results in a strange UI.
What you should be able to do in the ValidationTemplate though is putting the Grid element inside a Popup control. Please try that and report if it worked.

Resources