Created
December 11, 2013 13:13
-
-
Save iburlakov/7910205 to your computer and use it in GitHub Desktop.
A sample of inherit from Decorator class. CalloutBorder wraps content with custom shape border, parameters of pointer are customizable. Sample is also attached.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Windows; | |
using System.Windows.Controls; | |
using System.Windows.Media; | |
namespace iburlakov | |
{ | |
public class CalloutBorder : Decorator | |
{ | |
private const double DEFAULT_POINTER_HEIGTH = 20D; | |
private const double DEFAULT_POINTER_WIDTH = 10D; | |
public static readonly DependencyProperty BorderBrushProperty = DependencyProperty.Register("BorderBrush", typeof(Brush), typeof(CalloutBorder)); | |
public Brush BorderBrush | |
{ | |
get { return this.GetValue(BorderBrushProperty) as Brush; } | |
set { this.SetValue(BorderBrushProperty, value); } | |
} | |
public static readonly DependencyProperty BackgroundProperty = DependencyProperty.Register("Background", typeof(Brush), typeof(CalloutBorder)); | |
public Brush Background | |
{ | |
get { return this.GetValue(BackgroundProperty) as Brush; } | |
set { this.SetValue(BackgroundProperty, value); } | |
} | |
public static readonly DependencyProperty BorderThicknessProperty = DependencyProperty.Register("BorderThinckness", typeof(double), typeof(CalloutBorder)); | |
public double BorderThickness | |
{ | |
get { return (double)this.GetValue(BorderThicknessProperty); } | |
set { this.SetValue(BorderThicknessProperty, value); } | |
} | |
public static readonly DependencyProperty CornerRadiusProperty = DependencyProperty.Register("CornerRadius", typeof(double), typeof(CalloutBorder)); | |
public double CornerRadius | |
{ | |
get { return (double)this.GetValue(CornerRadiusProperty); } | |
set { this.SetValue(CornerRadiusProperty, value); } | |
} | |
public static readonly DependencyProperty PointerHeigthProperty = DependencyProperty.Register("PointerHeigth", typeof(double), typeof(CalloutBorder), new FrameworkPropertyMetadata(CalloutBorder.DEFAULT_POINTER_HEIGTH, FrameworkPropertyMetadataOptions.AffectsRender)); | |
public double PointerHeigth | |
{ | |
get { return (double)this.GetValue(PointerHeigthProperty); } | |
set { this.SetValue(PointerHeigthProperty, value); } | |
} | |
public static readonly DependencyProperty PointerWidthProperty = DependencyProperty.Register("PointerWidth", typeof(double), typeof(CalloutBorder), new FrameworkPropertyMetadata(CalloutBorder.DEFAULT_POINTER_WIDTH, FrameworkPropertyMetadataOptions.AffectsRender)); | |
public double PointerWidth | |
{ | |
get { return (double)this.GetValue(PointerWidthProperty); } | |
set { this.SetValue(PointerWidthProperty, value); } | |
} | |
public static readonly DependencyProperty PointerPositionProperty = DependencyProperty.Register("PointerPosition", typeof(double), typeof(CalloutBorder), new FrameworkPropertyMetadata(0D, FrameworkPropertyMetadataOptions.AffectsRender)); | |
public double PointerPosition | |
{ | |
get { return (double)this.GetValue(PointerPositionProperty); } | |
set { this.SetValue(PointerPositionProperty, value); } | |
} | |
protected override Size ArrangeOverride(Size arrangeSize) | |
{ | |
if (this.Child != null) | |
{ | |
var contentRect = new Rect(this.PointerWidth, 0, arrangeSize.Width - this.PointerWidth, arrangeSize.Height); | |
var borderMove = -1 * this.BorderThickness / 2; | |
contentRect.Inflate(borderMove, borderMove); | |
this.Child.Arrange(contentRect); | |
} | |
return arrangeSize; | |
} | |
protected override void OnRender(DrawingContext drawingContext) | |
{ | |
var borderRect = new Rect(0, 0, this.RenderSize.Width, this.RenderSize.Height); | |
var borderMove = -1 * this.BorderThickness / 2; | |
borderRect.Inflate(borderMove, borderMove); | |
// draw border with pointer | |
var geometry = new StreamGeometry(); | |
using (var context = geometry.Open()) | |
{ | |
// validate PointerHeigth | |
double pointerHeigth; | |
if (this.PointerHeigth < CalloutBorder.DEFAULT_POINTER_HEIGTH || this.PointerHeigth >= borderRect.Height) | |
{ | |
pointerHeigth = CalloutBorder.DEFAULT_POINTER_HEIGTH; | |
} | |
else | |
{ | |
pointerHeigth = this.PointerHeigth; | |
} | |
// validate PointerPosition | |
var minPointerMargin = 2; | |
var minPointerPosition = borderRect.Y + pointerHeigth / 2 + this.CornerRadius + minPointerMargin; | |
var maxPointerPosition = borderRect.Height - pointerHeigth / 2 - this.CornerRadius - minPointerMargin; | |
double pointerPosition = System.Math.Abs(this.PointerPosition); | |
var positionFromBottom = this.PointerPosition < 0; | |
if (pointerPosition > maxPointerPosition) | |
{ | |
pointerPosition = positionFromBottom ? minPointerPosition : maxPointerPosition; | |
} | |
else if (pointerPosition < minPointerPosition) | |
{ | |
pointerPosition = positionFromBottom ? maxPointerPosition : minPointerPosition; | |
} | |
// top-left corner | |
context.BeginFigure(new Point(borderRect.X + this.PointerWidth + this.CornerRadius, borderRect.Y), true, true); | |
context.ArcTo(new Point(borderRect.X + this.PointerWidth, borderRect.Y + this.CornerRadius), new Size(this.CornerRadius, this.CornerRadius), 0, false, SweepDirection.Counterclockwise, true, false); | |
if (positionFromBottom) | |
{ | |
// line to pointer | |
context.LineTo(new Point(borderRect.X + this.PointerWidth, borderRect.Height - pointerPosition - pointerHeigth / 2), true, true); | |
// draw pointer | |
context.LineTo(new Point(borderRect.X, borderRect.Height - pointerPosition), true, true); | |
context.LineTo(new Point(borderRect.X + this.PointerWidth, borderRect.Height - pointerPosition + pointerHeigth / 2), true, true); | |
} | |
else | |
{ | |
// line to pointer | |
context.LineTo(new Point(borderRect.X + this.PointerWidth, borderRect.Y + pointerPosition - pointerHeigth / 2), true, true); | |
// draw pointer | |
context.LineTo(new Point(borderRect.X, borderRect.Y + pointerPosition), true, true); | |
context.LineTo(new Point(borderRect.X + this.PointerWidth, borderRect.Y + pointerPosition + pointerHeigth / 2), true, true); | |
} | |
// line to bottom-left corner | |
context.LineTo(new Point(borderRect.X + this.PointerWidth, borderRect.Height - this.CornerRadius), true, true); | |
// bottom-left corner | |
context.ArcTo(new Point(borderRect.X + this.PointerWidth + this.CornerRadius, borderRect.Height), new Size(this.CornerRadius, this.CornerRadius), 0, false, SweepDirection.Counterclockwise, true, false); | |
// bottom line | |
context.LineTo(new Point(borderRect.Width - this.CornerRadius, borderRect.Height), true, false); | |
// bottom-rigth corner | |
context.ArcTo(new Point(borderRect.Width, borderRect.Height - this.CornerRadius), new Size(this.CornerRadius, this.CornerRadius), 0, false, SweepDirection.Counterclockwise, true, false); | |
// rigth line | |
context.LineTo(new Point(borderRect.Width, borderRect.Y + this.CornerRadius), true, false); | |
// top-left corner | |
context.ArcTo(new Point(borderRect.Width - this.CornerRadius, borderRect.Y), new Size(this.CornerRadius, this.CornerRadius), 0, false, SweepDirection.Counterclockwise, true, false); | |
} | |
drawingContext.DrawGeometry(this.Background, new Pen(this.BorderBrush, this.BorderThickness), geometry); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<Window x:Class="Test.MainWindow" | |
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" | |
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" | |
Title="MainWindow" SizeToContent="WidthAndHeight" | |
xmlns:c="clr-namespace:iburlakov"> | |
<Grid Width="365"> | |
<Grid.RowDefinitions> | |
<RowDefinition Height="200" /> | |
<RowDefinition Height="20" /> | |
<RowDefinition Height="auto" /> | |
<RowDefinition Height="auto" /> | |
<RowDefinition Height="auto" /> | |
</Grid.RowDefinitions> | |
<c:CalloutBorder x:Name="callout" Grid.Row="0" BorderBrush="Green" BorderThickness="1" CornerRadius="5" Width="175" Margin="0 20" | |
PointerPosition="80" PointerHeigth="20" PointerWidth="10"> | |
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center"> | |
line 1 | |
<LineBreak /> | |
line 2 | |
<LineBreak /> | |
line 3 | |
<LineBreak /> | |
line 4 | |
</TextBlock> | |
</c:CalloutBorder> | |
<StackPanel Grid.Row="2" VerticalAlignment="Center" Orientation="Horizontal"> | |
<Label Content="Pointer Position" Width="100" /> | |
<TextBox Width="50" Text="{Binding PointerPosition, ElementName=callout}" /> | |
<Slider Value="{Binding PointerPosition, ElementName=callout}" Minimum="0" Maximum="200" Width="200" /> | |
</StackPanel> | |
<StackPanel Grid.Row="3" VerticalAlignment="Center" Orientation="Horizontal"> | |
<Label Content="Pointer Heigth" Width="100" /> | |
<TextBox Width="50" Text="{Binding PointerHeigth, ElementName=callout}" /> | |
<Slider Value="{Binding PointerHeigth, ElementName=callout}" Minimum="0" Maximum="100" Width="200" /> | |
</StackPanel> | |
<StackPanel Grid.Row="4" VerticalAlignment="Center" Orientation="Horizontal"> | |
<Label Content="Pointer Width" Width="100" /> | |
<TextBox Width="50" Text="{Binding PointerWidth, ElementName=callout}"/> | |
<Slider Value="{Binding PointerWidth, ElementName=callout}" Minimum="0" Maximum="100" Width="200" /> | |
</StackPanel> | |
</Grid> | |
</Window> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment