WPF: Sizing Canvas to contain its children - c#

I have a task to draw multiple rectangles and manipulate them. So I'm using an ItemsControl with it's ItemsPanel as a Canvas, and a wrapping ScrollViewer:
<ScrollViewer Height="200" Grid.Row="1" >
<ItemsControl Name="rectanglesList" Background="AliceBlue">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Y}"/>
<Setter Property="Canvas.Top" Value="{Binding X}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<Rectangle Width="{Binding Width}" Height="{Binding Height}" Fill="{Binding Color}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
Now since the Canvas height and width properties do not include it's children, the Scrollviewer won't work unless I change the ItemsControl height and width manually by calculating summing the heights and widths of the children and assigning them to the ItemsControl.
The question is now is there any property 'm missing that does this automatically?

You may use a custom Canvas that overrides the MeasureOverride method
public class MyCanvas : Canvas
{
protected override Size MeasureOverride(Size constraint)
{
base.MeasureOverride(constraint);
var size = new Size();
foreach (var child in Children.OfType<FrameworkElement>())
{
var x = GetLeft(child) + child.Width;
var y = GetTop(child) + child.Height;
if (!double.IsNaN(x) && size.Width < x)
{
size.Width = x;
}
if (!double.IsNaN(y) && size.Height < y)
{
size.Height = y;
}
}
return size;
}
}
and which would be used like this:
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<local:MyCanvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
It requires that the item container element, besides Canvas.Left and Canvas.Top has its Width and Height set in the ItemContainerStyle:
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
<Setter Property="Width" Value="{Binding Width}"/>
<Setter Property="Height" Value="{Binding Height}"/>
</Style>
</ItemsControl.ItemContainerStyle>
The ItemTemplate would then just look like this:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderThickness="1" BorderBrush="Black">
<Rectangle Fill="{Binding Color}"/>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
or
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle StrokeThickness="1" Stroke="Black" Fill="{Binding Color}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
You may also want to put the SCrollViewer into the ControlTemplate of the ItemsControl:
<ItemsControl.Template>
<ControlTemplate TargetType="ItemsControl">
<ScrollViewer HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto">
<ItemsPresenter/>
</ScrollViewer>
</ControlTemplate>
</ItemsControl.Template>

#Clemens's answer is great. I just wanted to add some context as to why it's necessary in case it wasn't clear.
In WPF the Canvas panel does not consider its children when asked to measure itself. It is the responsibility of the parent control to ensure the canvas is sized appropriately for the items it will contain.
In the subclass #Clemens provides, the logic for considering all children is provided in MeasureOverride.
Note that this will not consider any non-FrameworkElement children. If you add a very large number of children, you may notice a performance hit during layout.

Related

WPF TemplateSelector to draw different figures on just one canvas

I have a block of code in my xaml that it is repeated several times, each to draw a different predefined figure (rectangle 1,2...n, sellipse1,2...n, etc) programmatically:
<ItemsControl ItemsSource="{Binding MyEllipse}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Width="200" Height="100" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="{Binding Width}" Height="{Binding Height}"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
I would like to find a way of having this summarized with a Data/Item TemplateSelector, but after trying several approaches i couldn't get it to work.
Any little working example would be much appreciated, thanks!

Enlarge an image on mouseover in Popup in WPF

Hi all I've looked through several of these forum posts with different solutions but can't seem to get it. My style
<Style x:Key="ScaleStyle" TargetType="Image">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Grid.ZIndex" Value="1"/>
<Setter Property="RenderTransform">
<Setter.Value>
<ScaleTransform ScaleX="2.5" ScaleY="2.5"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
</Style>
My UniformGrid with images:
<ListView Grid.ColumnSpan="5" Grid.Row="11" Name="Thumbnails">
<ListView.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="5"/>
</ItemsPanelTemplate>
</ListView.ItemsPanel>
<ListView.ItemTemplate>
<DataTemplate>
<Image Style="{StaticResource ScaleStyle}" RenderTransformOrigin="0.5,0.5" Source="{Binding}" Height="100" Width="100" Margin="3">
</Image>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
What happens with this is that the image gets bigger but inside the uniform grid which makes it overlap with other images and is just not nice looking.
On the other hand I tried using a tooltip popup and it would open a new popup but the image inside would be a giant zoom of the corner of the image.
<Image Name="Image" Source="/WpfApplication1;component/Images/Tulips.jpg" Height="100"
Stretch="Uniform">
<Image.ToolTip>
<ToolTip DataContext="{Binding PlacementTarget,
RelativeSource={RelativeSource Self}}">
<Border BorderBrush="Black" BorderThickness="1" Margin="5,7,5,5">
<Image Source="{Binding Source}" Stretch="None" />
</Border>
</ToolTip>
</Image.ToolTip>
The problem might be that the original images are very large and in the Uniform grid i set the width and height to a 100 which makes them look like thumbnails but the tooltip seems to reference the original width and height and starts from a corner until it fits the width and height of the tooltip popup which ends up just showing a small part of the original very large picture.
Any help would be greatly appreciated.
Setting the Stretch property of the image to fill will make your image resize to the size of your container.

Drawing multiple circles in wpf using ItemsControl

i have to write some circles to a canvas in wpf. i have this tutorial as a base but it won't work somehow:
http://www.wpf-tutorial.com/list-controls/itemscontrol/
my xaml
<ItemsControl Name="icCircles">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent" Width="300" Height="500"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Canvas.Top="{Binding X}" Canvas.Left="{Binding Y}" Fill="Black" Height="5" Width="5" Stroke="Black" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
the code behind:
List<Circle> circles = new List<Circle>();
circles.Add(new Circle() { X = 50, Y = 50 });
circles.Add(new Circle() { X = 100, Y = 50 });
icCircles.ItemsSource = circles;
the circle class:
public class Circle
{
public int X { get; set; }
public int Y { get; set; }
}
if i add nothing to the list, i don't get anything. if i add one circle, i see it, but at X0/Y0. if i add a second one, i still see only one circle. possibly because they are at the same location.
what am i doing wrong?
You have to bind the Canvas.Left and Canvas.Top properties in a Style for the ContentPresenter that serves as item container. The Style is applied by the ItemContainerStyle property:
<ItemsControl Name="icCircles">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas Background="Transparent"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Fill="Black">
<Path.Data>
<EllipseGeometry RadiusX="2.5" RadiusY="2.5"/>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
Note however that those small Ellipses are aligned at their top left corner, so it's not their center pointer at which they are drawn. You may therefore replace them by Paths with EllipseGeometries:
<ItemsControl.ItemTemplate>
<DataTemplate>
<Path Fill="Black">
<Path.Data>
<EllipseGeometry RadiusX="2.5" RadiusY="2.5"/>
</Path.Data>
</Path>
</DataTemplate>
</ItemsControl.ItemTemplate>

WPF length unit is not 1/96 in

I need to draw a rectangle with size of 10x10cm using WPF (Canvas). I tried many ways to convert centimeters to pixels. I used the LengthConverter class, I used an explicit assignment (Width="10cm") but the actual width and height of rectangle are less than 10cm (about 9.35cm). It is a bit strange.
As I know, 1 WPF unit == 1/96 inch by default. Where could be a problem?
Here is a piece of my XAML:
<ItemsControl Grid.Row="1" ItemsSource="{Binding Cells}" Background="White"
ClipToBounds="True">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="10cm" Height="10cm" StrokeThickness="0" Fill="Black"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

Wpf Prism give style to each items of a ItemControl (Views in a Region)

I have a WPF Prism project and it has a Region base on ItemControl:
<ItemsControl prism:RegionManager.RegionName="WorkspaceRegion" >
In this ItemControl i see some of my Views verticaly well but i want give a style to each Item of ItemControl (each View).
All of items (views) must have same style (for example: background
color, padding, margin, border and...)
I want something like this (for example):
I used a simple style and code like this:
<ItemsControl prism:RegionManager.RegionName="WorkspaceRegion" Background="#765e4d" Margin="10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style>
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Border BorderBrush="Red" Padding="10" BorderThickness="1" CornerRadius="5">
<ContentPresenter Content="{Binding}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
Its Error:
A style intended for type 'ItemsControl' cannot be applied to type
'View1'
Also i tested this codes:
<ItemsControl prism:RegionManager.RegionName="WorkspaceRegion" Background="#765e4d" Margin="10">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid >
<TextBlock Text="Test"/>
<Border BorderBrush="Red" Padding="10" Margin="10" BorderThickness="1" CornerRadius="5">
<ContentPresenter Content="{Binding}"/>
</Border>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
But the result is like when i write:
<ItemsControl prism:RegionManager.RegionName="WorkspaceRegion" >
Why? What is my mistake?
How can i do this?
Edit1:
I used <ItemsPresenter/> instead of <ContentPresenter Content="{Binding}"/>
Result: without any change
Edit2:
I write this style for ItemContainerStyle property of the ItemsControl and it works if i remove ControlTemplate part from it.
Now the question is which kind of Presenter or Xaml Tag i should use inside the following ControlTemplate to my Views (UserControls) be shown.
<Style TargetType="{x:Type UserControl}" x:Key="MyItemContainerStyle">
<Setter Property="Background" Value="Brown"/>
<Setter Property="BorderBrush" Value="Blue"/>
<Setter Property="BorderThickness" Value="2"/>
<Setter Property="Margin" Value="10"/>
<Setter Property="Template" >
<Setter.Value>
<ControlTemplate>
??????????????????????????
<!-- The following ContentPresenter not working and the Items dose not show -->
<ContentPresenter/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Set the ContentTemplate property of the style, not Control.Template or Template
An ItemsControl gets rendered like this :
<ItemsControl>
<ItemsPanel>
<ContentPresenter>
<ItemTemplate>
</ContentPresenter>
<ContentPresenter>
<ItemTemplate>
</ContentPresenter>
<ContentPresenter>
<ItemTemplate>
</ContentPresenter>
</ItemsPanel>
</ItemsControl>
The ItemContainerStyle applies to the ContentPresenter object that wraps each item in this XAML tree, and I don't believe a ContentPresenter has either a Control.Template or a Template property.
When changing how a ContentPresenter is displayed you should overwrite the ContentTemplate property instead.
This works for me in my test application:
<ItemsControl Background="#FF85664F" prism:RegionManager.RegionName="WorkspaceRegion">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border BorderBrush="DarkGray" Padding="10"
Margin="5, 5, 5, 0" BorderThickness="1"
CornerRadius="5" Background="#FFC3BF8F">
<ContentPresenter Content="{Binding}" />
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
<system:String>ItemsControl Item #1</system:String>
<system:String>ItemsControl Item #2</system:String>
<system:String>ItemsControl Item #3</system:String>
<system:String>ItemsControl Item #4</system:String>
<system:String>ItemsControl Item #5</system:String>
</ItemsControl>

Categories