Why Canvas inside Canvas dosen't fill it's parent in WPF? - wpf

I have an custom UserControl that displays an image, i'm trying to use this user control
in the MainWindow and for some reason the image dosen't fill it's content.
And i can't figure out why.
This is the custom UserControl :
<UserControl x:Class="BookWriter3D.Sheet"
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"
mc:Ignorable="d"
FlowDirection="RightToLeft">
<Canvas RenderTransformOrigin="0.0,0.0">
<Canvas.RenderTransform>
<RotateTransform x:Name="Angle"/>
</Canvas.RenderTransform>
<Canvas x:Name="LayoutRoot">
<Canvas.Clip>
<RectangleGeometry x:Name="imgRect" Rect="0,0,1500,800"/>
</Canvas.Clip>
<Image x:Name="sheetImage" Source="C:\Users\Eric\Documents\Papa\BookWriter3D\Resources\Images\Page0.jpg" Stretch="Fill"/>
<Image x:Name="shadowSpine" Source="C:\Users\Eric\Documents\Papa\BookWriter3D\Resources\Images\shadowspine.png" Canvas.Left="899" Canvas.Top="0"/>
<Image x:Name="curlShadow" Opacity="0.0" Source="C:\Users\Eric\Documents\Papa\BookWriter3D\Resources\Images\curlshadow.png" Canvas.Top="0" Stretch="Fill"
RenderTransformOrigin="1.0,0.66">
<Image.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="curlShadowScale" ScaleX="1.0" ScaleY="2.0"/>
<RotateTransform x:Name="curlShadowRotate" Angle="0"/>
<TranslateTransform x:Name="curlShadowTranslate" X="0" Y="0"/>
</TransformGroup>
</Image.RenderTransform>
</Image>
</Canvas>
</Canvas>
And this is the MainWindow.xaml :
<Canvas x:Name="layoutRoot" Background="White" RenderTransformOrigin=".5,.5">
<Canvas.RenderTransform>
<ScaleTransform ScaleX="1.0" ScaleY="1.0"/>
</Canvas.RenderTransform>
<local:Sheet x:Name="Page1Sheet"></local:Sheet>
<Canvas x:Name="traca" >
<Canvas x:Name="catrabs" Canvas.Top="0">
<Canvas.Clip >
<PathGeometry x:Name="baclpth" >
<PathGeometry.Figures >
<PathFigure IsClosed="True" x:Name="baclpz" StartPoint="0,0">
<PathFigure.Segments>
<LineSegment x:Name="baclzz" Point="400,0"/>
<LineSegment x:Name="baclzyy" Point="400,400"/>
<LineSegment x:Name="baclzyrq" Point="0,300" />
</PathFigure.Segments>
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</Canvas.Clip>
<local:Sheet x:Name="Page1TraceSheet" X="0" Y="625"/>
</Canvas>
This code continues, though i doubt it has any influense on this problem.
Any help fellas?
EDIT :
After a bit of debugging i found out that the outerWrapper Canvas fills the screen but the LayoutRoot dosen't fill it's parent but it's ( i guess) same size as image, so problem is different then i first susspected
EDIT 2 :
Problem solved.
As Canvas changes size according to it's content,
All i needed to do is set the width/height of the Image, and it worked perfectly.

A Canvas never resizes its children. Consider using a Grid instead, if you want the Images to automatically fill a certain area. Try this
<Grid>
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg" />
</Grid>
versus this
<Canvas>
<Image Source="C:\Users\Public\Pictures\Sample Pictures\Desert.jpg" />
</Canvas>
to get a feeling for the difference. And try different settings for Image.Stretch on an Image in a Canvas. They'll be ignored.

Problem solved. As Canvas changes size according to it's content, All i needed to do is set the width/height of the Image, and it worked perfectly.
<Image x:Name="sheetImage" Source="C:\Users\Eric\Documents\Papa\BookWriter3D\Resources\Images\Page0.jpg" Width="1280" Height="800" Stretch="Fill" />

Canvas does not alter or constrain other control's size or position. Maybe a Grid is what you need.

Related

Relative coordinates in DrawingBrush

I am somewhat confused about how relative coordinates work in WPF, especially in scenarios with DrawingBrushes.
Let's say I want to paint the background of a square area, which is flexible in it's size. I want to paint the background with a special "shape", let's say a kind of "T" laying on the side, with the vertical stroke going through the middle of the area.
Using relative coordinates (the size of the area is flexible), I came up with the following XAML:
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="722" Width="722" UseLayoutRounding="True">
<Window.Resources>
<DrawingBrush x:Key="EdgeGrid">
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<!-- draw a single T laying on the side -->
<GeometryGroup>
<!-- top to bottom -->
<LineGeometry StartPoint="0.5,0.0" EndPoint="0.5,1"/>
<!-- left to right -->
<LineGeometry StartPoint="0.5,0.5" EndPoint="1,0.5"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Thickness="0.01" Brush="Black" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Window.Resources>
<Grid Name="LayoutRoot">
<Rectangle Width="400" Height="400" Stroke="Black" StrokeThickness="1" Fill="{StaticResource EdgeGrid}">
</Rectangle>
</Grid>
But the result I get looks like this:
(source: bilder-hochladen.net)
Shouldn't the vertical stroke go right through the middle (X coordinate is 0.5)?
And also how can I set the pen thickness to be 1 or 2 pixels in relative mode?
Any ideas?
You'll have to set the ViewboxUnits property of the DrawingBrush to Absolute (instead of the default RelativeToBoundingBox). The Viewbox would still be (0,0,1,1).
See the TileBrush Overview article on MSDN for details about a brush's viewbox and viewport.
<DrawingBrush x:Key="EdgeGrid" ViewboxUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<LineGeometry StartPoint="0.5,0.0" EndPoint="0.5,1"/>
<LineGeometry StartPoint="0.5,0.5" EndPoint="1,0.5"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Thickness="0.01" Brush="Black" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
Of course this won't let you define stroke widths in pixels. Making the drawing in absolute coordinates and then putting the whole thing in a Viewbox won't also help much, because strokes would still be scaled.
In order to get a scalable drawing with fixed stroke width, you would have to use a Path element, where you set the StrokeThickness and Data properties and assign a ScaleTransform to the Transform property of the Geometry used as Data.
In your special case of drawing a centered, T-shaped figure with fixed stroke width, you may simply draw two (very long) Lines in a Canvas, where the coordinate origin is centered by putting the Canvas in a 2x2 Grid. You may even choose to have different strokes and stroke widths for the two Lines.
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Canvas Grid.Column="1" Grid.Row="1">
<Line Y1="-10000" Y2="10000" Stroke="Black" StrokeThickness="1"/>
<Line X2="10000" Stroke="Black" StrokeThickness="1"/>
</Canvas>
</Grid>
To get vertical stroke right you need to do it like this:
<GeometryGroup>
<!-- top to bottom -->
<LineGeometry StartPoint="0.75,0.0"
EndPoint="0.75,1" />
<!-- left to right -->
<LineGeometry StartPoint="0.5,0.5"
EndPoint="1,0.5" />
</GeometryGroup>
But that won't help you with pen thickness. In general, if you want to scale a geometry - first create it using absolute coordinates you like (say in 0-100 range), then put that into ViewBox or use ScaleTransform, like this:
<Viewbox Width="400"
Height="400">
<Path Stroke="Black"
StrokeThickness="1">
<Path.Data>
<GeometryGroup>
<!-- top to bottom -->
<LineGeometry StartPoint="75,0"
EndPoint="75, 100" />
<!-- left to right -->
<LineGeometry StartPoint="50,50"
EndPoint="100, 50" />
</GeometryGroup>
</Path.Data>
</Path>
</Viewbox>
Let's see how the proposed solution would look like.
Let's assume we want to show the shapes in a kind of grid and draw various shapes depending on the data (by selecting an appropriate DateTemplate). For simplicity, in this example, let's draw only one kind of shape (a cross, as in my initial question):
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="722" Width="722" UseLayoutRounding="True">
<ItemsControl ItemsSource="{Binding data}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<UniformGrid Columns="10" Rows="10">
</UniformGrid>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid x:Name="Cell">
<Path StrokeThickness="2" Stroke="Blue" SnapsToDevicePixels="True">
<Path.Data>
<GeometryGroup>
<GeometryGroup.Transform>
<ScaleTransform ScaleX="{Binding ElementName=Cell, Path=ActualWidth}" ScaleY="{Binding ElementName=Cell, Path=ActualHeight}"/>
</GeometryGroup.Transform>
<!-- top to bottom -->
<LineGeometry StartPoint="0.5,0.0" EndPoint="0.5,1"/>
</GeometryGroup>
</Path.Data>
</Path>
<Path StrokeThickness="1" Stroke="Black" SnapsToDevicePixels="True">
<Path.Data>
<GeometryGroup>
<GeometryGroup.Transform>
<ScaleTransform ScaleX="{Binding ElementName=Cell, Path=ActualWidth}" ScaleY="{Binding ElementName=Cell, Path=ActualHeight}"/>
</GeometryGroup.Transform>
<!-- left to right -->
<LineGeometry StartPoint="0,0.5" EndPoint="1,0.5"/>
</GeometryGroup>
</Path.Data>
</Path>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Window>
#Clemens Is this the solution you had in mind? Is this the correct way of doing it?
The only problem I have with the result is that the lines are blurry and there is even a break to be seen in the horizontal line. Is there any solution for this?

WPF / Xaml, vertical aligment of LineGeomtry inside grid not working properly

I try to get into creation of custom controls with for WPF. I found many good
tutorials and advises on the web so I started width a really simple example to get
my hands dirty and get some practice. I figured out that the issue stumbled across
is not really related to the subject of custom controls. So I extracted the xaml code to a simple wpf form.
<Window x:Class="WpfVerticalAigmentTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="200" Width="200">
<Grid>
<Grid Height="40" Background="LightCyan" VerticalAlignment="Center">
<Path Stroke="Red"
StrokeThickness="20" VerticalAlignment="Center" >
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint="100,0"></LineGeometry>
</Path.Data>
</Path>
</Grid>
</Grid>
My expectation was to get a line centered in the grid and claiming the half of the stroke thickness on each side from the center. But as the linked image shows differs from my expectation.
"Resulting visualization"
So it look like I missed a detail about the line shape or linegeomtry. How do I get the the line displayed as shown in the following image?
"Expected result"
You need to match the Width and Height of the LineGeometry to the Width and Height of the Path and set the VerticalAlignment property to Bottom:
<Grid Height="20" Width="200" Background="LightCyan" VerticalAlignment="Center">
<Path Stroke="Red" StrokeThickness="20" VerticalAlignment="Bottom">
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint="200,0"></LineGeometry>
</Path.Data>
</Path>
</Grid>
If your goal is your the expectaions, and not the way how u have reached this, I could prefer to you this:
<Grid>
<Grid Height="40" Background="LightCyan" VerticalAlignment="Center">
<Border BorderThickness="10" VerticalAlignment="Center" BorderBrush="Red" />
</Grid>
</Grid>
The problem here is that the starting point of the XY Coordinates of the Path starts on the top left, and the stroke expands in both directions but thereby only makes the Path bigger to the bottom (I can't really tell you why, but that's just what seems to happen).
You can see this pretty good in the Design View:
To work around this simply move your Y Coordinates down half of the stroke size.
<Grid Height="40"
VerticalAlignment="Center"
Background="LightCyan">
<Path VerticalAlignment="Center"
Stroke="Red"
StrokeThickness="20">
<Path.Data>
<LineGeometry StartPoint="0,10" EndPoint="100,10" />
</Path.Data>
</Path>
</Grid>
Or wrap it in another control (Canvas is the commonly used controls for Paths) with the desired height:
<Grid Height="40"
VerticalAlignment="Center"
Background="LightCyan">
<Canvas Height="20" VerticalAlignment="Center">
<Path Stroke="Red"
StrokeThickness="20">
<Path.Data>
<LineGeometry StartPoint="0,10" EndPoint="100,10" />
</Path.Data>
</Path>
</Canvas>
</Grid>
And you are good to go:

Canvas as Image in Button WPF

I am in the process of moving all images from our project into a ResourceDictionary to be used across all projects. When using Syncfusion Metro Studio I am able to get the XAML source of the images.
<Viewbox x:Shared="false" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Grid>
<Grid Name="backgroundGrid" Width="48" Height="48" Visibility="Collapsed" />
<Path Data="M0,4.1309996L20.362437,4.1309996C20.038338,4.8125897,19.782439,5.5301299,19.594339,6.2769008L2.1451931,6.2769008 2.1451931,23.445208 30.042807,23.445208 30.042807,20.78887C30.342306,20.81167 30.642406,20.834471 30.949104,20.834471 31.367603,20.834471 31.781102,20.81027 32.188001,20.76737L32.188001,25.590239 20.922435,25.590239 20.922435,27.736771 23.605427,27.736771 23.605427,29.882 8.5839529,29.882 8.5839529,27.736771 11.265565,27.736771 11.265565,25.590239 0,25.590239z M29.967411,3.9921243L29.967411,8.1359167 25.823251,8.1359167 25.823251,10.100034 29.967411,10.100034 29.967411,14.243865 31.931586,14.243865 31.931586,10.100034 36.075645,10.100034 36.075645,8.1359167 31.931586,8.1359167 31.931586,3.9921243z M30.948448,0C35.985142,1.0841802E-08 40.067997,4.0825729 40.067997,9.1171007 40.067997,14.153367 35.985142,18.236 30.948448,18.236 25.912456,18.236 21.830998,14.153367 21.830998,9.1171007 21.830998,4.0825729 25.912456,1.0841802E-08 30.948448,0z" Stretch="Uniform" Fill="#FF666666" Width="26" Height="26" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<TransformGroup.Children>
<RotateTransform Angle="0" />
<ScaleTransform ScaleX="1" ScaleY="1" />
</TransformGroup.Children>
</TransformGroup>
</Path.RenderTransform>
</Path>
</Grid>
</Viewbox>
The issue here is that I am creating buttons with the image next to my text as follows:
<corecontrols:IdyllicButton Name="btnClose" Height="30">
<StackPanel Orientation="Horizontal">
<Image Source="/MHA.Modules.PolicyAdmin;component/Images/Cancel.png"/>
<TextBlock VerticalAlignment="Center" Margin="5,0" Text="Close"/>
</StackPanel>
</corecontrols:IdyllicButton>
But I cannot set my Image.ImageSource to my StaticResource that I have created in a ResourceDictionary as I get the following error:
An object of the type "System.Windows.Controls.Viewbox" cannot be
applied to a property that expects the type
"System.Windows.Media.ImageSource".
I have also tried using a canvas.
Any ideas how to do this?
Hoping this helps someone:
I found the solution, instead of using a Image, use the Rectangle control in its place and set the OpacityMask to the Viewbox resource.
<Rectangle Width="15" VerticalAlignment="Center"
Height="15"
Fill="{Binding RelativeSource={RelativeSource AncestorType=Button}, Path=Foreground}">
<Rectangle.OpacityMask>
<VisualBrush Stretch="Uniform" Visual="{StaticResource Close}"/>
</Rectangle.OpacityMask>
</Rectangle>
I found this scrolling MahApps.Metro source code for the WindowCommand Buttons.

Compass with an arrow wpf

I have to show a compass with an arrow inside a circle
I have the following code:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Page.Resources>
<Pen x:Key="BlackPen1" Thickness="1" Brush="Black"></Pen>
</Page.Resources>
<Grid>
<!-- Image for the Circle -->
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Pen="{StaticResource BlackPen1}" >
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry RadiusX="50" RadiusY="50"></EllipseGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Path Grid.Row="1" Data="M15,0 L30,40 L0,40Z" Stroke="Black" Fill="Black" StrokeThickness="1" HorizontalAlignment="Center" />
<Line Grid.Row="0" Y1="40" Y2="400" X1="0" X2="0" Stroke="Black" StrokeThickness="5" HorizontalAlignment="Center" />
</Grid>
</Page>
Now I have to transform this whole compass based on the input angle.
One thing I know is If I move my arrow shape into Image type I can tans form this using the following
<Image.RenderTransform>
<RotateTransform Angle="{Binding ElementName=root, Path=Angle}"/>
</Image.RenderTransform>
But I am not able to draw this geometry inside the Image tag.
How to achieve this?
Why would you need to rotate the Ellipse? Surely only the arrow moves in a compass. In order to make that job easier, why don't you create the arrow in just one Path, instead of additionally using a Line element? You could define the same arrow with rotation like this:
<Path Grid.Row="1" Data="M15,0 30,40 18,40 18,400 12,400 12,40 0,40Z" Stroke="Black"
Fill="Black" StrokeThickness="1" HorizontalAlignment="Center"
RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<RotateTransform Angle="{Binding Angle, ElementName=root}" />
</Path.RenderTransform>
</Path>
You can apply the rotation to the whole Grid That contains all of your controls. Don't forget to set RenderTransformOrigin="0.5,0.5" on the element you rotate so it rotates arround the center and not the top left corner.
Or you could add a RotationTransform to each of the elements Path, Line and Ellipse. However in this case Rotation centers will probably be different for each one and it makes it more complicated.

ScaleTransform and CenterX

I have the following code
<Canvas Width="800" Height="600">
...
<local:UpgradeLandDialog x:Name="upgradeDialog" Canvas.Left="250" Canvas.Top="200" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0">
<local:UpgradeLandDialog.LayoutTransform>
<ScaleTransform ScaleX="0" ScaleY="0" CenterX="400" CenterY="300"/>
</local:UpgradeLandDialog.LayoutTransform>
</local:UpgradeLandDialog>
</Canvas>
In the UserControl I animate the ScaleTranform to 1. I want UserControl to "grow" from its center, but it "grows" from the upper left corner of it. The values in CenterX and CenterY do nothing. How can I make it Scale as I want?
You can use RenderTransformOrigin="0.5,0.5" on the control you want to animate.
You can change your code like this:
<Canvas Width="800" Height="600" RenderTransformOrigin="0.5,0.5">
<local:UpgradeLandDialog x:Name="upgradeDialog" Canvas.Left="250" Canvas.Top="200" HorizontalAlignment="Center" VerticalAlignment="Center" Opacity="0">
<local:UpgradeLandDialog.LayoutTransform>
<ScaleTransform ScaleX="0" ScaleY="0"/>
</local:UpgradeLandDialog.LayoutTransform>
</local:UpgradeLandDialog>
</Canvas>
Remove (CenterX="400" CenterY="300") and add (RenderTransformOrigin="0.5,0.5") to the Canvas. This way if you have a container with dynamic width and height, it can scale from the center without problem.
To make it grow from its center, you'll have to animate its margins as well (at half the rate at which you animate the width and height).
I ran into this problem not too long ago as well. I ended up repositioning the user control at every layout update to simulate a custom point based growth.
This does work for me. Did I miss something?
<Rectangle StrokeThickness="1" Stroke="Black" Width="200" Height="200">
<Rectangle.RenderTransform>
<ScaleTransform
ScaleX="{Binding ElementName=slider, Path=Value}"
ScaleY="{Binding ElementName=slider, Path=Value}"
CenterX ="100" CenterY="100"/>
</Rectangle.RenderTransform>
</Rectangle>
Even though this is an old post, I thought I'd share my findings, since it took me way too long to figure out this fairly simple solution.
Flipping the y-axis was easy, but I couldn't get CenterX and CenterY working. I really needed to be able to set the origin at any position I wanted.
Solution: nested canvasses.
<Canvas HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
<Canvas Canvas.Left="{Binding MyOriginLeft}" Canvas.Bottom="{Binding MyOriginBottom}">
<Canvas.LayoutTransform>
<ScaleTransform ScaleX="1" ScaleY="-1"/>
</Canvas.LayoutTransform>
<!-- This now does what you expect it to do, independent of position of origin -->
<Line X1="10" Y1="20" X2="30" Y2="40" Stroke="Black" StrokeThickness="1"/>
</Canvas>
</Canvas>

Resources