Re-sizing WPF Geometry Path - wpf

I'm working on a WPF application. Given a geometry string path, such as:
F1 M 27,18L 23,26L 33,30L 24,38L 33,46L 23,50L 27,58L 45,58L 55,38L 45,18L 27,18 Z
Is it possible to scale the drawing to a width and height (no matter how small/large the original was) while keeping the figure as a whole, and then finally return the string path representation of the new scaled figure?

There is no need to scale the values in a path geometry string. Just put it in the Data property of a Path control and set its Width, Height and Stretch properties as needed:
<Path Data="F1 M27,18 L23,26 33,30 24,38 33,46 23,50 27,58 45,58 55,38 45,18 27,18 Z"
Width="100" Height="100" Stretch="Uniform" Fill="Black"/>

Yes you can do it! The only thing you need to do, is to use a Viewbox for wrapping the item. This is a sample with a code that I had done, in this case I used the geometry as a DrawingBrush
...
<UserControl.Resources>
<DrawingBrush x:Key="Field" Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="{StaticResource FieldGrassBrush}" Geometry="F1 M 91.733,119.946C 92.8352,122.738 93.9374,125.529 92.9241,129.209C 91.9107,132.889 88.7819,137.458 84.4263,139.271C 80.0707,141.084 74.4885,140.142 70.8885,137.982C 67.2885,135.822 65.6707,132.444 65.1819,129.182C 64.693,125.92 65.333,122.773 65.973,119.626L 0.16,53.9203C 0.444319,53.4758 0.728638,53.0312 3.48413,48.7023C 6.23962,44.3733 11.4663,36.16 18.5596,28C 25.653,19.84 34.613,11.7333
........
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</UserControl.Resources>
...
Then the View Box (Note that the Grid has fixed Height and Width, but it will be stretched to the Viewbox's size, in this case, in an uniform way):
...
<Viewbox Stretch="Uniform" Grid.Row="2" Grid.ColumnSpan="2">
<Grid Height="300" Width="390">
<Rectangle Fill="{DynamicResource Field}" StrokeThickness="0"/>
...

Related

Visually split Path into two side-by-side colors in WPF

If I have a rather meandering Path in my WPF app, is there a way I can make it appear as two differently-colored Paths of identical widths side-by-side? I'd rather not try to hand-code the whole thing again with slightly different values. I thought of using a Brush, but the list of Brushes doesn't appear to have one such.
Edit: I want a Path divided sharply by color, even if it curves, like this:
Made a little search, and found that also :
Two-color Path object
Timwi answer :
<StackPanel Orientation="Horizontal">
<StackPanel.LayoutTransform>
<ScaleTransform CenterX="0" CenterY="0" ScaleX="15" ScaleY="15" />
</StackPanel.LayoutTransform>
<Grid Margin="-5,0,0,0">
<Path Fill="Blue" Stroke="Transparent">
<Path.Data>
<PathGeometry>M10,10 C20,10 10,20 20,20 L20,19 C11,19 21,9 10,9</PathGeometry>
<!-- |← original path →| |← generated part →| -->
</Path.Data>
</Path>
<Path Fill="Red" Stroke="Transparent">
<Path.Data>
<PathGeometry>M10,10 C20,10 10,20 20,20 L20,21 C9,21 19,11 10,11</PathGeometry>
<!-- |← original path →| |← generated part →| -->
</Path.Data>
</Path>
</Grid>
</StackPanel>
So "playing" with margin may be much easier that the other options I told you about for what you need.
DropShadowEffect solved my issue.

How do I use the icon that is stored in a xaml file in WPF?

Sorry for my bad English. The icon is stored in a xaml file named Icon.xaml. And I add the file to the project as a resource.
<ResourceDictionary x:Class="resources_icons_xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Canvas x:Key="appbar_acorn" Width="48" Height="48" Clip="F1 M 0,0L 48,0L 48,48L 0,48L 0,0">
<Path Width="22.3248" Height="25.8518" Canvas.Left="13.6757" Canvas.Top="11.4012" Stretch="Fill" Fill="{DynamicResource BlackBrush}" Data="F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z "/>
</Canvas>
</ResourceDictionary >
Now,I wanna use the icon in a window's titlebar. How can I do this?
The Window.Icon is an ImageSource. So you should not use a Canvas here. Try changing it to some DrawingImage like this:
<DrawingImage x:Key="appbar_acorn">
<DrawingImage.Drawing>
<GeometryDrawing Brush="{DynamicResource BlackBrush}" Geometry="F1 M 16.6309,18.6563C 17.1309,8.15625 29.8809,14.1563 29.8809,14.1563C 30.8809,11.1563 34.1308,11.4063 34.1308,11.4063C 33.5,12 34.6309,13.1563 34.6309,13.1563C 32.1309,13.1562 31.1309,14.9062 31.1309,14.9062C 41.1309,23.9062 32.6309,27.9063 32.6309,27.9062C 24.6309,24.9063 21.1309,22.1562 16.6309,18.6563 Z M 16.6309,19.9063C 21.6309,24.1563 25.1309,26.1562 31.6309,28.6562C 31.6309,28.6562 26.3809,39.1562 18.3809,36.1563C 18.3809,36.1563 18,38 16.3809,36.9063C 15,36 16.3809,34.9063 16.3809,34.9063C 16.3809,34.9063 10.1309,30.9062 16.6309,19.9063 Z">
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
Then use it for your Window like this:
<Window ...
Icon="{StaticResource appbar_acorn}">
<!-- ... -->
</Window>
If you import the resource file inside the Window scope, you have to use DynamicResource instead:
<Window ...
Icon="{DynamicResource appbar_acorn}">
<!-- ... -->
</Window>

Drawing Viewbox using a DrawingContext

I'm writing a WPF application. All icons I use are vector and stored in standalone ResourceDictionary as series of Viewboxes:
<ResourceDictionary>
<ViewBox x:Shared="False" x:Key="IconKey" Stretch="Uniform">
<Canvas Width="16" Height="16">
<Path ... />
</Canvas>
</ViewBox>
<!-- ... -->
</ResourceDictionary>
I'm implementing now a user control, which, due to very specific requirements, is drawn from scratch by me in OnRender method.
I need to render these icons on my control. One solution is to rasterize them and then draw bitmaps, but the problem is, that view in my control can be freely zoomed, and such icons would look ugly when zoomed in (as opposed to vector ones). Is there a way to render a ViewBox using the DrawingContext?
I don't know how you can render ViewBox but you can render Geometry and that will not be ugly when zoomed.
I have created icon in a XAML file. Build Action: Page.
File:Icon.xaml
<?xml version="1.0" encoding="UTF-8"?>
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
<Canvas Width="25" Height="25">
<Path Fill="#FF000000" StrokeThickness="0.4">
<Path.Data>
<PathGeometry Figures="M 4.9586532 4.4020592 12.198751 19.198359 V 0.91721594 H 20.200561 h 2.894515 Z" FillRule="Nonzero"/>
</Path.Data>
</Path>
</Canvas>
</Viewbox>
To draw this with DrawingContext create method like this
void DrawIcon(DrawingContext context, double left, double top, double scale = 1)
{
var transform = new MatrixTransform(scale, 0, 0, scale, left, top);
var viewBox = (Viewbox) Application.LoadComponent(new Uri("/MyProjectNameSpace;component/Icon.xaml", UriKind.Relative));
var canvas = viewBox.Child as Canvas;
if (canvas?.Children == null) return;
foreach (UIElement child in canvas.Children)
if (child is Path path)
{
path.Data.Transform = transform;
context.DrawGeometry(Brushes.Black, new Pen(Brushes.Black, 1), path.Data);
}
}

How to remap colors in a DrawingBrush?

Say that I have a DrawingBrush that has three colors hard-coded, i.e. a border, a foreground, and a background.
<!-- Resource -->
<DrawingBrush x:Key="EventIcon" Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FF9200CE" Geometry="F1 M 51.2119,61.4688L 43.4193,61.4688L 43.4194,29.318L 27.8341,29.318L 27.834,61.4688L 20.0414,61.4688L 35.6267,77.1353L 51.2119,61.4688 Z "/>
<GeometryDrawing Brush="#FFB400FF" Geometry="F1 M 44.4789,64.2014L 40.2667,64.2667L 40.13,29.318L 27.8341,29.318L 27.834,61.4688L 20.0414,61.4688L 33.8667,75.1467L 44.4789,64.2014 Z "/>
<GeometryDrawing Geometry="F1 M 51.2119,61.4688L 43.4193,61.4688L 43.4194,29.318L 27.8341,29.318L 27.834,61.4688L 20.0414,61.4688L 35.6267,77.1353L 51.2119,61.4688 Z ">
<GeometryDrawing.Pen>
<Pen Thickness="2" StartLineCap="Round" EndLineCap="Round" LineJoin="Round" Brush="#FF3D0033"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
<GeometryDrawing Brush="#FFFFFFFF" Geometry="F1 M 33.7559,53.2538L 32.6202,40.9989L 32.6202,35.3362L 37.3531,35.3362L 37.3531,40.9989L 36.2333,53.2538L 33.7559,53.2538 Z M 32.6202,59.6771L 32.6202,54.9442L 37.3531,54.9442L 37.3531,59.6771L 32.6202,59.6771 Z "/>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
<!-- Usage -->
<Rectangle Width="16" Height="16" Fill="{StaticResource EventIcon}" />
Question
What would be the best approach to be able to change these colors from the parent Rectangle, yet still have a default fallback?
As I write this question, I have thought to two possible solutions...
Possible Solution #1
Using a RelativeSource binding to connect each to their equivalent property, e.g. {Binding Path=BorderBrush, RelativeSource={RelativeSource AncestorType={x:type Rectangle}} however:
Rectangle being a Shape does not have BorderBrush properties;
I could not provide a default value. Specifying FallbackValue in the binding won't work as the binding would resolve and take it's default value. (Edit: As I write this, I thinking that I could possibly use the NullValue property).
Possible Solution #2
Write an attached property that takes an array of colors/brushes and then have a converter to map it to the GeometryDrawing.Brush. Provide a default value using the Binding.IsNull property as I can guarantee a null value is return if it cannot map if the attached property is null or that color is not remapped.
I would go with a dynamic resource reference, define the defaults at the application level (Application.Resources) and change them locally by adding brushes with the same key in some control's resources.
You could create attached properties for each of the three colours, and then create a default style to give them default values.
You could then override these values in your Rectangle declaration if you wanted using normal attached property syntax.
The only other way I can think of is to have the colours as static resources, which you could recreate in your Rectangle's resource dictionary if you wanted to override.

WPF Path sizing problem

Hey guys, I've been playing around with WPF's Path shape, but I'm a bit annoyed with some behaviour.
Specifically, the path does not size itself as I would like. If you look at the image below, what I want is for the entire path to be within the white square (which represents the bounds of the Path control), but the arcs hang out a bit. I think this is because Path sizes itself according to the points used to draw the shape, and not according to the shape that is actually drawn.
My question is: does anyone know how to overcome this? I mean, aside from explicitly setting the dimensions of the path. Is there some option that I have overlooked in order to get the path to size itself according to the shape, and not according to the points used to make the shape? Thanks for any answers.
Here's two versions of (what should be) equivalent code:
1) First, using databindings (written out in a very verbose manner):
<UserControl x:Class="OrbitTrapWpf.LineSegmentTool"
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:OrbitTrapWpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Root" Background="White">
<UserControl.Resources>
<local:ArcSizeConverter x:Key="ArcSizeConverter"/>
<local:ArcPointConverter x:Key="ArcPointConverter"/>
</UserControl.Resources>
<Path Name="path" Stroke="Black">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True">
<PathFigure.StartPoint>
<Binding ElementName="Root" Path="point0"></Binding>
</PathFigure.StartPoint>
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment SweepDirection="Counterclockwise" >
<ArcSegment.Size>
<Binding ElementName="Root" Path="Radius" Converter="{StaticResource ArcSizeConverter}"/>
</ArcSegment.Size>
<ArcSegment.Point>
<Binding ElementName="Root" Path="point1" />
</ArcSegment.Point>
</ArcSegment>
<LineSegment>
<LineSegment.Point>
<Binding ElementName="Root" Path="point2" />
</LineSegment.Point>
</LineSegment>
<ArcSegment SweepDirection="Counterclockwise">
<ArcSegment.Size>
<Binding ElementName="Root" Path="Radius" Converter="{StaticResource ArcSizeConverter}"/>
</ArcSegment.Size>
<ArcSegment.Point>
<Binding ElementName="Root" Path="point3" />
</ArcSegment.Point>
</ArcSegment>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
2) And this one, using the mini-language:
<UserControl x:Class="OrbitTrapWpf.LineSegmentTool"
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:OrbitTrapWpf"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
x:Name="Root" Background="White">
<UserControl.Resources>
<local:ArcSizeConverter x:Key="ArcSizeConverter"/>
<local:ArcPointConverter x:Key="ArcPointConverter"/>
</UserControl.Resources>
<Grid Name="grid">
<Path Name="path" Stroke="Black" Data="M 0.146446609406726,1.14644660940673 A 0.5,0.5 0 1 0 0.853553390593274,1.85355339059327 L 1.85355339059327,0.853553390593274 A 0.5,0.5 0 1 0 1.14644660940673,0.146446609406726 Z " />
I thought that the two should be roughly the same, but apparently the mini-language version sizes nearly correctly, while the original is much different.
Basically, what your path xaml says, is:
Start at Point0, draw an arc to Point1.
From Point1, draw a line to Point2.
From Point2, draw an arc to Point 3.
'IsClosed' draws another line from Point3 to Point0.
What you've defined is exactly what is being produced - the only way you can change it is to change your positions - but the arc will still extend beyond Point0 on the X axis because that's what you have defined.
If you need your shape to fit entirely within some boundary, you can put a border around your shape, with margin of, say, 1/2 radius (I'm sure there is a formula for the exact protrusion) at the bottom and right.
Since the second screenshot looks different to the first, I would conclude that they are different shapes - which can only mean that the path data was translated incorrectly.
Okay, so I found the problem and solved it. I had set the IsLargeArc flag in the mini-language version, while in the purely XAML version I had left this as False. So I changed it to True, and I magically got the results I expected.
This seems to be a bug to me, because in this case the large and small arcs are one and the same, since I am drawing a half-arc. If anyone knows a reason for this behaviour, it would be awesome to hear about it!
I ran across this post and thought I would post an answer in case anyone is looking for a easy way to resize paths or Icons. The easiest way I have found is by using a Viewbox for all of my Path displays. This is because a path will scale itself nicely inside of a Viewbox. I use a Canvas to hold each path, the size of this Canvas is very important if you want to be able to use "Nice" numbers.
Here is a example of how to do this:
First (Optional) Draw your shape in a Vector Program like Inkscape or CorelDraw! I used CorelDraw to create the .svg File. Note: When using a program to create a vector make your page size something like 100 X 100 Pixels this is what you are going to set your Canvas Size to. If you are writing the path by hand this is also a very handy approach just pick a size like 100 X 100 and all of your path measurements are < 100 use that as your scale in other words.
Next use a translator program like Vector to Xaml Converter and generate a path. Save this into a Resource Dictionary or put it in the file where you need it. Put the Path inside a Canvas like so:
<Canvas x:Key="someName" Width="100" Height="100">
<Path Fill="#FF000000" Stroke="#FF373435" StrokeThickness="1" Data="M92,8L92,8C103,18,103,35,92,45L45,92C35,103,18,103,8,92L8,92C-3,82,-3,65,8,55L55,8C65,-3,82,-3,92,8z"/>
</Canvas>
Again note the size of the Canvas, this should match the dimensions of your "Drawing Board".
Then to use this just put a ContentControl inside of a ViewBox that has the Width and Height that you want the Path to display at like so:
<Viewbox x:Name="btnClose" Width="30" Height="30">
<ContentControl Content="{StaticResource someName}" />
</Viewbox>
Thats it! Another nice thing about using Paths is you can bind the Color of hte Background (Fill) or Foreground (Stroke). Taking our example here is how to control the Colors:
<SolidColorBrush x:Key="stForeColor" Color="#FFD4D7EA" />
<Canvas x:Key="someName" Width="100" Height="100">
<Path Fill="{StaticResource stForeColor}" Stroke="Transparent" StrokeThickness="1" Data="M92,8L92,8C103,18,103,35,92,45L45,92C35,103,18,103,8,92L8,92C-3,82,-3,65,8,55L55,8C65,-3,82,-3,92,8z"/>
</Canvas>
There are also tons of other things that you can do, anything that you can do with any other Shape, Effects, Animations etc.

Resources