Different fill behavior inside a Path GeometryGroup - wpf

Here is a very simplified version of my xaml :
<Path>
<Path.Data>
<GeometryGroup>
<EllipseGeometry/>
<EllipseGeometry/>
</GeometryGroup>
</Path.Data>
</Path>
I would like the first EllipseGeometry to be filled, but not the second one. But the Fill property is defined at the Path's level.
I could define two Paths each containing an EllipseGeometry but I want them to share the path's Stroke. The Path's stroke is going to be modified by a trigger so I can't use a StaticResource. I also don't want to have to duplicate the trigger.

You can define the stroke in a shared style and use two Paths:
<Canvas>
<Canvas.Resources>
<Style TargetType="Path">
<Style.Triggers>
<DataTrigger>
<Setter Property="Stroke" Value="AliceBlue" />
</DataTrigger>
</Style.Triggers>
</Style>
</Canvas.Resources>
<Path Fill="Green">
<Path.Data>
<EllipseGeometry />
</Path.Data>
</Path>
<Path Fill="Red">
<Path.Data>
<EllipseGeometry />
</Path.Data>
</Path>
</Canvas>
This way you can define the stroke in one place and the fills separately. Of course you don't have to use a canvas as the container.
Cheers,
Eric

Related

shared area of two shapes in wpf

I'm making an app that is a venn diagram and I don't know how to do the first step. There are to ellipses and I want to color the shared area of two ellipses.
The green area is the shared area I meant.
You can use intersect mode on a combinedgeometry to find the parts overlap:
<Grid>
<Grid.Background>
<VisualBrush Stretch="None">
<VisualBrush.Visual>
<Canvas>
<Path Fill="Yellow">
<Path.Data>
<EllipseGeometry Center="150,50" RadiusX="75" RadiusY="75" />
</Path.Data>
</Path>
<Path Fill="Yellow">
<Path.Data>
<EllipseGeometry Center="50,50" RadiusX="75" RadiusY="75" />
</Path.Data>
</Path>
<Path Fill="Green">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Intersect">
<CombinedGeometry.Geometry1>
<EllipseGeometry Center="150,50" RadiusX="75" RadiusY="75"/>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="50,50" RadiusX="75" RadiusY="75"/>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Grid.Background>
Position textblocks in the grid on top of the background shapes using rows and columns.
Or just put everything in a canvas rather than using a visualbrush.
Use Canvas.Left and Canvas.Top to position some textblocks on top of the ellipses.

Changing DrawingImage brush binded to DynamicResource

I have a DrawingImage object as a resource which uses brush color from DynamicResource like this:
<DrawingImage x:Key="img_building">
<DrawingImage.Drawing>
<DrawingGroup ClipGeometry="M0,0 V512 H512 V0 H0 Z">
<DrawingGroup Opacity="1">
<GeometryDrawing Geometry="F1 M512,512z M0,0z M256.5,17.2L512,119.4 512,153.8 477.6,153.8C477.6,158.4 475.7,162.2 472,165.9 468.3,169.6 463.6,170.5 459,170.5L53,170.5C48.4,170.5 43.7,168.6 40,165.9 36.3,163.1 34.4,158.5 34.4,153.8L0,153.8 0,119.4 256.5,17.2z M68.8,188.2L136.6,188.2 136.6,392.6 171,392.6 171,188.2 238.8,188.2 238.8,392.6 273.2,392.6 273.2,188.2 341,188.2 341,392.6 375.4,392.6 375.4,188.2 443.2,188.2 443.2,392.6 459,392.6C463.6,392.6 468.3,394.5 472,397.2 475.7,400 477.6,404.6 477.6,409.3L477.6,426 35.3,426 35.3,409.3C35.3,404.7 37.2,400.9 40.9,397.2 44.6,393.5 49.3,392.6 53.9,392.6L69.7,392.6 69.7,188.2 68.8,188.2z M493.4,443.7C498,443.7 502.7,445.6 506.4,448.3 510.1,452 512,455.7 512,460.4L512,494.8 0.9,494.8 0.9,460.4C0.9,455.8 2.8,452 6.5,448.3 10.2,444.6 14.9,443.7 19.5,443.7L493.4,443.7z" >
<GeometryDrawing.Brush>
<SolidColorBrush Color="{DynamicResource Image.FillColor}" />
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingGroup>
</DrawingImage.Drawing>
</DrawingImage>
And then I'm using this resource in a template like this:
<DataTemplate x:Key="buildingImage">
<WrapPanel>
<WrapPanel.Resources>
<Style TargetType="{x:Type Image}">
<Setter Property="Margin" Value="0,0,4,0"></Setter>
<Setter Property="MaxHeight" Value="20"></Setter>
</Style>
</WrapPanel.Resources>
<Image Source="{DynamicResource img_building}" />
</WrapPanel>
</DataTemplate>
It's running fine but when I change the theme of my app, image remains same although the Image.FillColor changes.
I guess it's rendering the image for once and then uses the same rendered resource over and over again.
How to fix this? Is there a way to force a hard reload on DrawingImage in code behind?

Why my UserControl using a Path as ControlTemplate is not properly clickable?

I created a class derived from UserControl, directly on code (without XAML), and defined a ControlTemplate in a ResourceDictionary. This control template is a pair of ellipses, as follows:
<ControlTemplate x:Key="MusclePositionControlTemplate" TargetType="{x:Type local:MusclePositionControl}">
<ControlTemplate.Resources>
<EllipseGeometry x:Key="bolinha" RadiusX="{StaticResource radius}" RadiusY="{StaticResource radius}">
<EllipseGeometry.Center>
<Point X="0" Y="{StaticResource distance}"/>
</EllipseGeometry.Center>
</EllipseGeometry>
</ControlTemplate.Resources>
<Path Fill="#9900ffff" StrokeThickness="1" Stroke="Black">
<Path.Data>
<GeometryGroup>
<StaticResource ResourceKey="bolinha"/>
<GeometryGroup>
<GeometryGroup.Transform>
<ScaleTransform ScaleY="-1"/>
</GeometryGroup.Transform>
<StaticResource ResourceKey="bolinha"/>
</GeometryGroup>
</GeometryGroup>
</Path.Data>
<Path.RenderTransform>
<TransformGroup>
<RotateTransform Angle="{Binding Angle, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
<TranslateTransform
X="{Binding X, RelativeSource={RelativeSource Mode=TemplatedParent}}"
Y="{Binding Y, RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
</ControlTemplate>
When I edit this template in Blend, I can select the path clicking on it, and it displays its outlines like this:
But when I add the actual control to a canvas, with its X, Y and Angle properties set to some value, the control renders itself fine, but its defining geometry keeps being that of a rectangle at the origin, and this affects hit testing (I cannot select at design time, or click at runtime, by clicking at the shape itself):
So my questions are:
How should I create a ControlTemplate using a Path as "root" visual element, so that the clickable area of the control is that Path?
Assuming a Path should not or could not be used as root visual for a ControlTemplate, how should I achieve the same end result?

Dependency property does not work within a geometry in a controltemplate

I have a DepencencyProperty (a boolean) that works fine on an Ellipse, but not on an ArcSegment.
Am I doing something that is not possible?
Here's part of the xaml. Both the TemplateBindings of Origin and LargeArc do not work in the geometry. But the LargeArc DependencyProperty does work in the Ellipse, so my DependencyProperty seems to be set up correctly.
<ControlTemplate TargetType="{x:Type nodes:TestCircle}">
<Canvas Background="AliceBlue">
<Ellipse Height="10" Width="10" Fill="Yellow" Visibility="{TemplateBinding LargeArc, Converter={StaticResource BoolToVisConverter}}"/>
<Path Canvas.Left="0" Canvas.Top="0" Stroke="Black" StrokeThickness="3">
<Path.Data>
<GeometryGroup>
<PathGeometry>
<PathFigure IsClosed="True" StartPoint="{TemplateBinding Origin}">
<LineSegment Point="150,100" />
<ArcSegment Point="140,150" IsLargeArc="{TemplateBinding LargeArc}" Size="50,50" SweepDirection="Clockwise"/>
</PathFigure>
</PathGeometry>
</GeometryGroup>
</Path.Data>
</Path>
</Canvas>
</ControlTemplate>
What I'm trying to build is a (sort of) pie-shaped usercontrol where the shape of the Pie is defined by DependencyProperties and the actual graphics used are in a template, so they can be replaced or customized.
In other words: I would like the code-behind to be visual-free (which, I assume, is good separation).
SOLUTION--------------------------(I'm not allowed to answer my own questions yet)
I found the answer myself, and this can be useful for others encountering the same issue. This is why the TemplateBinding on the Geometry failed:
A TemplateBinding will only work when binding a DependencyProperty to another DependencyProperty.
Following article set me on the right track: http://blogs.msdn.com/b/liviuc/archive/2009/12/14/wpf-templatebinding-vs-relativesource-templatedparent.aspx
The ArcSegment properties are no DependencyProperties.
Thus, the solution to the above problem is to replace
<ArcSegment Point="140,150" IsLargeArc="{TemplateBinding LargeArc}"
with
<ArcSegment Point="140,150" IsLargeArc="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=LargeArc}"
Colin, your working example where an 'ordinary' binding was used in the geometry set me on the right track.
BTW, love the infographics and the construction of your UserControl in your blogpost.
And, hey, that quick tip on code snippets, and especially on that DP attribute and the separation of those DPs into a partial class file is pure gold!
That should work. Whilst I cannot see an immediate issue, I just thought I would point out that I wrote an article which included a Silverlight implementation of a pie-piece shape:
Plotting Circular Relationship Graphs with Silverlight
The XAML for this shape is shown below:
<Style TargetType="local:NodeSegment">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:NodeSegment">
<Path Stroke="{TemplateBinding Stroke}"
StrokeThickness="{TemplateBinding StrokeThickness}"
Fill="{TemplateBinding Background}"
DataContext="{Binding ViewModel}"
x:Name="segmentShape">
<Path.Data>
<PathGeometry>
<PathFigure StartPoint="{Binding Path=S1}"
IsClosed="True">
<ArcSegment Point="{Binding Path=S2}"
SweepDirection="Counterclockwise"
IsLargeArc="{Binding Path=IsLargeArc}"
Size="{Binding Path=OuterSize}"/>
<LineSegment Point="{Binding Path=S3}"/>
<ArcSegment Point="{Binding Path=S4}"
SweepDirection="Clockwise"
IsLargeArc="{Binding Path=IsLargeArc}"
Size="{Binding Path=InnerSize}"/>
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>

Access inner element in the ControlTemplate

Here goes my code:
<ControlTemplate TargetType="{x:Type ToggleButton}">
<Border x:Name="Chrome">
<Grid x:Name="Arrow">
<Grid.Background>
<DrawingBrush Viewport="0,0,4,4" Viewbox="0,-0.4,16,16" ViewboxUnits="Absolute">
<DrawingBrush.Drawing>
<GeometryDrawing x:Name="ArrowDrawing">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathFigure StartPoint="1,1" IsClosed="True">
<LineSegment Point="2,2.45"/>
<LineSegment Point="3,1"/>
<LineSegment Point="2,1.75"/>
</PathFigure>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Grid.Background>
</Grid>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Brush" TargetName="ArrowDrawing" Value="{StaticResource DisabledForecolor}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
So when the trigger triggers (on compile time), I got the error:
Cannot find the Trigger target 'ArrowDrawing'. (The target must appear before any Setters, Triggers, or Conditions that use it.)
How do I actually access that GeometryDrawing named ArrowDrawing from the trigger?
Actually there is two ways to achieve this, one is using a binding:
Storyboard.Target="{Binding ElementName=ContentPopup}"
and the other one is to use a XAML internal reference:
Storyboard.Target="{x:Reference Name=ContentPopup}"
the same answer is here!
This post might help
There is a way to solve this, though,
by using data binding. With a
Binding, you can find your way out of
the brush to an element, and then the
Setter can target that element.

Resources