WPF Multiple CombinedGeometry - wpf

I wish to bind a collection of viewmodels with X,Y,Radius properties to a CombinedGeometry of circles using Union. However it seems that CombinedGeometry only supports 2 Geometries.
Is there anyway around this limitation?
Example of what I'm aiming for
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Union" ItemsSource="{Binding Circles}">
<CombinedGeometry.Template>
<EllipseGeometry RadiusX="{Binding Radius}" RadiusY="{Binding Radius}" CenterX="{Binding X}" CenterY="{Binding Y}"/>
</CombinedGeometry.Template>
</CombinedGeometry>
</Path.Data>
</Path>
It is indeed possible to have CombinedGeometries within CombinedGeometry as seen below. However, I don't know how to set it up so that it is easily bindable.
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<!-- Combines two geometries using the union combine mode. -->
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<EllipseGeometry RadiusX="50" RadiusY="50" Center="200,200" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry RadiusX="50" RadiusY="50" Center="125,200" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<EllipseGeometry RadiusX="50" RadiusY="50" Center="100,100" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry RadiusX="50" RadiusY="50" Center="150,120" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>

Are you looking for GeometryGroup ?
MSDN Code Sample :
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<!-- Creates a composite shape from three geometries. -->
<GeometryGroup FillRule="EvenOdd">
<LineGeometry StartPoint="10,10" EndPoint="50,30" />
<EllipseGeometry Center="40,70" RadiusX="30" RadiusY="30" />
<RectangleGeometry Rect="30,55 100 30" />
</GeometryGroup>
</Path.Data>
</Path>

This code produces the same result as the second code by the OP:
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<!-- any geometry here -->
<GeometryGroup/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<GeometryGroup FillRule="Nonzero">
<EllipseGeometry RadiusX="50" RadiusY="50" Center="200,200" />
<EllipseGeometry RadiusX="50" RadiusY="50" Center="125,200" />
<EllipseGeometry RadiusX="50" RadiusY="50" Center="100,100" />
<EllipseGeometry RadiusX="50" RadiusY="50" Center="150,120" />
</GeometryGroup>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
And instead of all the <EllipseGeometry>, you can bind to a collection via Children="{Binding Circles}" in <GeometryGroup>.

I have kind of a similar problem and found a hepfull post here:
How to make the border trim the child elements?
You could also try to create a converter that receives the collection in that binding and builds up the combined geometries as desired.
I'll do exactly that now :)

Related

How do I scale an arcsegment in XAML without using Viewbox

I would like to scale an arcsegment similarly to how the linesegment is scaled. I don't want to use Viewbox since this will increase/decrease the line thickness as the window is resized. In my example I have a line segment that is scaled appropriately and I'd like to scale the arc segment similarly. How can this be done in XAML?
<UserControl x:Class="TMUI.UserControls.Chart"
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:TMUI"
xmlns:c="clr-namespace:TMUI.Converters"
xmlns:bc="clr-namespace:TMUI.BindingConverters"
xmlns:vm="clr-namespace:TMUI.ViewModels"
mc:Ignorable="d"
Visibility="{Binding BBMainMenuVisibility}" >
<Grid x:Name="grid" Background="Black">
<Grid.Resources>
<ScaleTransform x:Key="transform"
ScaleX="{Binding ActualWidth, ElementName=grid}"
ScaleY="{Binding ActualHeight, ElementName=grid}"/>
</Grid.Resources>
<Path Stroke="White" StrokeThickness="1">
<Path.Data>
<LineGeometry StartPoint="0.01,0.01" EndPoint="0.99,0.99"
Transform="{StaticResource transform}"/>
</Path.Data>
</Path>
<Path Stroke="White" StrokeThickness="1">
<Path.Data>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="10,20">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="40,30" RotationAngle="45" IsLargeArc="True" SweepDirection="CounterClockwise" Point="100,100"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
</Grid>
You would scale the PathGeometry in the same way you scaled the LineGeometry, i.e. by assigning a ScaleTransform to its Transform property.
When you use the same ScaleTransform as for the LineGeometry, you would also need to use the same coordinate range from 0 to 1.
<Path Stroke="White" StrokeThickness="1">
<Path.Data>
<PathGeometry Transform="{StaticResource transform}">
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure StartPoint="0.1,0.2">
<PathFigure.Segments>
<PathSegmentCollection>
<ArcSegment Size="0.4,0.3" Point="1,1"
RotationAngle="45" IsLargeArc="True"
SweepDirection="CounterClockwise"/>
</PathSegmentCollection>
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</Path.Data>
</Path>
You may also draw a single Path with multiple geometries in a GeometryGroup:
<Path Stroke="White" StrokeThickness="1">
<Path.Data>
<GeometryGroup Transform="{StaticResource transform}">
<LineGeometry StartPoint="0.01,0.01" EndPoint="0.99,0.99"/>
<PathGeometry>
<PathGeometry.Figures>
...
</PathGeometry.Figures>
</PathGeometry>
</GeometryGroup>
</Path.Data>
</Path>

Why is WPF PathFigure not filled at certain rotations?

The arrow is supposed to have a red fill but the fill is missing at certain angles of the RotateTransform. It vanishes from ~92° to ~279°
In my full project it also occured that only a part of the arrow was filled. Is this a WPF rendering bug or am I doing something wrong here?
<StackPanel Orientation="Vertical">
<Slider x:Name="slider"
Value="180"
Minimum="0"
Maximum="360" />
<Canvas Width="296"
Height="296">
<Canvas.Background>
<DrawingBrush Stretch="Uniform">
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Pen>
<Pen Brush="Lime"
Thickness="2" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Transform>
<TransformGroup>
<RotateTransform Angle="{Binding ElementName=slider, Path=Value}" />
</TransformGroup>
</PathGeometry.Transform>
<PathGeometry.Figures>
<PathFigure IsClosed="True"
StartPoint="100 50">
<LineSegment Point="50 87.5" />
<LineSegment Point="50 62.5" />
<LineSegment Point=" 0 62.5" />
<LineSegment Point=" 0 37.5" />
<LineSegment Point="50 37.5" />
<LineSegment Point="50 12.5" />
</PathFigure>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Canvas.Background>
</Canvas>
</StackPanel>
You could simply rotate a Path by its RenderTransform:
<Path Width="296" Height="296" Stretch="Uniform"
Fill="Red" Stroke="Lime" StrokeThickness="8"
Data="M100,50 L50,87.5 50,62.5 0,62.5 0,37.5 50,37.5 50,12.5Z"
RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<RotateTransform Angle="{Binding ElementName=slider, Path=Value}" />
</Path.RenderTransform>
</Path>

VisualBrush of geometry DrawingImage is blurry and cut off

I would like to draw circular geometry in VisualBrush (in order to create an OpacityMask), the result however are of rather low quality:
This image is 500% zoomed in but the cut off of the circle is apparent (especially on top and bottom) and even at original size the mask is rather blurry. The image was generated with following code:
<Border Background="Blue">
<Border.OpacityMask>
<VisualBrush TileMode="None" Stretch="Uniform" AlignmentX="Center" AlignmentY="Center">
<VisualBrush.Visual>
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<EllipseGeometry Center="0,0" RadiusX="1" RadiusY="1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</VisualBrush.Visual>
</VisualBrush>
</Border.OpacityMask>
</Border>
How can I fix the mask so that is neither blurry nor cut off?
This may be better. At least it is simpler.
<Border Background="Blue">
<Border.OpacityMask>
<DrawingBrush Stretch="Uniform" AlignmentX="Center" AlignmentY="Center">
<DrawingBrush.Drawing>
<GeometryDrawing Brush="Black">
<GeometryDrawing.Geometry>
<EllipseGeometry RadiusX="1" RadiusY="1" />
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.OpacityMask>
</Border>

EllipseGeometry is not rendering in Silverlight

I'm trying to draw a circle in WP7 Silverlight using EllipseGeometry instead of Ellipse. The sample XAML in MSDN does not display anything on the canvas in Visual Studio. If I run the app, it does display in the emulator.
<Canvas>
<Path Fill="Gold" Stroke="Black" StrokeThickness="1">
<Path.Data>
<EllipseGeometry Center="50,50" RadiusX="50" RadiusY="50" />
</Path.Data>
</Path>
</Canvas>
Any ideas what is happening?
For some unclear reasons, you should assign Width and Height.
<Canvas>
<Path Width="100" Height="100" Fill="Gold" Stroke="Black" StrokeThickness="1">
<Path.Data>
<EllipseGeometry Center="50,50" RadiusX="50" RadiusY="50" />
</Path.Data>
</Path>
</Canvas>
In blend, you will not have this issue.

Painting a different brush over the intesection of two separate shape objects

Is there a way in WPF when two shape objects overlap each other that the overlapping portions of the object get painted in a different brush?
Worked it out.
You can use a geometry drawing containing a GeometryGroup with a fill rule of EvenOdd. This paints any overlapping items in white. Then just put another image over the top with CombinedGeometry containing the same objects as the Geometry group with a GeometryCombineMode of Intersect and that will highlight the intersect in your custom brush. The sample code is below:
<Grid>
<Image Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness="3" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<GeometryGroup FillRule="EvenOdd">
<EllipseGeometry RadiusX="80" RadiusY="80" Center="0,0" />
<EllipseGeometry RadiusX="80" RadiusY="80" Center="40,0" />
</GeometryGroup>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Image HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Stretch="None">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing Brush="LightBlue">
<GeometryDrawing.Geometry>
<CombinedGeometry GeometryCombineMode="Intersect">
<CombinedGeometry.Geometry1>
<EllipseGeometry RadiusX="80" RadiusY="80" Center="0,0" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry RadiusX="80" RadiusY="80" Center="40,0" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Grid>
Thanks!

Resources