WPF wrap panel and scrolling - c#

I have a simple WrapPanel which contains a number of wide controls. When I resize the Width of the Window everything works as expected. The controls will go across on a single line if there is enough space or wrap down to the next line when there isn't.
However, what I need to happen is that if all of the controls are basically stacked vertically (since there is no more horizontal space) and the Width of the Window is decreased even more, a horizontal scroll bar appears so that I can scroll and see the entire control if I want to. Below is my xaml. I tried wrapping the WrapPanel in a ScrollViewer but I couldn't achieve my goal.
<Window x:Class="WpfQuotes.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="Auto" Width="600" Foreground="White">
<WrapPanel>
<Button Width="250">1</Button>
<Button Width="250">2</Button>
<Button Width="250">3</Button>
</WrapPanel>
</Window>
So if you reduce the Width of the above Window to its minimum, you will not be able to see the text of the buttons. I would like a horizontal scroll bar appear so that I can scroll to see the text but not interfere with the usual wrapping functionality.
Thanks.
Update:
I have followed Paul's suggestion below and the horizontal scrollbar appears as expected now. However, I also wanted vertical scrolling available so I set VerticalScrollBarVisibility="Auto". The thing is, if I resize the window so that a vertical scroll bar appears, the horizontal one also always appears, even if it is not needed (there is enough horizontal space to see the entire control). It seems like the vertical scrollbar appearing is messing with the width of the scrollviewer. Is there a way to correct this so that the horizontal scrollbar doesn't appear unless it is actually needed?
Below is my xaml and the only code I added in the CustomWrapPanel:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwp="clr-namespace:CustomWrapPanelExample"
Title="Window1" Height="Auto" Width="300" Foreground="White" Name="mainPanel">
<ScrollViewer x:Name="MyScrollViewer" HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<cwp:CustomWrapPanel Width="{Binding ElementName=MyScrollViewer, Path=ActualWidth}">
<Button Width="250">1</Button>
<Button Width="250">2</Button>
<Button Width="250">3</Button>
<Button Width="250">4</Button>
<Button Width="250">5</Button>
<Button Width="250">6</Button>
<Button Width="250">7</Button>
<Button Width="250">8</Button>
<Button Width="250">9</Button>
</cwp:CustomWrapPanel>
</ScrollViewer>
</Window>
The only thing overridden in CustomWrapPanel:
protected override Size MeasureOverride(Size availableSize)
{
double maxChildWidth = 0;
if (Children.Count > 0)
{
foreach (UIElement el in Children)
{
if (el.DesiredSize.Width > maxChildWidth)
{
maxChildWidth = el.DesiredSize.Width;
}
}
}
MinWidth = maxChildWidth;
return base.MeasureOverride(availableSize);
}

Here's the thing, if your going to use a wrap panel, it does two things, it will take up as much available space in one direction, and expand as needed in the other. For instance, if you place it inside of a window like you have it, it takes up as much horizontal space as it can, and then expands as needed downward, that's why a vertical scroll bar will work, the parent container says "this is how wide I am, but you can make yourself as big as you want vertically", if you change it to a horizontal scroll bar, the scroll viewer is essentially saying "this is how tall you can be, but you can be as wide as you want" in this case the wrap panel doesn't wrap because there is no horizontal constraints.
One potential solution is to change the direction the wrap panel wraps from horizontal to vertical like this (Which is probably not the ideal or expected behavior):
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<WrapPanel Orientation="Vertical">
<Button Width="250">1</Button>
<Button Width="250">2</Button>
<Button Width="250">3</Button>
</WrapPanel>
</ScrollViewer>
In order to get the behavior your expecting, you'll have to do something closer to this:
<ScrollViewer x:Name="MyScrollViewer" HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled">
<WrapPanel MinWidth="250" Width="{Binding ElementName=MyScrollViewer, Path=ViewportWidth}">
<Button Width="250">1</Button>
<Button Width="250">2</Button>
<Button Width="250">3</Button>
</WrapPanel>
</ScrollViewer>
However, this second solution only works if you already know the width of your child elements, ideally you want your max width to be set to the actual width of the largest child item, but in order to do that you'd have to create a custom control that derives from wrap panel and write the code yourself to check for that.

This is my solution for this:
<Grid Width="475">
<ItemsControl ItemsSource="{Binding Items}"
Height="450" Width="475" >
<ItemsControl.ItemTemplate>
<DataTemplate>
<local:HorizontalListItemControl />
</DataTemplate>
</ItemsControl.ItemTemplate>
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
</ItemsControl>
</Grid>
I'll try to explain:
I used an ItemsControl, its ItemsSource was bound to my Items collection.
Inside it, I defined a WrapPanel as the ItemsPanelTemplate. This is what makes the wrapping job done.
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
But now, there is no scrolling, right?
To solve this, I defined an ItemsPresenter inside a ScrollViewer as the ControlTemplate:
<ItemsControl.Template>
<ControlTemplate>
<ScrollViewer>
<ItemsPresenter />
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>
And now you can scroll.
Hope I helped.

public bool CheckUIElementInBounary(UIElement element, Rect r)
{
bool inbound = false;
Point p1 = element.PointToScreen(new Point(0, 0));
Point p2 = element.PointToScreen(new Point(0, element.RenderSize.Height));
Point p3 = element.PointToScreen(new Point(element.RenderSize.Width, 0));
Point p4 = element.PointToScreen(new Point(element.RenderSize.Width, element.RenderSize.Height));
if (CheckPoint(p1, r) || CheckPoint(p2, r) || CheckPoint(p3, r) || CheckPoint(p4, r))
{
inbound = true;
}
return inbound;
}
public bool CheckPoint(Point p, Rect bounday)
{
bool inbound = false;
if (p.X >= bounday.Left && p.X <= bounday.Right && p.Y <= bounday.Top && p.Y <= bounday.Bottom)
{
inbound = true;
}
return inbound;
}
===================
void mainViewer_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
foreach (var item in this.mainContent.Items)
{
Button btn = item as Button;
Point p1 = mainViewer.PointToScreen(new Point(0, 0));
Point p2 = mainViewer.PointToScreen(new Point(mainViewer.ActualWidth, mainViewer.ActualHeight));
Rect bounds = new Rect(p1, p2);
if (!CheckUIElementInBounary(btn, bounds))
{
this.Title = btn.Content.ToString();
mainContent.ScrollIntoView(btn);
break;
}
}
}

Related

How to exclude child from layouting

Look at example:
<Grid VerticalAlignment="Top" Background="Yellow">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="auto" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="textBlock"/>
<Button Grid.Column="1">
<Viewbox>
<Path Height="100" Width="100" Fill="Black" Data="M 0,0 H 100 V 100 H 0 Z" />
</Viewbox>
</Button>
</Grid>
Here is a button with vector graphics and I want it to be as small as it needs to be (restrict vector graphics from exploding).
Here is how it looks like and how I want it to be:
There are several possible solutions how to overcome the problem, to list few:
By binding Width/Height to some other element (this has issues with designer):
...
<Button Height="{Binding ActualHeight, ElementName=textBlock}" ...>
...
This is often used together with the ghost: special invisible element used by others (have no relationship with it) to layout themselves.
By hosting element inside Canvas (which is magical container), but then Canvas itself require layouting.
You can try putting Button inside Canvas. This will cause parent Grid only take height of TextBlock, but then there is another problem: how to position (layout) Canvas itself, so that its children are layout properly, kek.
In example above I don't actually want TextBlock to be a sister of Button, they could overlap (you wouldn't want the button to be hidden if available size is not enough, it should rather overlap something less important), I just want them to have same parent (if it moves - children will move). Confused? Look here:
<Grid>
<TextBlock ... />
<Button HorizontalAlignment="Right" ... />
</Grid>
This layout has same problems and can be solved similarly.
Now try to abstract from concrete examples above.
What I actually want: is to learn how to exclude element from layout of container. Like if element is collapsed, so the parent container will measure children sizes (except this element), layout children and then, after layouting is finished, element suddenly become visible and is restricted by parent container, while can use various alignments.
Does it make sense what I am asking? Maybe custom container is the way? Or do I miss something existing and obvious?
If you want to constrain the Path by the size of the described geometry, it's as simple as setting StretchDirection="DownOnly" on the Viewbox.
If you truly want it to request no vertical space of its own, and have its height determined by its layout 'neighbors' (in this case, the TextBlock), then you'll need to wrap it in a custom layout container. But you can't simply exclude it from layout--at least not completely. If you did, the element would always end up with zero width and height. Instead, you can measure in two passes, with the first pass requesting a child height of zero, and the second pass basing the requested size on the arrange size given after the first pass.
I think the container below will give you what you want, but be warned that I haven't tested it thoroughly. Use at your own risk.
public class ZeroHeightDecorator : Border
{
private Size _lastSize;
private Size _idealSize;
protected override void OnVisualChildrenChanged(DependencyObject added, DependencyObject removed)
{
base.OnVisualChildrenChanged(added, removed);
_idealSize = new Size();
_lastSize = new Size();
}
protected override Size MeasureOverride(Size constraint)
{
var child = this.Child;
if (child == null)
return new Size();
if (child.IsMeasureValid)
child.Measure(new Size(Math.Max(_lastSize.Width, constraint.Width), _lastSize.Height));
else
child.Measure(new Size(constraint.Width, 0d));
_idealSize = child.DesiredSize;
return new Size(_idealSize.Width, 0d);
}
protected override Size ArrangeOverride(Size arrangeSize)
{
var child = this.Child;
if (child != null)
{
if (arrangeSize != _lastSize)
{
// Our parent will assume our measure is the same if the last
// arrange bounds are still available, so force a reevaluation.
this.InvalidateMeasure();
}
child.Arrange(new Rect(arrangeSize));
}
_lastSize = arrangeSize;
return arrangeSize;
}
}
A more flexible container would allow you to specify which dimension(s) to minimize: Width , Height, or Neither, or Both. Feel free to extend it :).
Example in action:
<Grid VerticalAlignment="Top" Background="Yellow">
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<TextBlock x:Name="textBlock" />
<t:ZeroHeightDecorator Grid.Column="1">
<Button>
<Viewbox>
<Path Fill="Black" Data="M 0,0 H 100 V 100 H 0 Z" />
</Viewbox>
</Button>
</t:ZeroHeightDecorator>
</Grid>

WPF ListView items to be displayed with individual height

I'm currently trying to display a Dictionary (which is held in a Dictionary itself).
I started at first using a UniformGrid as ItemsPanelTemplate, but realized pretty fast, the items to display can have individual heights.
I've got so far, that I can display all content using the UniformGrid, but can't seem to get it working using a Grid or StackPanel as ItemsPanelTemplate.
The code below is working fine with the downside that each Operation-block is given the same height though their height can be variable.
After given some thought I came to the conclusion that a StackPanel would be best to use, as the Operations would be shown bleow each other taking the height they needed. But when I tried, I relaized they take only a fraction of the ListView's height.
Worth to mention:
The Operation-UserControl in itself does evaluate its height and build its layout accordingly. So it doesn't take the space needed to display all content, but displays the content which does fit in the available space.
So how can I achieve that the ListViewItems (=operations) take the ListView's full height?
EDIT: clarification
if the described behaviour isn't possible with any above mentioned control, but any other could provide the needed funtionality, let me know...
EDIT2: some examples
Total available space: 500
No Scrollbar.
Sidenote: there is no maxItemLimit, but it's highly unlikely that the ItemCount would exceed 10.
Given 1 item: (needed space to display all content 300)
This single item would take 300.
Given 2 items: (these would need 150 and 200 of space)
Both items would be displayed in there full size: 150, 200.
(Presumably only working with a StackPanel.)
Given 10 items:
Those 10 would be squeezed equally or relative to full-desired size in the 500 (so 50 per item).
Both behaviours would be fine.
UniformGrid vs StackPanel
<UserControl x:Name="vDay" x:Class="RefServiceClient.Day"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:RefServiceClient"
mc:Ignorable="d"
d:DesignHeight="60" d:DesignWidth="120"
MinHeight="40">
<Grid x:Name="gridDay"
Width="{Binding ActualWidth, ElementName=vDay, Mode=OneWay}"
Height="{Binding ActualHeight, ElementName=vDay, Mode=OneWay}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid x:Name="DayHeader" Grid.Row="0">
<!--containing header info: several textboxes, which are irrelevant for the question-->
<TextBox x:Name="dayShortname"
Grid.Row="0" Grid.Column="1"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Margin="1"
Text="{Binding Path=Value.DayShortname}"/>
</Grid>
<ListView x:Name="operations" Grid.Row="1" Background="Aqua"
ItemsSource="{Binding Path=Value.OperationList}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
ScrollViewer.HorizontalScrollBarVisibility="Disabled"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<ListView.ItemTemplate>
<DataTemplate>
<local:Operation Background="Crimson" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch"/>
<!--<local:Operation Background="Crimson" />-->
</DataTemplate>
</ListView.ItemTemplate>
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<!--<Grid Grid.IsSharedSizeScope="True"/>-->
<!--<StackPanel/>-->
<!--<VirtualizingStackPanel Orientation="Vertical"/>-->
<UniformGrid Columns="1"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
</ListView>
</Grid>
You could create a custom panel that arranges your item according to your rules. Then you just have to design your items in a way that they display nicely for whatever size they are allowed to take.
A rough sketch of the panel could look as follows:
public class SqueezeStackPanel : Panel
{
protected override Size MeasureOverride(Size availableSize)
{
var desiredHeight = 0.0;
foreach (UIElement child in InternalChildren)
{
child.Measure(availableSize);
desiredHeight += child.DesiredSize.Height;
}
if (availableSize.Height < desiredHeight)
{
// we will never go out of bounds
return availableSize;
}
return new Size(availableSize.Width, desiredHeight);
}
protected override Size ArrangeOverride(Size finalSize)
{
// measure desired heights of children in case of unconstrained height
var size = MeasureOverride(new Size(finalSize.Width, double.PositiveInfinity));
var startHeight = 0.0;
var squeezeFactor = 1.0;
// adjust the desired item height to the available height
if (finalSize.Height < size.Height)
{
squeezeFactor = finalSize.Height / size.Height;
}
foreach (UIElement child in InternalChildren)
{
var allowedHeight = child.DesiredSize.Height * squeezeFactor;
var area = new Rect(new Point(0, startHeight), new Size(finalSize.Width, allowedHeight));
child.Arrange(area);
startHeight += allowedHeight;
}
return new Size(finalSize.Width, startHeight);
}
}
This panel can be used in the ItemsPanelTemplate of your ListView with disabled scrollbars.
It depends. I'll give you an options. First. Implement, let say, Boolean AttachedProperty, marking whether this particular instance should be of certain size. In case 0/1 is not sufficient, declare appropriate enumeration. Second. Extend existing StackPanel, override appropriate protected members. At least, MeasureOverride/ArrangeOverride. There you can read the value of corresponding attached property and decide how big or small it has to be. Does it sound like a solution? In case it does, I can provide some examples.

Grid tilt effect like Windows 8 Start Screen

I'm trying to create a tile in WPF that looks like the tiles of the Windows 8 Start screen.
Actually, the problem is that I don't know how to make the tilt effect of the Windows 8 tiles ( the click effect ).
I tried different transformations such as matrix transofmration, but this is not what I want.
Explanation in image :
Default style:
Here is my code
<Grid >
<Grid.LayoutTransform>
<MatrixTransform>
<MatrixTransform.Matrix >
<Matrix OffsetX="5" OffsetY="5" M11="1" M12="0.1"></Matrix></MatrixTransform.Matrix>
</MatrixTransform>
</Grid.LayoutTransform>
<Grid.Background>
<ImageBrush ImageSource="/MaCollectivitéWPF;component/src/Img/Home/Porte Document-petite.jpg" Stretch="UniformToFill" >
</ImageBrush>
</Grid.Background>
It's almost what I want but I only want the bottom to be tilted.
I've take a look at the 3D controls but I think it's to complicated for what I'm looking for.
Is there a solution with Layout Transformation I would not have seen yet ?
I think you want to pinch Greg Schechter's Planerator for tilting.
Your use case is exactly the one in the article, so I doubt you'd need to change anything. I've not tried it myself, but it seems to reduce tilting the plane to fairly easy xaml. Worth a look at least.
<pl:Planerator RotationY="35">
<StackPanel Orientation="Horizontal" ... >
<StackPanel>
<Label FontSize="24" Content=" " Foreground="#FF000000"/>
<Border ... >
<MediaElement x:Name="myMediaElement" />
</Border>
</StackPanel>
<ListBox ... />
</StackPanel>
</pl:Planerator>

WPF Arrange Panel Children

I have a DockPanel with some images nested inside.
<DockPanel x:Name="ControlStack"
Background="LightGray"
MouseLeftButtonUp="ECIControlStack_MouseLeftButtonUp">
<DockPanel.Resources>
<Style TargetType="{x:Type Image}">
<Setter Property="Margin" Value="0,0,3,0"/>
</Style>
</DockPanel.Resources>
<Image x:Name="FirstImage" Source="{Binding GpsImage}" Height="36" Width="36"/>
<Image x:Name="SecondImage" Source="{Binding YellowIcon}" Height="36"/>
<Image x:Name="ThirdImage" Source="{Binding RedIcon}" Height="36"/>
<Image x:Name="FourthImage" Source="{Binding GreenIcon}" Height="36"/>
</DockPanel>
I would like to change the order of the items programmatically from code behind or really anywhere.
Just doing some general tinkering, trying to even get the objects to move.
I've attempted something such as:
Rect rectangleBounds = new Rect();
rectangleBounds = ControlStack.RenderTransform.TransformBounds(
new Rect(0, 0, ControlStack.Width, ControlStack.Height));
Canvas.SetLeft(FirstImage, rectangleBounds.Left + 100);
Canvas.SetRight(FirstImage, rectangleBounds.Right + 100);
Canvas.SetTop(FirstImage, rectangleBounds.Top + 100);
Canvas.SetBottom(FirstImage, rectangleBounds.Bottom + 100);
But to no avail.
1.I don't think this works because it is in the dockpanel and not a canvas child?
2.Even if it did work it would move freely and I assume destroy the dockPanel stack order.
Thanks for taking a look.
As stated in the comments, changing the order is easiest by simply adjusting the index of the images. I ended up using the following:
private void SetTopImage()
{
Image NewTopImage = FindIconPriority(); // Finds what Icon I would like to have in front.
ControlStack.Children.Remove(NewTopImage);
ControlStack.Children.Insert(0, NewTopImage);
}

Textblock dyanmic placement WPF

I have a text data. I would like to place the data dynamically to a certain point [Absolute point, it may be (0,0) (10,10) or (100,100)].
Here the problem comes. My requirement is text block's center point should be the base point. How to make it and place it directly on the canvas? [Which means align the absolute point and center point of the textblock]. Because in winforms we can set the alignment property and place it directly. Please check this question .It describes how to move the textblock after placing it on the canvas since actual width can be found.
I have two specific questions
Is it possible to do it in WPF
Is there anyway to find the actual width before placement, in case I can do it as in the other question
Update (Further explanation)::::: This is my Data Template to place the text, I can use TranslateTransform to translate the text as soon as the text is placed on the canvas by using MoveToPoint function mention below.
The real problem come, how to pass the ActualWidth to MoveToPoint inside datatemplate,
XAML
<DataTemplate DataType="{x:Type local:Text}">
<TextBlock Text="{Binding Description}"
FontSize= "{Binding Thickness}"
RenderTransformOrigin="0.5,0.5"
Foreground="#FFF63AFF"
FontWeight="Bold" >
<TextBlock.RenderTransform>
<TransformGroup>
<TranslateTransform X= "{Binding StartPoint.X}" Y= "{Binding StartPoint.Y}" />
<RotateTransform Angle= "{Binding Angle}" />
</TransformGroup>
</TextBlock.RenderTransform>
</TextBlock>
</DataTemplate>
C#
void MoveToPoint(UIElement sender, Point point)
{
Canvas.SetLeft(sender, point.X - sender.RenderTransformOrigin.X * sender.ActualWidth);
Canvas.SetTop(sender, point.Y - sender.RenderTransformOrigin.Y * sender.ActualHeight);
}

Categories