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>
Related
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?
I have implement Callout control of Blend. Problem which I am facing is anchor point of the Callout starts with some margin from the top, While I want to have anchor from the top left of the callout.
Any help will be appreciated.
What I have now:
What I would like to have:
You seem to be mistaken about using this control. From MSDN, the Callout.AnchorPoint property Gets or sets the position of the callout relative to the top and left corner. It is used for positioning the control and does not alter the shape of the Callout.
UPDATE >>>
Dude!!! That's a really simple shape... just draw your own one with a Path... then you can make it any shape you want:
<Path Stroke="Black" StrokeThickness="1" Fill="#CCCCFF">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<RectangleGeometry RadiusX="20" RadiusY="20" Rect="0,0,300,200">
<RectangleGeometry.Transform>
<TranslateTransform X="30" />
</RectangleGeometry.Transform>
</RectangleGeometry>
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<PathGeometry>
<PathFigure StartPoint="0,30">
<LineSegment Point="50,10" />
<LineSegment Point="50,50" />
</PathFigure>
</PathGeometry>
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
<Path.Effect>
<DropShadowEffect Color="Black" Opacity="0.4" Direction="-135"
ShadowDepth="10" />
</Path.Effect>
</Path>
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
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.
I want to draw a triangle as a border background. One way of doing this is by using a DrawingBrush, but at smaller sizes anti-aliasing is distorting the triangle and making it blurry. How can I disable anti-aliasing?
<Border>
<Border.Background>
<DrawingBrush>
<DrawingBrush.Drawing>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Geometry>
<PathGeometry>
<PathGeometry.Figures>
<PathFigureCollection>
<PathFigure IsClosed="True" StartPoint="0,3" IsFilled="True">
<PathFigure.Segments>
<LineSegment Point="3,0" />
<LineSegment Point="6,3" />
</PathFigure.Segments>
</PathFigure>
</PathFigureCollection>
</PathGeometry.Figures>
</PathGeometry>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.Background>
</Border>
I've tried setting RenderOptions.EdgeMode="Aliased" and SnapsToDevicePixels="true" on all possible elements, but that hasn't worked...
Edit:
This is what the drawn triangle looks like at Width=17; Height=12 (zoomed to 800%):
As you can see the edges are anti-aliased. All the usual options for disabling anti-aliasing don't seem to be working...
I also became very frustrated with this. After some research, I found the answer: Using a Visual Brush with the Image element in the visual set to SnapToDevicePixels = true and Aliased Edge Mode. See the example below:
<Canvas VerticalAlignment="Top" Height="12" Margin="0,24,0,0">
<Canvas.Background>
<VisualBrush TileMode="Tile" Stretch="None" Viewport="-5,0,10,10" ViewportUnits="Absolute">
<VisualBrush.Visual>
<Image Stretch="None" RenderOptions.EdgeMode="Aliased" SnapsToDevicePixels="True">
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing>
<GeometryDrawing.Pen>
<Pen Brush="#8F8E8F" Thickness="1" />
</GeometryDrawing.Pen>
<GeometryDrawing.Geometry>
<LineGeometry StartPoint="0,0" EndPoint="0,10"/>
</GeometryDrawing.Geometry>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</VisualBrush.Visual>
</VisualBrush>
</Canvas.Background>
</Canvas>
Considering there haven't been any more replies, then it seems that the answer is - it's impossible.
I think you should be able to do it with SnapToDevicePixels (although sometimes it takes a little trial and error). You should set it on the owning object (so whatever the border is being put in). Have you read this article?
http://msdn.microsoft.com/en-us/library/aa970908.aspx