How to clip a line to an ellipse in XAML - wpf

I have defined two concentric circles and two diagonal lines, which I want to clip to one of the circles.
I have written the following XAML and my problem is that I do not understand its behavior.
<Canvas>
<Ellipse x:Name="OuterCircle" Width="200" Height="200" StrokeThickness="2" Stroke="Black">
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFCBCFD5" Offset="1"/>
<GradientStop Color="#FFF5F6F7" Offset="0"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<Ellipse x:Name="InnerCircle" Width="184" Height="184" Margin="8" >
<Ellipse.Fill>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFCBCFD5" Offset="0"/>
<GradientStop Color="#FFF5F6F7" Offset="1"/>
</LinearGradientBrush>
</Ellipse.Fill>
</Ellipse>
<!-- In my opinion the lines are clipped to the wrong circle -->
<Line X1="0" X2="200" Y1="0" Y2="200" Stroke="Black" StrokeThickness="2">
<Line.OpacityMask>
<VisualBrush Visual="{Binding ElementName=InnerCircle}" />
</Line.OpacityMask>
</Line>
<Line X1="0" X2="200" Y1="200" Y2="2" Stroke="Black" StrokeThickness="2">
<Line.OpacityMask>
<VisualBrush Visual="{Binding ElementName=InnerCircle}" />
</Line.OpacityMask>
</Line>
</Canvas>
</Grid>
</Window>
This appears as follows:
The diagonal lines are clearly clipped to the outer circle.
In the XAML they are specifically clipped to the inner circle using ElementName=InnerCircle.
Why is that?
UPDATE
Using Stretch="None" as suggested by Clemens
<Line X1="0" X2="200" Y1="0" Y2="200" Stroke="Black" StrokeThickness="2">
<Line.OpacityMask>
<VisualBrush Visual="{Binding ElementName=InnerCircle}" Stretch="None"/>
</Line.OpacityMask>
</Line>
<Line X1="0" X2="200" Y1="200" Y2="2" Stroke="Black" StrokeThickness="2">
<Line.OpacityMask>
<VisualBrush Visual="{Binding ElementName=InnerCircle}" Stretch="None"/>
</Line.OpacityMask>
</Line>
produces exactly the result I had expected.
Using Clip did not work as I expected
<Line X1="0" X2="200" Y1="0" Y2="200" Stroke="Black" StrokeThickness="2" Clip="{Binding ElementName=InnerCircle, Path=RenderedGeometry}"/>
<Line X1="0" X2="200" Y1="200" Y2="2" Stroke="Black" StrokeThickness="2" Clip="{Binding ElementName=InnerCircle, Path=RenderedGeometry}"/>
In the designer, it looked like this
Clearly the margin on the inner circle has not been accounted for. (If I set the margin to 0, the circles are not concentric, but the clipping is correct.)
Weirdly, when I start the application, the diagonal lines do not appear at all.

Brushes are stretched by default. Set Stretch to None:
<VisualBrush Visual="{Binding ElementName=InnerCircle}" Stretch="None"/>
Alternatively, you can also clip the Lines by the geometry of the Ellipse:
<Line ... Clip="{Binding ElementName=InnerCircle, Path=RenderedGeometry}"/>

Related

StackPanel with rounded and degraded background

Im trying to create a stackpanel with a rounded background, that also has a linearbrush from grey to trasparent
I used the ideas exposed here to prevent clipping
http://chriscavanagh.wordpress.com/2008/10/03/wpf-easy-rounded-corners-for-anything/
The problem now is that the text inside the stackpanel also has degradation and then turns invisible
any help?
Similar question
How do I create a WPF Rounded Corner container?
Code:
<Border Margin="235,78,0,0" VerticalAlignment="Top" HorizontalAlignment="Left"
BorderBrush="Red" BorderThickness="1" CornerRadius="8" >
<Grid>
<Border Name="mask" CornerRadius="7">
<Border.Background>
<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">
<GradientStop Color="Gray" Offset="0"/>
<GradientStop Color="Transparent" Offset="1"/>
</LinearGradientBrush>
</Border.Background>
</Border>
<StackPanel Orientation="Horizontal" >
<StackPanel.OpacityMask>
<VisualBrush Visual="{Binding ElementName=mask}"/>
</StackPanel.OpacityMask>
<Image Height="16" Width="16" RenderOptions.BitmapScalingMode="NearestNeighbor" />
<TextBlock Foreground="Black" Margin="5,0,3,0" Text="00620"/>
<TextBlock Foreground="Black" Margin="5,0,3,0" Text="Error sincronización" />
</StackPanel>
</Grid>
</Border>
This is due to the OpacityMask, try to remove these lines from your XAML:
<StackPanel.OpacityMask>
<VisualBrush Visual="{Binding ElementName=mask}"/>
</StackPanel.OpacityMask>
And it should work

Creating Button using GeometryDrawing WPF

Hi I am trying to create a Button with a semi arc form something like this:
alt text http://www.freeimagehosting.net/uploads/6f804323db.jpg
I am using xaml and Control templates, it works fine but, the button recives the click event even if i click in any part into the rectangle form by the geometry, it launch the click event, I want the event to be catch only inside the geomtry...
here is the xaml
<ControlTemplate x:Key="ButtonTemplate" TargetType="{x:Type Button}">
<Grid>
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing x:Name="X"Geometry= "M 0,0
A .8,.8 180 1 1 0,4
L 0,3
A .6,.6 180 1 0 0,1
L 0,0">
<GeometryDrawing.Pen>
<Pen Brush="Black" Thickness=".1" />
</GeometryDrawing.Pen>
<GeometryDrawing.Brush>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
</LinearGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
<Viewbox>
<ContentControl Margin="20" Content="{TemplateBinding Content}"/>
</Viewbox>
</Grid>
</ControlTemplate>
...
<Button Template="{StaticResource ButtonTemplate}" Click="Button_Click" HorizontalAlignment="Right">OK</Button>
Thanks for any Comments
Instead of using an Image, which will have rectangular bounds that are used for hit testing, you can use a Path element with your Geometry data. The Path will only do hit testing on the area defined by the outline. Whatever text or other content is set will also be clickable unless you set IsHitTestVisible="false" on the ContentPresenter.
<Button Content="OK">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Grid>
<Path Data="M 0,0
A .8,.8 180 1 1 0,4
L 0,3
A .6,.6 180 1 0 0,1
L 0,0" Stroke="Black" StrokeThickness="1" Stretch="Uniform">
<Path.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0" Color="Blue"/>
<GradientStop Offset="1" Color="Red"/>
</LinearGradientBrush>
</Path.Fill>
</Path>
<Viewbox>
<ContentPresenter Margin="20" />
</Viewbox>
</Grid>
</ControlTemplate>
</Button.Template>
</Button>

WPF Rectangle filled with horizontal or vertical lines

I am trying to create one rectangle filled with Horizontal or Vertical lines.
The width of the rectangle is dynamic so I can not use an image brush.
Please let me know if anybody knows any solution.
I figured out a straight-forward way of doing this; finally, I used following visual brush resources to fill rectangle with horizontal, vertical or dotted vertical lines respectively
<!--for horizontal lines-->
<VisualBrush
x:Key="HorizontalLines"
TileMode="Tile" Viewport="0,0,4,4"
ViewportUnits="Absolute" Viewbox="0,0,10,10"
ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Canvas>
<Path Stroke="Black" Data="M 0 10 l 10 0" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
<!--For vertical lines-->
<VisualBrush
x:Key="VerticalLines"
TileMode="Tile" Viewport="0,0,4,4"
ViewportUnits="Absolute" Viewbox="0,0,10,10"
ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Canvas >
<Path Stroke="Black" Data="M 0 0 l 0 10" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
<!--For dotted vertical lines-->
<VisualBrush
x:Key="DottedVerticalLinesWithFill"
TileMode="Tile" Viewport="0,0,10,10"
ViewportUnits="Absolute" Viewbox="0,0,10,10"
ViewboxUnits="Absolute">
<VisualBrush.Visual>
<Canvas>
<Path Stroke="Purple" Data="M 0 5l 0 -10" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
You can easily do this with a LinearGradientBrush:
<Rectangle Width="100" Height="100">
<Rectangle.Fill>
<LinearGradientBrush SpreadMethod="Reflect" StartPoint="0 0" EndPoint="0 0.05">
<GradientStop Offset="0.5" Color="Black"/>
<GradientStop Offset="0.5" Color="White"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
You control line thickness and orientation with the EndPoint property.
Have you tried using a TileBrush?
ImageBrush derives from TileBrush so you can use the Viewport property to repeat the image. See this MSDN page for an example.

WPF geometry (Path) partly filled shapes

The idea is to get rating control, that could have value like 0.3 and draw it as partly filled shape.
The approach I used was described in CodeProject article. Have Path, add mask (rectangle), add outline.
Original code used Margin for mask and fixed-width paths.
The problem is that using rectangle as mask re-draws background, which is gradient, so I can't set the same background value for mask.
I've changed colors just to make it more clear.
Is it possible to simulate partly filled paths?
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<Grid x:Name="gdStar" Width="Auto" Height="Auto">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="0.5*"/>
<ColumnDefinition Width="0.5*"/>
</Grid.ColumnDefinitions>
<Path
Grid.ColumnSpan="2"
Fill="Red" Stretch="Fill" Stroke="Blue" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z"/>
<Rectangle Grid.Column="1"
Fill="Yellow"/>
<Path
Grid.ColumnSpan="2"
Fill="Transparent" Stretch="Fill" Stroke="Red" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z"/>
</Grid>
</Page>
EDIT
This could be wrong approach. What else could be used? OpacityBrush/Clip?
Are you looking for something like this:
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d">
<Grid x:Name="gdStar" Width="Auto" Height="Auto">
<Path
Fill="Red" Stretch="Fill" Stroke="Blue" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z">
<Path.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Transparent" Offset="0.3"/>
<GradientStop Color="White" Offset="0.3"/>
</LinearGradientBrush>
</Path.OpacityMask>
</Path>
<Path
Fill="Transparent" Stretch="Fill" Stroke="Red" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z"/>
</Grid>
</Page>
Edit Here is a version that changes the background of non filled area by adding a white star behind it, I've left the previous example there because its a bit clearer.
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Background="yellow">
<Grid x:Name="gdStar" Width="Auto" Height="Auto">
<Path
Fill="White" Stretch="Fill" Stroke="Blue" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z"/>
<Path
Fill="Red" Stretch="Fill" Stroke="Blue" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z">
<Path.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
<GradientStop Color="Transparent" Offset="0.3"/>
<GradientStop Color="White" Offset="0.3"/>
</LinearGradientBrush>
</Path.OpacityMask>
</Path>
<Path
Fill="Transparent" Stretch="Fill" Stroke="Red" StrokeThickness="1" Margin="-0.5,-0.5,-0.5,-0.019" Data="M63.50031,-0.50054431 L47.500523,55.499079 -0.49883747,55.499079 39.50063,95.498578 23.500843,159.49799 63.833676,128.4989 103.7623,160.51698 87.095797,95.850405 127.49946,55.499079 79.500097,55.499079 z"/>
</Grid>
</Page>

WPF drawing that stretches without stretching the Pen

I need to draw some simple lines within a Border control (or similar) that always stretch to the bounds of the Border. Is there a way to stretch the lines only but not its pen? Without involving lots of C#?
In this version the lines stretch:
<Border>
<Border.Background>
<DrawingBrush>
<DrawingBrush.Drawing>
<DrawingGroup>
<GeometryDrawing Brush="Red">
<GeometryDrawing.Geometry>
<GeometryGroup>
<RectangleGeometry Rect="0,0 100,1000" />
<LineGeometry StartPoint="0,0" EndPoint="100,1000"/>
<LineGeometry StartPoint="100,0" EndPoint="0,1000"/>
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Pen>
<Pen Thickness="20" Brush="Black"/>
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingGroup>
</DrawingBrush.Drawing>
</DrawingBrush>
</Border.Background>
</Border>
The best solution I have come up with is this:
<Border>
<Grid>
<Path Stretch="Fill" Fill="Red" Stroke="Black" StrokeThickness="4" Data="M0,0 L100,0 100,1000 0,1000 z" />
<Path Stretch="Fill" Stroke="Black" StrokeThickness="4" Data="M 0,0 L0,0 100,1000" />
<Path Stretch="Fill" Stroke="Black" StrokeThickness="4" Data="M 100,0 L100,0 0,1000" />
</Grid>
</Border>
But isn't there a better solution? That doesn't involve extra Grid?
I've done this by scaling a Path's Data, not the visual component.
Place a Path in a Canvas.
Set path.Data to a Geometry representing your data as percentages of the logical range.
Set path.Data.Transform to a ScaleTransform with ScaleX and ScaleY bound to the actual width and height.
Within a line, you can bind the width (or height, depending on which way you are drawing the line) to that of the parent container to achieve what you want.
<Grid x:Name="Grid" Margin="10">
<Border BorderBrush="Black" BorderThickness="1" />
<Line X1="0" X2="{Binding ElementName=Grid, Path=ActualWidth}" Y1="1" Y2="1" Stroke="Red" Margin="0,10,0,0" />
<Line X1="0" X2="{Binding ElementName=Grid, Path=ActualWidth}" Y1="1" Y2="1" Stroke="Green" Margin="0,30,0,0" />
<Line X1="0" X2="{Binding ElementName=Grid, Path=ActualWidth}" Y1="1" Y2="1" Stroke="Blue" Margin="0,50,0,0" />
</Grid>
Edit: Here is another way without using binding
<Border BorderBrush="Black" BorderThickness="1" >
<Path Stroke="Red" StrokeThickness="1" Data="M0,0 1,0Z" Stretch="Fill" />
</Border>
None that I know of. But unless you're doing something really extravagant, it really isn't a lot of effort to override OnRender and draw it yourself:
public class CustomBorder : Border
{
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
dc.DrawLine(new Pen(BorderBrush, BorderThickness.Top), new Point(0, 0), new Point(ActualWidth, ActualHeight));
}
}
Result:

Resources