xaml path data for arrow - wpf

I have trying to draw simple "up" arrow by this code:
<Canvas Width="500" Height="500">
<Path Height="120" Width="120" StrokeThickness="1" Stroke="Red" Data="M 60,60 L 60,0 L 50,10 L 60,0 L 70,10"/>
</Canvas>
And I don't see symmetric arrow on my screen.
I want to understand this "magic".

You have created a Path that is 120x120. Coordinate 0,0 is the upper-left corner.
M 60, 60 -> Move to the very center of the Path object x=60,y=60
L 60,0 -> Draw a Line from the last coordinate (60,60) to x=60,y=0 (straight up)
L 50,10 -> Draw a Line from the last coordinate (60,0) to x=50,y=10 (to the left 10 and down 10)
L 60, 0 -> Draw a Line from the last coordinate (50,10) to x=60, y=0 (retrace your line up and to the right by 10 each)
L 70,10 -> Draw a Line from the last coordinate (60,0) to x=70,y=10 ( to right 10 and down 10)
The reason that it is not symmetrical is because you are backtracking along the left arm of the arrow. This adds a join at that point, and essentially adds more to the line there because of your stroke thickness.
You can fix that like this:
<Path Height="120" Width="120"
StrokeThickness="1" Stroke="Red" Data="M 60,60 L 60,0 L 50,10 M60,0 L70,10"/>

Related

Expalanation about WPF concept in unleashed WPF 4.0 book

Can anyone expalin what does the point ( 0,0) refers to within the CompositionTarget_Rendering . I could understand that the cube rotates around the Y axis, but what I cannot understand how could it be that the 2D point 0,0 always relates to the same corner of the cube while it's rotating. In terms of objects, we have the big cube, the small cube, and, as per my undesrtanding, we have a button that is interactive but at the end of the day is USED as brush (not as an object in the 3D sense). I envision a space where a cube stands up and rotates around a vertical axis Y, but how the placement of the scaled down purple cube winds up with the same relative position with respect to the big cube, namely, one particular top corner of the big cube. The book says "the Point (0,0) from the Viewport2DVisual3D’s hosted Button
is mapped into 3D space, and a purple cube is drawn where that Point3D lies in 3D space"
and I am confused because the sentence implies that the button is situated somewhere on a 2d layer (supposedly behaind the 3D space) but the button is EVERYWHERE in that it is used to "paint" each of the side of the cubes. What is the best way to envision this 3D space in relation to the 2D layer and the button?
It will also be good to know why the purple small cube revolves around itself, as I could only see the that the trigger rotates the big cube, and the rendering event places the small cube at a fixed position WITHOUT rotating it.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid Name="myGrid">
<Viewport3D Panel.ZIndex="0">
<Viewport3D.Camera>
<PerspectiveCamera Position="3,3,4" LookDirection="-1,-1,-1" FieldOfView="60"/>
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<DirectionalLight Direction="-0.3,-0.4,-0.5" />
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D x:Name="Container">
<Viewport2DVisual3D >
<Viewport2DVisual3D.Transform>
<Transform3DGroup>
<TranslateTransform3D OffsetX="1.5" />
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="rotationY" Axis="0,1,0" Angle="0" />
</RotateTransform3D.Rotation>
</RotateTransform3D>
</Transform3DGroup>
</Viewport2DVisual3D.Transform>
<Viewport2DVisual3D.Geometry>
<MeshGeometry3D Positions="1,1,-1 1,-1,-1 -1,-1,-1 -1,1,-1 1,1,1 -1,1,1 -1,-1,1 1,-1,1 1,1,-1 1,1,1 1,-1,1 1,-1,-1 1,-1,-1 1,-1,1 -1,-1,1 -1,-1,-1 -1,-1,-1 -1,-1,1 -1,1,1 -1,1,-1 1,1,1 1,1,-1 -1,1,-1 -1,1,1"
TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7 8 9 10 8 10 11 12 13 14 12 14 15 16 17 18 16 18 19 20 21 22 20 22 23"
TextureCoordinates="0,1 0,0 1,0 1,1 1,1 -0,1 0,-0 1,0 1,1 -0,1 0,-0 1,0 1,0 1,1 -0,1 0,-0 -0,0 1,-0 1,1 0,1 1,-0 1,1 0,1 -0,0"/>
</Viewport2DVisual3D.Geometry>
<Viewport2DVisual3D.Material>
<DiffuseMaterial Viewport2DVisual3D.IsVisualHostMaterial="True"/>
</Viewport2DVisual3D.Material>
<Button Name="TestButton">
<Button.RenderTransform>
<ScaleTransform ScaleY="-1" />
</Button.RenderTransform>
Hello, 3D
</Button>
</Viewport2DVisual3D>
</ModelVisual3D>
<ModelUIElement3D>
<ModelUIElement3D.Transform>
<Transform3DGroup>
<ScaleTransform3D ScaleX="0.2" ScaleY="0.2" ScaleZ="0.2" />
<TranslateTransform3D x:Name="cube_translation" />
</Transform3DGroup>
</ModelUIElement3D.Transform>
<ModelUIElement3D.Model>
<GeometryModel3D>
<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<SolidColorBrush Color="Purple" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
<GeometryModel3D.Geometry>
<MeshGeometry3D
Positions="1,1,-1 1,-1,-1 -1,-1,-1 -1,1,-1 1,1,1 -1,1,1 -1,-1,1 1,-1,1 1,1,-1 1,1,1 1,-1,1 1,-1,-1 1,-1,-1 1,-1,1 -1,-1,1 -1,-1,-1 -1,-1,-1 -1,-1,1 -1,1,1 -1,1,-1 1,1,1 1,1,-1 -1,1,-1 -1,1,1"
TriangleIndices="0 1 2 0 2 3 4 5 6 4 6 7 8 9 10 8 10 11 12 13 14 12 14 15 16 17 18 16 18 19 20 21 22 20 22 23"
TextureCoordinates="0,1 0,0 1,0 1,1 1,1 -0,1 0,-0 1,0 1,1 -0,1 0,-0 1,0 1,0 1,1 -0,1 0,-0 -0,0 1,-0 1,1 0,1 1,-0 1,1 0,1 -0,0"/>
</GeometryModel3D.Geometry>
</GeometryModel3D>
</ModelUIElement3D.Model>
</ModelUIElement3D>
</Viewport3D.Children>
</Viewport3D>
</Grid>
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded" >
<BeginStoryboard>
<Storyboard Name="myStoryBoardY">
<DoubleAnimation
Storyboard.TargetName="rotationY"
Storyboard.TargetProperty="Angle"
From="0" To="360" Duration="0:0:12" RepeatBehavior="Forever"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
</Window>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
static TimeSpan lastRenderTime = new TimeSpan();
void CompositionTarget_Rendering(object sender, EventArgs e)
{
// Ensure we only do this once per frame
if (lastRenderTime == ((RenderingEventArgs)e).RenderingTime)
return;
lastRenderTime = ((RenderingEventArgs)e).RenderingTime;
GeneralTransform2DTo3D transform = TestButton.TransformToAncestor(Container);
Point3D point = transform.Transform(new Point(0, 0));
cube_translation.OffsetX = point.X;
cube_translation.OffsetY = point.Y;
cube_translation.OffsetZ = point.Z;
}
}
The (0,0) here refers to the 2D coordinates in the Button referential. It is the top left corner of the button. There 6 different visualisations of the same button here but it seems that WPF assumes that the real one is on the first triangle that contains this point of the button.
In this case it is actually the first triangle of the first face. This face is pointing to the negative Z direction (1,1,-1 1,-1,-1 -1,-1,-1 -1,1,-1).
To actually see this you can setup the camera to point to the positive direction so you can see this face of the cube:
<PerspectiveCamera Position="0,0,-10" LookDirection="0,0,1" FieldOfView="60"/>
Set the light to point in positive direction:
<DirectionalLight Direction="-0.3,-0.4,0.5" />
Remove the triger to avoid animation and replace (0,0) in with (27, 12) (it is about the center of the button).
You should see the red cube in the middle of the face pointing towards the camera (the neagtive Z face).
You can also try to remove the first triangle (remove just the first 3 triangle indices). In this case the red cube will move to the positive Z face.

Draw Cylinder Path in Expression Blend 4

I am trying to "draw" a simple cylinder path in Expression Blend 4, and I can't seem to get it quite right.
(1) I started out by adding two Ellipses and one Rectangle:
<Grid Background="Transparent">
<Ellipse Fill="Transparent" Height="13.25" Margin="352,0,352,227" Stroke="Black" VerticalAlignment="Bottom"/>
<Rectangle Fill="Transparent" Margin="352,216,352,233" Stroke="Black"/>
<Ellipse Fill="Transparent" Height="13.25" Margin="352,209.625,352,0" Stroke="Black" VerticalAlignment="Top"/>
</Grid>
(2) Next, I selected the bottom Ellipse and Rectangle, and performed a Combine -> Unite:
<Grid Background="Transparent">
<Path Data="M0.5,0.5 L47.5,0.5 47.5,47.375 47.5,47.5 47.493931,47.5 47.492325,47.533089 C47.170608,50.84277 36.775898,53.5 24,53.5 11.2241,53.5 0.82939076,50.84277 0.50767487,47.533089 L0.50606853,47.5 0.5,47.5 0.5,47.375 z" Fill="Transparent" Margin="352,216,352,227" Stretch="Fill" Stroke="Black"/>
<Ellipse Fill="Transparent" Height="13.25" Margin="352,209.625,352,0" Stroke="Black" VerticalAlignment="Top"/>
</Grid>
(3) Next, I selected the top Ellipse and the Path resulting from step 2 and then performed a Path -> Make Compound Path. Then with the direct selection tool, I removed the line cutting though the top Ellipse. It looks good until I try and apply a Fill="Green" to the Path.
<Grid Background="Transparent">
<Path Fill="Green" Data="M47.5,6.875 L47.5,53.75 47.5,53.875 47.493931,53.875 47.492325,53.908089 C47.170608,57.21777 36.775898,59.875 24,59.875 11.2241,59.875 0.82939076,57.21777 0.50767487,53.908089 L0.50606853,53.875 0.5,53.875 0.5,53.75 0.5,6.875 M47.5,6.625 C47.5,10.007744 36.978692,12.75 24,12.75 11.021308,12.75 0.5,10.007744 0.5,6.625 0.5,3.2422559 11.021308,0.5 24,0.5 36.978692,0.5 47.5,3.2422559 47.5,6.625 z" Margin="352,209.625,352,227" Stretch="Fill" Stroke="Black"/>
</Grid>
I've tried various operations, but I can't for the life of me figure out how to get a cylindrical Path where I can apply a Fill to the entire thing.
Don't know how to do that in Blend, but that figure could be made up of two arcs for (two halves of) the upper ellipse, one line down, one arc for the lower half ellipse, one line up and a final arc for half of the upper ellipse:
<Path Stroke="Black" Fill="LightGreen" Stretch="Fill" Data="
M0,0
A50,10 0 0 0 100,0
A50,10 0 0 0 0,0
M100,00 L100,100
A50,10 0 0 1 0,100
L0,0
A50,10 0 0 0 100,0" />
Is there a way to make it tilted tho? I am trying to shift this to give it an near-isometric look, but can't quite figure it out. Would the way to go be to have the circle shifted? So that the sides are at 10 and 4 or similar (as on a clock)?
Been working on this, but I either get an overlap in the bottom left, or some pixels outside of the circle.
<Path Stroke="Black" Fill="AliceBlue" Data="
M 0 0
A 45,40 0 0 0 100,0
A 45,40 0 0 0 0,0
L 15,-100
A 45,40 0 0 1 115,-100
L 100,0
A 45,40 0 0 0 0,0
"/>
EDIT: I more or less managed now, some minor tweaks and it should be there, the trick was to rotate the circles before adding the lines.
<Path Stroke="Black" Fill="AliceBlue" Data="
M 0 0
A 45,40 0 0 0 100,45
A 45,40 0 0 0 0,0
L 60,-100
A 45,40 0 0 1 160,-60
L 100,45
A 45,40 0 0 0 0,0
"/>

Combining multiple paths with different fills while still able to stretch uniformly

I'm trying to create a re-sizable drawing from a group of paths I've generated from Adobe Illustrator, but can't figure out how to make it re-sizable and maintain the fills. The original path data is:
<Path Fill="#ff221e1f" Data="F1 M 65.778,45.310 L 85.092,45.310 L 85.092,47.630 L 68.504,47.630 L 68.504,63.174 L 83.700,63.174 L 83.700,65.494 L 68.504,65.494 L 68.504,84.402 L 65.778,84.402 L 65.778,45.310 Z"/>
<Path Fill="#ff221e1f" Data="F1 M 95.300,64.218 L 101.505,64.218 C 107.538,64.218 111.482,60.854 111.482,55.692 C 111.482,49.718 107.074,47.282 100.926,47.282 C 98.257,47.282 96.286,47.572 95.300,47.804 L 95.300,64.218 Z M 92.574,45.832 C 94.952,45.310 98.316,45.020 100.984,45.020 C 106.087,45.020 109.278,46.122 111.540,48.268 C 113.222,49.892 114.266,52.502 114.266,55.228 C 114.266,60.506 111.133,63.870 106.610,65.436 L 106.610,65.552 C 109.742,66.538 111.713,69.496 112.642,73.846 C 113.976,79.936 114.730,82.836 115.542,84.402 L 112.642,84.402 C 112.062,83.242 111.192,79.820 110.148,74.890 C 108.988,69.264 106.551,66.712 101.390,66.480 L 95.300,66.480 L 95.300,84.402 L 92.574,84.402 L 92.574,45.832 Z"/>
<Path StrokeThickness="3.0" Stroke="#ff221e1f" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 179.125,90.312 C 179.125,139.362 139.362,179.125 90.312,179.125 C 41.263,179.125 1.500,139.362 1.500,90.312 C 1.500,41.263 41.263,1.500 90.312,1.500 C 139.362,1.500 179.125,41.263 179.125,90.312 Z"/>
<Path StrokeThickness="3.0" Stroke="#ff221e1f" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 75.533,117.798 L 103.986,117.798"/>
I've been able to make the drawing re-sizable without the fills by combining the path data into one path like this:
<Path Stretch="Uniform" StrokeThickness="1.5" Stroke="#ff221e1f" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round"
Data="F1 M 179.125,90.312 C 179.125,139.362 139.362,179.125 90.312,179.125 C 41.263,179.125 1.500,139.362 1.500,90.312 C 1.500,41.263 41.263,1.500 90.312,1.500 C 139.362,1.500 179.125,41.263 179.125,90.312 Z
M 75.533,117.798 L 103.986,117.798
M 65.778,45.310 L 85.092,45.310 L 85.092,47.630 L 68.504,47.630 L 68.504,63.174 L 83.700,63.174 L 83.700,65.494 L 68.504,65.494 L 68.504,84.402 L 65.778,84.402 L 65.778,45.310 Z
M 95.300,64.218 L 101.505,64.218 C 107.538,64.218 111.482,60.854 111.482,55.692 C 111.482,49.718 107.074,47.282 100.926,47.282 C 98.257,47.282 96.286,47.572 95.300,47.804 L 95.300,64.218 Z M 92.574,45.832 C 94.952,45.310 98.316,45.020 100.984,45.020 C 106.087,45.020 109.278,46.122 111.540,48.268 C 113.222,49.892 114.266,52.502 114.266,55.228 C 114.266,60.506 111.133,63.870 106.610,65.436 L 106.610,65.552 C 109.742,66.538 111.713,69.496 112.642,73.846 C 113.976,79.936 114.730,82.836 115.542,84.402 L 112.642,84.402 C 112.062,83.242 111.192,79.820 110.148,74.890 C 108.988,69.264 106.551,66.712 101.390,66.480 L 95.300,66.480 L 95.300,84.402 L 92.574,84.402 L 92.574,45.832 Z"/>
Image with combined paths - no fill
How can I combine these paths in a way that it will still be stretchable and maintain the fills?
EDIT - When I place the paths into a Grid & Viewbox the filled parts (letters) fill the entire grid instead maintaining their size as they do when all the paths are combined
Image with paths in Viewbox & Grid
Thanks
Group your 4 Paths into a Grid then put that Grid into a Viewbox that should scale the way you want.
<Viewbox Stretch="Fill" HorizontalAlignment="Left" Height="125.96" VerticalAlignment="Top" Width="127">
<Grid>
<Path Fill="#ff221e1f" Data="F1 M 65.778,45.310 L 85.092,45.310 L 85.092,47.630 L 68.504,47.630 L 68.504,63.174 L 83.700,63.174 L 83.700,65.494 L 68.504,65.494 L 68.504,84.402 L 65.778,84.402 L 65.778,45.310 Z"/>
<Path Fill="#ff221e1f" Data="F1 M 95.300,64.218 L 101.505,64.218 C 107.538,64.218 111.482,60.854 111.482,55.692 C 111.482,49.718 107.074,47.282 100.926,47.282 C 98.257,47.282 96.286,47.572 95.300,47.804 L 95.300,64.218 Z M 92.574,45.832 C 94.952,45.310 98.316,45.020 100.984,45.020 C 106.087,45.020 109.278,46.122 111.540,48.268 C 113.222,49.892 114.266,52.502 114.266,55.228 C 114.266,60.506 111.133,63.870 106.610,65.436 L 106.610,65.552 C 109.742,66.538 111.713,69.496 112.642,73.846 C 113.976,79.936 114.730,82.836 115.542,84.402 L 112.642,84.402 C 112.062,83.242 111.192,79.820 110.148,74.890 C 108.988,69.264 106.551,66.712 101.390,66.480 L 95.300,66.480 L 95.300,84.402 L 92.574,84.402 L 92.574,45.832 Z"/>
<Path StrokeThickness="3.0" Stroke="#ff221e1f" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 179.125,90.312 C 179.125,139.362 139.362,179.125 90.312,179.125 C 41.263,179.125 1.500,139.362 1.500,90.312 C 1.500,41.263 41.263,1.500 90.312,1.500 C 139.362,1.500 179.125,41.263 179.125,90.312 Z"/>
<Path StrokeThickness="3.0" Stroke="#ff221e1f" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeLineJoin="Round" Data="F1 M 75.533,117.798 L 103.986,117.798"/>
</Grid>
</Viewbox>
You might want to size the Grid down closer to your paths' real size before putting it in a Viewbox.

WPF: is it possible to render a circle using GeometryDrawing?

I've got a GeometryDrawing similar like this:
<DrawingImage x:Key="{ComponentResourceKey TypeInTargetAssembly={x:Type wpfhlp:FokusGroupBox},ResourceId=IconTest}">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Black" Geometry="M0,260 L0,600 L110,670 L110,500 L190,550 L190,710 L300,775 L300,430 L150,175"/>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
Now I'd like to draw a circle instead, but I only can find commands to move, draw a line, nothing to draw a circle.
Is there some way to get the GeometryDrawing to draw a circle?
....
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<EllipseGeometry Center="0,0" RadiusX="1" RadiusY="1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
Alternatively, you can also use Path Markup Syntax to draw two elliptic arcs (upper and lower half of the circle):
<GeometryDrawing Brush="Black" Geometry="M -1,0 A 1,1 0 1 1 1,0 M -1,0 A 1,1 0 1 0 1,0" />
In case you're wondering, this is what those cryptic drawing instructions mean:
M -1,0: Start drawing at point (-1, 0).
A 1,1 0 1 1 1,0: Draw an elliptic arc with radius (1, 1) to point (1, 0). The single-digit flags in between mean: no rotation (0), angle 180° or larger (1), positive-angle direction (1, meaning clockwise). This yields the upper half of the circle.
M -1,0: Start drawing again at point (-1, 0)
A 1,1 0 1 0 1,0: Draw the same arc as before, only this time in negative-angle direction (0, meaning counter-clockwise).
<Path Stretch="Fill"
Fill="Transparent"
Stroke="Black"
StrokeThickness="5"
Data="M 0,0 A 180,180 180 1 1 1,1 Z"/>
Without second M (move) command:
Data="M0,5A5,5,0,1,0,10,5A5,5,0,1,0,0,5"
Radius: 5, Start (0,5)
Can omit the "z" at the end, and it is still a closed curve

How can I use SnapToDevicePixels and StrokeDashArray

I have the following XAML code:
<Path Data="M0,0 L 12 0 L 12 12 L 0 12 Z M 6 0 L 6 12 M 0 6 L 12 6" StrokeDashArray="1 1" Stroke="Black" StrokeThickness="1" SnapsToDevicePixels='True">
</Path>
However it looks horribly fuzzy on my screen.
Is there a solution?
If you just want a crisp, dashed path and you may be able to get the results you want by declaring the RenderOptions.EdgeMode as Aliased to prevent the framework from interpolating the path outline when a dash falls between pixel boundaries:
<Path Data="M0,0 L 12 0 L 12 12 L 0 12 Z M 6 0 L 6 12 M 0 6 L 12 6" StrokeDashArray="1 1" Stroke="Black" StrokeThickness="1" RenderOptions.EdgeMode="Aliased">
</Path>

Resources