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

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?

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.

wpf xaml binding to translateTransform in Resource

I have a path defined as a resource in a resourceDictionary. In the code a rectangle with a transformgroup is defined. There are a few transforms to position the rectangle at a local datum then a translateTransform with a binding.
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<GeometryGroup x:Key="box">
<RectangleGeometry Rect="0,0,70,25"/>
<GeometryGroup.Transform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<TranslateTransform X="-37.5" Y="-12.5"/>
<TranslateTransform X="500" Y="0"/>
<RotateTransform Angle="0"/>
<TranslateTransform X="{Binding BX}" Y="{Binding BY}"/>
</TransformGroup>
</GeometryGroup.Transform>
</GeometryGroup>
</ResourceDictionary>
The viewmodel holds an observable collection of "box" objects. Each box object has a BX and BY property to define the position of the box. The xaml page displays those boxes.
There is a data template as shown here which points to the resource:
<DataTemplate DataType="{x:Type cfg:Box}">
<Path Fill="White" Stroke="Black" Data="{StaticResource box}"/>
</DataTemplate>
Currently the boxes are displayed on a canvas with the following code:
<ItemsControl ItemsSource="{Binding Boxes}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding Path=BX}"/>
<Setter Property="Canvas.Top" Value="{Binding Path=BY}"/>
</Style>
</ItemsControl.ItemContainerStyle>
</ItemsControl>
This does locate the shapes on the canvas by setting the Canvas.Left and Canvas.Top properties. But instead what I would like to do is bind the BX and BY values from each "box" object in the observablecollection to the translateTransform associated with each item that is bound to the itemssource. (Right now the binding in the resource's translatetransform fails.)
So if I have 5 boxes in my observablecollection in the view model, and each one has a unique position in a BX and BY property (with an option to rotate, scale, etc) - how do I bind those properties to the corresponding items in the ItemsControl?
I don't follow why binding the contentpresenter canvas.left and top is not suitable.
Any bindings on properties in your geometrygroup cannot work as you have it in a resource dictionary.
Geometrygroup is a freezable and will be frozen if you put it in a resource dictionary. There is a built in mechanism calls Freeze() on any and all freezables you use in a resource dictionary. That means those variables aren't going to change.
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.geometrygroup?view=windowsdesktop-7.0
Inheritance:
Object
DispatcherObject
DependencyObject
Freezable
Animatable
Geometry
GeometryGroup
You could put your geometrygroup in windows resources instead and it wouldn't be frozen.
The translatetransform has an Xproperty and Yproperty which I've animated in the past so I think the binding could work if there's a suitable BX and BY property in the datacontext of the resource.
You should be able to do something like:
<Style TargetType="ContentPresenter">
<Setter Property="RenderTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform x:Name="RotateTransform"
Angle="{Binding Angle}"
CenterX="{Binding CenterX}"
CenterY="{Binding CenterY}"/>
<ScaleTransform x:Name="MirrorTransform"
ScaleX="{Binding MirroringX}"
ScaleY="{Binding MirroringY}"
CenterX="{Binding CenterX}"
CenterY="{Binding CenterY}"/>
<ScaleTransform x:Name="ScaleTransform"
ScaleX="{Binding ScaleX}"
ScaleY="{Binding ScaleY}"/>
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</ItemsControl.ItemContainerStyle>
So long as you don't get your transform frozen by putting it in a resource dictionary.

Different fill behavior inside a Path GeometryGroup

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

Assistance designing a WPF control template

I am trying to design a template to be used with Bing Maps. I have started with the base template and torn a few bits out and added a few more bits, but I am making a bit of a mess of it!
What I am trying to acheive is a pushpin that has what appears to be a tooltip coming out of each side into which I can insert more information (though these are not actually tooltips; they are displayed permanantly based upon a binding property). Something like...
As you can see, I am not much of an artist and have not really gotten very far!
Can somebody help? What I would like is for the orange bars on each side of the pin to be able to contain text based upon a binding (and the bars should grow if required); whilst also being able to hide one or both bars depending on a binding (IsLeftTextVisible/IsRightTextVisible for example, the names do not matter I can change those later) or even if the bound field contains no text.
I would also really like for the orange bars to be a bit nicer; but if somebody can help me get to the point where I can display the text I (hopefully) should be able to take it from there.
Here is the code to produce what I have...
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:m="clr-namespace:Microsoft.Maps.MapControl.WPF;assembly=Microsoft.Maps.MapControl.WPF">
<ControlTemplate x:Key="PushpinTemplate"
TargetType="{x:Type m:Pushpin}">
<Grid Grid.Name="LayoutRoot"
FrameworkElement.Height="{TemplateBinding FrameworkElement.Height}"
FrameworkElement.Width="{TemplateBinding FrameworkElement.Width}">
<Canvas FrameworkElement.Height="35"
FrameworkElement.HorizontalAlignment="Left"
FrameworkElement.VerticalAlignment="Top"
FrameworkElement.Width="34">
<Path x:Name="CollectionTextPath"
Fill="{TemplateBinding Background}"
Data="M 0,20 H100 V-20 H-0"
Canvas.Left="20"
Stretch="Fill"
Width="100"
Height="18.905"
Canvas.Top="3.19"
RenderTransformOrigin="0.5,0.5" >
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<Path x:Name="DeliveryTextPath"
Fill="{TemplateBinding Background}"
Data="M 0,20 H100 V-20 H-0"
Canvas.Left="-81"
Stretch="Fill"
Width="100"
Height="18.905"
Canvas.Top="4.19"
RenderTransformOrigin="0.5,0.5" >
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="-1"/>
<SkewTransform/>
<RotateTransform/>
<TranslateTransform/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<Path x:Name="Path_0"
Stretch="Fill"
StrokeLineJoin="Round"
Stroke="#FF333333"
Fill="#FFFFFFFF"
Data="F1M13.25,0.50001502C20.2917,0.50001502 26,6.2083602 26,13.25 26,17.817347 23.598524,21.823648 19.989363,24.075348 18.67861,25.105957 17.863953,27.531982 17.863953,27.531982L13.21736,39.595642 8.6221838,27.528591C8.6221838,27.528591 7.8198605,25.084908 6.6245556,24.145586 2.952121,21.907652 0.5,17.865215 0.5,13.25 0.5,6.2083602 6.2083402,0.50001502 13.25,0.50001502z"
UseLayoutRounding="False"
Height="39.694"
Width="26"
Canvas.Left="8.282"
Canvas.Top="0.001" />
<Ellipse Height="21"
Width="21"
Fill="{TemplateBinding Background}"
Canvas.Top="2.434"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Canvas.Left="10.806"
Stroke="{x:Null}" />
<Ellipse Height="18.905"
Width="18.905"
Canvas.Top="3.19"
Canvas.Left="11.911"
Stroke="{x:Null}">
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0.5,1"
StartPoint="0.5,0">
<GradientStop Color="#00FFFFFF"
Offset="0.438" />
<GradientStop Color="#6EFFFFFF"
Offset="0.987" />
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<UIElement.RenderTransform>
<TransformGroup>
<TranslateTransform TranslateTransform.X="-4" />
</TransformGroup>
</UIElement.RenderTransform>
</Canvas>
<Grid FrameworkElement.HorizontalAlignment="Center"
FrameworkElement.VerticalAlignment="Top"
FrameworkElement.Margin="0,2,0,0"
FrameworkElement.Height="22"
FrameworkElement.Width="21">
<ContentPresenter ContentPresenter.Content="{TemplateBinding ContentControl.Content}"
FrameworkElement.HorizontalAlignment="Center"
FrameworkElement.VerticalAlignment="Center" />
</Grid>
</Grid>
</ControlTemplate>
<Style x:Key="{x:Type m:Pushpin}"
TargetType="{x:Type m:Pushpin}">
<Setter Property="Template"
Value="{StaticResource PushpinTemplate}" />
</Style>
</ResourceDictionary>
As you might gather, the canvas, grids and ellipses (the bits that look nice) are from the original template. I have then added the two Paths but I am now at a loss as to how to put text inside them (I could probably bind the text without problem if I had somewhere to put it).
I know this is a bit of an ask; but is there somebody who is a bit graphical and could move me forward a bit please. Any help would be appreciated.
I have extended the layout sufficiently.

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>

Resources