Convert Geometry/Path to Minilanguage String? - wpf

It's not too hard to track down how to programmatically convert path strings into path objects in WPF, but is there a built-in function to convert a geometry or path back to a string in the mini-language?

Edit: Looking at this just now i thought that there should be a class called GeometryConverter which should be able to do this, and indeed there is. Just create one of those and use ConvertToString on the geometry you want to convert.
You can use the XamlWriter class to output objects as XAML, geometry will automatically be reduced to the mini-language.
e.g. if this is your input:
<DrawingImage x:Name="segmentsDrawing">
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Pen>
<Pen Brush="Black" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathFigure StartPoint="100,100">
<PathFigure.Segments>
<LineSegment Point="100,0"/>
<ArcSegment Point="186.6,150" SweepDirection="Clockwise" Size="100,100"/>
<LineSegment Point="100,100"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Blue">
<GeometryDrawing.Pen>
<Pen Brush="Black"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathFigure StartPoint="100,100">
<PathFigure.Segments>
<LineSegment Point="186.6,150"/>
<ArcSegment Point="13.4,150" SweepDirection="Clockwise" Size="100,100"/>
<LineSegment Point="100,100"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="Green">
<GeometryDrawing.Pen>
<Pen Brush="Black"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathFigure StartPoint="100,100">
<PathFigure.Segments>
<LineSegment Point="13.4,150"/>
<ArcSegment Point="100,0" SweepDirection="Clockwise" Size="100,100"/>
<LineSegment Point="100,100"/>
</PathFigure.Segments>
</PathFigure>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
...and you serialize it...
XmlTextWriter writer = new XmlTextWriter(#"C:\Users\Public\Test.xml", new UTF8Encoding());
writer.Formatting = Formatting.Indented;
writer.Indentation = 1;
writer.IndentChar = '\t';
XamlWriter.Save(segmentsDrawing, writer);
...you get the following:
<DrawingImage xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="#FFFF0000">
<GeometryDrawing.Pen>
<Pen Brush="#FF000000" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="M100,100L100,0A100,100,0,0,1,186.6,150L100,100" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF0000FF">
<GeometryDrawing.Pen>
<Pen Brush="#FF000000" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="M100,100L186.6,150A100,100,0,0,1,13.4,150L100,100" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF008000">
<GeometryDrawing.Pen>
<Pen Brush="#FF000000" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry Figures="M100,100L13.4,150A100,100,0,0,1,100,0L100,100" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
All the PathGeometry is now in mini-language. If you want to use this right away in your application i suppose you could write it to a MemoryStream and get the data from it by creating a XmlDocument from it.

Related

DrawingBrush always rendered on CPU (not GPU)?

I'm struggling with some WPF performance issues. The Ants profiler and dotTrace both show that all the time is deep in the WPF internals. I have a number of DrawingBrush objects presently in use. The old WpfPerf.exe shows that my DrawingBrush objects are being rendered on the CPU instead of the GPU. Is there something I can do to change that? Below is an example of one. Why does it render CPU-side?
<DataTemplate DataType="mapViewModel:ObstacleVM" x:Key="ObstacleShapeTemplate">
<Path Stroke="{DynamicResource Mobius.UI.Resources.Colors.ObstacleShapeOutlineBrush}" StrokeThickness="{Binding WorldAndScreen.MetersPerPixel, Converter={StaticResource Multiplier}, ConverterParameter=1}" StrokeLineJoin="Bevel" StrokeEndLineCap="Square" StrokeStartLineCap="Flat">
<Path.Fill>
<DrawingBrush Stretch="Uniform" ViewportUnits="Absolute" TileMode="Tile">
<DrawingBrush.Transform>
<ScaleTransform ScaleY="{Binding WorldAndScreen.MetersPerPixel, Converter={StaticResource Multiplier}, ConverterParameter=5}" />
</DrawingBrush.Transform>
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="{DynamicResource Mobius.UI.Resources.Colors.ObstacleShapeFillBrush}">
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="1,0" />
<LineSegment Point="1,1" />
<LineSegment Point="0,1" />
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="{DynamicResource Mobius.UI.Resources.Colors.ObstacleShapeOutlineBrush}">
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="0,.33" />
<LineSegment Point="1,.33" />
<LineSegment Point="1,0" />
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Path.Fill>
<Path.Data>
<PathGeometry FillRule="Nonzero" Figures="{Binding Figures, FallbackValue={StaticResource DefaultFigures}}" />
</Path.Data>
</Path>
</DataTemplate>
After reading around further, I found some sources suggesting the use of VisualBrish instead. Indeed, I tried this and it seems to fix it (again, according to WpfPerf.exe).
<DataTemplate DataType="mapViewModel:ObstacleVM" x:Key="ObstacleShapeTemplate">
<Path Stroke="{DynamicResource Mobius.UI.Resources.Colors.ObstacleShapeOutlineBrush}" StrokeThickness="{Binding WorldAndScreen.MetersPerPixel, Converter={StaticResource Multiplier}, ConverterParameter=1}" StrokeLineJoin="Bevel" StrokeEndLineCap="Square" StrokeStartLineCap="Flat">
<Path.Fill>
<VisualBrush Stretch="Uniform" ViewportUnits="Absolute" TileMode="Tile">
<VisualBrush.Transform>
<ScaleTransform ScaleY="{Binding WorldAndScreen.MetersPerPixel, Converter={StaticResource Multiplier}, ConverterParameter=5}" />
</VisualBrush.Transform>
<VisualBrush.Visual>
<Image Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="{DynamicResource Mobius.UI.Resources.Colors.ObstacleShapeFillBrush}">
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="1,0" />
<LineSegment Point="1,1" />
<LineSegment Point="0,1" />
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="{DynamicResource Mobius.UI.Resources.Colors.ObstacleShapeOutlineBrush}">
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="0,.33" />
<LineSegment Point="1,.33" />
<LineSegment Point="1,0" />
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</VisualBrush.Visual>
</VisualBrush>
</Path.Fill>
<Path.Data>
<PathGeometry FillRule="Nonzero" Figures="{Binding Figures, FallbackValue={StaticResource DefaultFigures}}" />
</Path.Data>
</Path>
</DataTemplate>

Is there opportunity to combine rectangles to one rectangle?

I have some rectangles which their heights are same. But i filled them with different color. Can i combine them as result i get Rectangle? I can do it with RectangleGeometry but i need Rectangle type
How do you want to combine the colours?
Do you just want to specify 2 rectangular regions which overlap but use different colors with a level of transparency so that the colours blend together?
Or do you want the Rectangle subdivided and using different colours in different regions?
Is there a reason you need to keep it as a Rectangle?
Here's a way to keep it as a Rectangle but specify your 2 colours to combine/mix as the Fill:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Rectangle Width="100" Height="100">
<Rectangle.Fill>
<DrawingBrush Viewport="0,0,1,1" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Red" Opacity="1"/>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<SolidColorBrush Color="White" Opacity=".5"/>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Page>
Or this one for subdivided rectangles:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Rectangle Width="100" Height="100">
<Rectangle.Fill>
<DrawingBrush Viewport="0,0,1,1" TileMode="None">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,1,1" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Yellow"/>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0,0,0.5,0.5" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Red"/>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0.5,0.5,0.5,0.5" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Green"/>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<RectangleGeometry Rect="0.25,0.25,0.25,0.25" />
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<SolidColorBrush Color="Blue"/>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Rectangle.Fill>
</Rectangle>
</Grid>
</Page>
(move the brush described by the DrawingBrush into Resources if you intend to use it in multiple places....and/or create a new Style for Rectangles).
Rectangle is sealed so it can be overridden, and it's not a Control so you can't change a template.
You might want to consider doing your own "Shape", so that you can better encapsulate the enhanced behaviour of "your" Rectangle.
Here's an example to get your started.
http://www.codeproject.com/Articles/21449/WPF-PartiallyRoundedRectangle-Choose-Which-Corners

Create stripe brush in WPF

How can I create a brush (DrawingBrush) for stripes as shown in the blog:
http://blog.pixelingene.com/2008/09/quick-tip-to-get-a-striped-background/
I can't use it because it uses scale transform, which means if the UI element is small, the stripes are pretty much not visible or too close together.
I can't use image brush because I need to bind the colors.
Just use MappingMode="Absolute":
<LinearGradientBrush MappingMode="Absolute" x:Key="HatchBrush" StartPoint="0,0" EndPoint="4,4" SpreadMethod="Repeat">
<GradientStop Offset="0" Color="LightCoral"/>
<GradientStop Offset="0.75" Color="LightCoral"/>
<GradientStop Offset="0.75" Color="Gray"/>
<GradientStop Offset="1" Color="Gray"/>
</LinearGradientBrush>
Creates downward 45 degree angle stripe pattern.
Alter viewport to change size of stripes
<DrawingBrush Stretch="UniformToFill" ViewportUnits="Absolute" Viewport="0,0,10,10" TileMode="Tile">
<DrawingBrush.Drawing>
<DrawingGroup>
<DrawingGroup.Children>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="100,0"/>
<LineSegment Point="100,100"/>
<LineSegment Point="0,100"/>
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="#FF404040">
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="Nonzero">
<PathGeometry>
<PathFigure StartPoint="0,0">
<LineSegment Point="25,0"/>
<LineSegment Point="100,75"/>
<LineSegment Point="100,100"/>
<LineSegment Point="75,100"/>
<LineSegment Point="0,25"/>
<LineSegment Point="0,0"/>
</PathFigure>
<PathFigure StartPoint="75,0">
<LineSegment Point="100,25"/>
<LineSegment Point="100,0"/>
</PathFigure>
<PathFigure StartPoint="0,75">
<LineSegment Point="25,100"/>
<LineSegment Point="0,100"/>
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup.Children>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
Alternatively you could bind the scale transform to the height and width of the control using multibinding. Then with a converter, you alter the scale to the max of height or width, then the stripes will remain the same size.

Help with WPF Binding

I need to bind the custom dependency property to the Image elements inside a control.
Now the Label's Foreground binds very well to TextForeground, but not the GeometryDrawing inside the Image (the Image remains Transparent).
What is wrong?
<UserControl x:Class="MyStopControl"
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" Height="12" Width="24">
<Canvas >
<Image x:Name="Dot" Canvas.Left="0" Canvas.Top="0">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<DrawingGroup>
<GeometryDrawing>
<GeometryDrawing.Pen>
<Pen Brush="{Binding RelativeSource={x:Static RelativeSource.Self},Path=TextForeground}" Thickness="2" x:Name="BigCircleThickness"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry x:Name="BigCircle" Center="0,0" RadiusX="7" RadiusY="7"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing>
<GeometryDrawing.Pen>
<Pen Brush="{Binding RelativeSource={x:Static RelativeSource.Self},Path=TextForeground}" Thickness="1"/>
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry x:Name="MediumCircle" Center="0,0" RadiusX="4" RadiusY="4"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
<GeometryDrawing Brush="{Binding RelativeSource={x:Static RelativeSource.Self},Path=TextForeground}">
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry x:Name="SmallCircle" Center="0,0" RadiusX="2" RadiusY="2"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Border x:Name="StopShadow"
Background="{Binding ElementName=TextBackground}"
LayoutTransform="{Binding ElementName=StopText, Path=LayoutTransform}">
<Label x:Name="StopLabel"
Content="Bla bla some text"
Foreground="{Binding ElementName=TextForeground}" />
</Border>
</Canvas>
</UserControl>
GeometryDrawing does not have a TextForeground property. You are referencing Self, which would be the GeometryDrawing. Change your RelativeSource if you are trying to grab the TextForeground from a different Control.
<GeometryDrawing.Pen>
<Pen Brush="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type UserControl}}, Path=TextForeground}" Thickness="1"/>
</GeometryDrawing.Pen>
<UserControl x:Name="MyStopControl" >
...
<Pen Brush="{Binding ElementName=MyStopControl, Path=TextForeground}"/>
...
</UserControl>

How to fill an overlapped area when FillRule fails?

I'm trying to draw a hand-made DB drum alike shape. The problem is that the top ellipse is not completely filled.
Sample:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Image Width="126" Height="42" Margin="3" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Silver">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="1" LineJoin="Bevel" EndLineCap="Round" StartLineCap="Round" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="NonZero">
<EllipseGeometry Center="62,8" RadiusX="62" RadiusY="5" />
<PathGeometry>
<PathFigure StartPoint="0,8" IsClosed="False">
<LineSegment Point="0,38" />
<QuadraticBezierSegment Point1="60,49" Point2="124,38" />
<LineSegment Point="124,8" />
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
</Page>
Any alternatives to solve this issue?
Thanks.
Finally... the solution was to make two separate path figures using arc segments:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Image Width="126" Height="42" Margin="3" HorizontalAlignment="Left" VerticalAlignment="Top">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Silver">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="1" LineJoin="Bevel" EndLineCap="Round" StartLineCap="Round" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<GeometryGroup x:Key="Drum" FillRule="NonZero">
<PathGeometry>
<PathFigure StartPoint="0,6" IsClosed="True">
<ArcSegment Point="125,6" IsLargeArc="False" IsStroked="True" SweepDirection="Clockwise" Size="15,1.5" />
<ArcSegment Point="0,6" IsLargeArc="False" IsStroked="True" SweepDirection="Clockwise" Size="15,1.5" />
</PathFigure>
</PathGeometry>
<PathGeometry>
<PathFigure StartPoint="0,6" IsClosed="True">
<ArcSegment Point="125,6" IsLargeArc="False" IsStroked="True" Size="15,1.5" IsSmoothJoin="True" />
<LineSegment Point="125,35" IsSmoothJoin="True" />
<ArcSegment Point="0,35" IsLargeArc="False" IsStroked="True" SweepDirection="Clockwise" Size="15,1.5" IsSmoothJoin="True" />
<LineSegment Point="0,6" IsSmoothJoin="True" />
</PathFigure>
</PathGeometry>
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
</Page>

Resources