Here it is, the full code for this task:
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace Decorators
{
public enum Position
{
None,
Top,
Bottom,
RightSide,
LeftSide,
}
public enum SpecificPosition
{
None,
LeftOrTop = 25,
Center = 50,
RightOrBottom = 75,
}
internal class BubbleTextDecorator : Decorator
{
#region DependencyProperties
public static readonly DependencyProperty VerticalMarginProperty = DependencyProperty.Register("VerticalMargin",
typeof(double),
typeof(BubbleTextDecorator),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender));
public double VerticalMargin
{
get { return (double)GetValue(VerticalMarginProperty); }
set { SetValue(VerticalMarginProperty, value); }
}
public static readonly DependencyProperty HorizontalMarginProperty = DependencyProperty.Register("HorizontalMargin",
typeof(double),
typeof(BubbleTextDecorator),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender));
public double HorizontalMargin
{
get { return (double)GetValue(HorizontalMarginProperty); }
set { SetValue(HorizontalMarginProperty, value); }
}
public static readonly DependencyProperty PointerPositionProperty = DependencyProperty.Register("PointerPosition",
typeof(Position),
typeof(BubbleTextDecorator),
new FrameworkPropertyMetadata(Position.None,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public Position PointerPosition
{
get { return (Position)GetValue(PointerPositionProperty); }
set { SetValue(PointerPositionProperty, value); }
}
public static readonly DependencyProperty AlignmentPositionProperty = DependencyProperty.Register("AlignmentPosition",
typeof(SpecificPosition),
typeof(BubbleTextDecorator),
new FrameworkPropertyMetadata(SpecificPosition.None,
FrameworkPropertyMetadataOptions.AffectsRender |
FrameworkPropertyMetadataOptions.AffectsMeasure));
public SpecificPosition AlignmentPosition
{
get { return (SpecificPosition)GetValue(AlignmentPositionProperty); }
set { SetValue(AlignmentPositionProperty, value); }
}
public static readonly DependencyProperty PointerHeightProperty = DependencyProperty.Register("PointerHeight",
typeof(double),
typeof(BubbleTextDecorator),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsRender));
public double PointerHeight
{
get { return (double)GetValue(PointerHeightProperty); }
set { SetValue(PointerHeightProperty, value); }
}
public static readonly DependencyProperty PointerWidthProperty = DependencyProperty.Register("PointerWidth",
typeof(double),
typeof(BubbleTextDecorator),
new FrameworkPropertyMetadata(0.0,
FrameworkPropertyMetadataOptions.AffectsMeasure |
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.AffectsRender));
public double PointerWidth
{
get { return (double)GetValue(PointerWidthProperty); }
set { SetValue(PointerWidthProperty, value); }
}
#endregion
protected override Size ArrangeOverride(Size arrangeSize)
{
Size desiredSize = base.ArrangeOverride(arrangeSize);
if (Child != null)
{
switch (PointerPosition)
{
case Position.Top:
Child.Arrange(new Rect(new Point(0.0, PointerHeight), new Point(desiredSize.Width, desiredSize.Height)));
break;
case Position.Bottom:
Child.Arrange(new Rect(new Point(0.0, 0.0), new Point(desiredSize.Width, desiredSize.Height - PointerHeight)));
break;
case Position.LeftSide:
Child.Arrange(new Rect(new Point(PointerHeight, 0.0), new Point(desiredSize.Width, desiredSize.Height)));
break;
case Position.RightSide:
Child.Arrange(new Rect(new Point(0.0, 0.0), new Point(desiredSize.Width - PointerHeight, desiredSize.Height)));
break;
}
}
return arrangeSize;
}
protected override Size MeasureOverride(Size constraint)
{
Size desiredSize = base.MeasureOverride(constraint);
Size size = (PointerPosition == Position.Top || PointerPosition == Position.Bottom)
? new Size(desiredSize.Width + (HorizontalMargin * 2), desiredSize.Height + (VerticalMargin * 2) + PointerHeight)
: new Size(desiredSize.Width + (HorizontalMargin * 2) + PointerHeight, desiredSize.Height + (VerticalMargin * 2));
return size;
}
protected override void OnRender(DrawingContext drawingContext)
{
Brush renderBrush = Brushes.Transparent;
Pen renderPen = new Pen(Brushes.Black, 1);
StreamGeometry geom = new StreamGeometry();
switch (PointerPosition)
{
case Position.Top:
DrawTop(geom);
break;
case Position.Bottom:
DrawBottom(geom);
break;
case Position.RightSide:
DrawRight(geom);
break;
case Position.LeftSide:
DrawLeft(geom);
break;
}
// Some arbitrary drawing implements.
drawingContext.DrawGeometry(renderBrush, renderPen, geom);
}
private void DrawLeft(StreamGeometry geom)
{
using (StreamGeometryContext ctx = geom.Open())
{
ctx.BeginFigure(
new Point(PointerHeight, 0.0),
true,
true);
ctx.LineTo(
new Point(ActualWidth, 0.0),
true,
false);
ctx.LineTo(
new Point(ActualWidth, ActualHeight),
true,
false);
ctx.LineTo(
new Point(PointerHeight, ActualHeight),
true,
false);
ctx.LineTo(
new Point(PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) + (PointerWidth / 2)),
true,
false);
ctx.LineTo(
new Point(0.0, ActualHeight * (double)AlignmentPosition / 100),
true,
false);
ctx.LineTo(
new Point(PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) - (PointerWidth / 2)),
true,
false);
ctx.LineTo(
new Point(PointerHeight, 0.0),
true,
false);
}
}
private void DrawRight(StreamGeometry geom)
{
using (StreamGeometryContext ctx = geom.Open())
{
ctx.BeginFigure(
new Point(0.0, 0.0),
true,
true);
ctx.LineTo(
new Point(ActualWidth - PointerHeight, 0.0),
true,
false);
ctx.LineTo(
new Point(ActualWidth - PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) - (PointerWidth / 2)),
true,
false);
ctx.LineTo(
new Point(ActualWidth, ActualHeight * (double)AlignmentPosition / 100),
true,
false);
ctx.LineTo(
new Point(ActualWidth - PointerHeight, (ActualHeight * (double)AlignmentPosition / 100) + (PointerWidth / 2)),
true,
false);
ctx.LineTo(
new Point(ActualWidth - PointerHeight, ActualHeight),
true,
false);
ctx.LineTo(
new Point(0.0, ActualHeight),
true,
false);
ctx.LineTo(
new Point(0.0, 0.0),
true,
false);
}
}
private void DrawBottom(StreamGeometry geom)
{
using (StreamGeometryContext ctx = geom.Open())
{
ctx.BeginFigure(
new Point(0.0, 0.0),
true,
true);
ctx.LineTo(
new Point(ActualWidth, 0.0),
true,
false);
ctx.LineTo(
new Point(ActualWidth, ActualHeight - PointerHeight),
true,
false);
ctx.LineTo(
new Point((ActualWidth * (double)AlignmentPosition / 100) + (PointerWidth / 2), ActualHeight - PointerHeight),
true,
false);
ctx.LineTo(
new Point(ActualWidth * (double)AlignmentPosition / 100, ActualHeight),
true,
false);
ctx.LineTo(
new Point((ActualWidth * (double)AlignmentPosition / 100) - (PointerWidth / 2), ActualHeight - PointerHeight),
true,
false);
ctx.LineTo(
new Point(0.0, ActualHeight - PointerHeight),
true,
false);
ctx.LineTo(
new Point(0.0, 0.0),
true,
false);
}
}
private void DrawTop(StreamGeometry geom)
{
using (StreamGeometryContext ctx = geom.Open())
{
ctx.BeginFigure(
new Point(0.0, PointerHeight),
true,
true);
ctx.LineTo(
new Point((ActualWidth * (double)AlignmentPosition / 100) - (PointerWidth / 2), PointerHeight),
true,
false);
ctx.LineTo(
new Point(ActualWidth * (double)AlignmentPosition / 100, 0.0),
true,
false);
ctx.LineTo(
new Point((ActualWidth * (double)AlignmentPosition / 100) + (PointerWidth / 2), PointerHeight),
true,
false);
ctx.LineTo(
new Point(ActualWidth, PointerHeight),
true,
false);
ctx.LineTo(
new Point(ActualWidth, ActualHeight),
true,
false);
ctx.LineTo(
new Point(0.0, ActualHeight),
true,
false);
ctx.LineTo(
new Point(0.0, PointerHeight),
true,
false);
}
}
}
}
And this is how you use it:
<localdecorators:BubbleTextDecorator PointerHeight="10"
PointerWidth="20"
PointerPosition="LeftSide"
AlignmentPosition="Center"
VerticalMargin="30"
HorizontalMargin="30"
HorizontalAlignment="Left">
<TextBlock Text="this"
HorizontalAlignment="Center"
VerticalAlignment="Center"/>
</localdecorators:BubbleTextDecorator>
