Skip to content

Instantly share code, notes, and snippets.

@iburlakov
Created December 11, 2013 13:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iburlakov/7910205 to your computer and use it in GitHub Desktop.
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.
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);
}
}
}
<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