WPF usercontrol combine ellipse and arrow - wpf

I want to create a custom usercontrol to represent a player in a 2D map.
I had an ellipse to represent the player but I want to have on the border of the ellipse an arrow to indicate where the player is looking.
This is what I tried :
<Ellipse Width="17" Height="17" Stroke="Black" Fill="White" HorizontalAlignment="Center" />
<Path Data="M5,0 0,5 5,10" Fill="White" Stroke="Black" VerticalAlignment="Center" >
<Path.LayoutTransform>
<RotateTransform Angle="10"/>
</Path.LayoutTransform>
</Path>
the result :
That looks like what I want (it's not properly aligned but that's not the point here).
The problems are :
I know the position of the ellipse's center without the arrow
When the arrow will be on the right the relative position of the ellipse's center will be different --> I could solve this problem using a square control
My Circle has a textblock on top (Horitonzal + vertical center) to
display its id
How to move the arrow depending on the position looked ? I thought the easier might be to calculate an angle and rotate the whole control.
My first idea was to draw using any vector drawing software (illustrator for instance) the path, and get the coordinates of the path, and paste them in WPF.
then just rotate the usercontrol.
But doing this will also rotate the text and I don't want the text to rotate.
I'm stuck on this one, I hope my problem is enough described to be understood.
EDIT My first try :
<ControlTemplate TargetType="ContentControl">
<Grid Width="34" Height="34">
<Path x:Name="contour_forme"
Stroke="{TemplateBinding BorderBrush}"
StrokeThickness="1"
Stretch="Uniform"
Width="28"
Height="22"
HorizontalAlignment="Left"
Fill="{TemplateBinding Background}"
Data="M28.857,53.500 C24.537,53.487 20.477,52.380 16.938,50.443 C16.938,50.443 16.938,50.500 16.938,50.500 C16.938,50.500 16.785,50.350 16.785,50.350 C12.845,48.157 9.579,44.924 7.317,41.032 C7.317,41.032 -6.176,27.755 -6.176,27.755 C-6.176,27.755 8.206,14.530 8.206,14.530 C10.380,11.316 13.289,8.649 16.681,6.736 C16.681,6.736 16.938,6.500 16.938,6.500 C16.938,6.500 16.938,6.581 16.938,6.581 C20.525,4.615 24.641,3.496 29.021,3.509 C42.835,3.551 53.996,14.775 53.951,28.580 C53.906,42.385 42.670,53.542 28.857,53.500 ZM29.004,8.507 C17.953,8.474 8.965,17.400 8.929,28.443 C8.893,39.487 17.822,48.467 28.873,48.500 C39.924,48.533 48.912,39.608 48.948,28.564 C48.985,17.520 40.056,8.540 29.004,8.507 Z"
>
<Path.LayoutTransform>
<RotateTransform Angle="0" />
</Path.LayoutTransform>
</Path>
<TextBlock Style="{DynamicResource StyleTextes}" Foreground="White" VerticalAlignment="Center" HorizontalAlignment="Center"
Text="5"
/>
</Grid>
</ControlTemplate>
With the result :
As you can see I didn't manage to center the text inside my 22px circle.
My arrow is about 6 px height so I've created a control of 22 (circle's size expected) + 2 * 6px depending on the arrow position.
But when I try to rotate my path doing :
<Path.LayoutTransform> <RotateTransform Angle="90" />
</Path.LayoutTransform>
I have the following result :
I'm not sure on how I can keep the circle of my path in the center of the control when I rotate the path.

Just apply the RotateTransform to the "image" but not to the text.
Also I would use a render transform instead of a layout transform.
<Canvas Canvas.Left="206.333" Canvas.Top="119" Height="80" Width="80">
<Path Data="M244,99.333333 L210.16667,109.50034 244.83334,125.50034" Fill="#FFF4F4F5" Stretch="Fill" Stroke="Black" RenderTransformOrigin="0.5,0.5" Height="60" Canvas.Left="3" Canvas.Top="5" Width="60">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform/>
<SkewTransform/>
<RotateTransform Angle="70"/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<TextBlock TextWrapping="Wrap" Text="TextBlock" Height="20" Width="80" Canvas.Top="30" TextAlignment="Center"/>
</Canvas>

Related

How do I draw with coordinates in [0;1] instead of pixels in WPF?

My coordinates are relative to the control size, in the 0 to 1 range. I currently draw on my control using manual scaling by RenderSize, which works fine, but is surely the wrong way.
How can I draw directly in 0-1 coordinates instead?
You may use Path controls and scale their Data by applying an appropriate transform to the Geometry.Transform property, like in the trivial example shown below. This way you would scale the drawn shapes, but not their stroke thicknesses.
<Grid>
<Canvas>
<Canvas.Resources>
<ScaleTransform x:Key="transform"
ScaleX="{Binding Value, ElementName=scaleSlider}"
ScaleY="{Binding Value, ElementName=scaleSlider}"/>
</Canvas.Resources>
<Path Stretch="None" Stroke="Blue" StrokeThickness="2">
<Path.Data>
<RectangleGeometry Rect="0.1,0.1,0.8,0.4"
Transform="{StaticResource transform}"/>
</Path.Data>
</Path>
<Path Stretch="None" Stroke="Red" StrokeThickness="2">
<Path.Data>
<EllipseGeometry Center="0.6,0.5" RadiusX="0.3" RadiusY="0.3"
Transform="{StaticResource transform}"/>
</Path.Data>
</Path>
</Canvas>
<Slider x:Name="scaleSlider" HorizontalAlignment="Center" VerticalAlignment="Bottom"
Width="100" Minimum="100" Maximum="500"/>
</Grid>

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