Skip to content

Instantly share code, notes, and snippets.

@mouadcherkaoui
Forked from davidhagg/WPF Visual Geometry Border Inside
Last active March 5, 2021 07:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mouadcherkaoui/6a40c23220c762c4e6d95251a2b69d84 to your computer and use it in GitHub Desktop.
Save mouadcherkaoui/6a40c23220c762c4e6d95251a2b69d84 to your computer and use it in GitHub Desktop.
WPF Create Visual with border that appears to be only inside of drawn geometry
<UserControl x:Class="Wpf3DPlayer.InformationsIcon"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:Wpf3DPlayer"
mc:Ignorable="d"
d:DesignHeight="50" d:DesignWidth="50">
<Grid>
<Viewbox xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" Stretch="Uniform">
<Canvas Name="svg110" Width="50" Height="50" Canvas.Left="0" Canvas.Top="0">
<Canvas.RenderTransform>
<TranslateTransform X="0" Y="0"/>
</Canvas.RenderTransform>
<Canvas.Resources/>
<!--Unknown tag: metadata-->
<!--Unknown tag: sodipodi:namedview-->
<Canvas Name="g108">
<Canvas Name="g76">
<Canvas Name="g74">
<Canvas Name="g72">
<Path Name="path64" Fill="#FF196DA5" StrokeThickness="0.10175">
<Path.Data>
<PathGeometry Figures="m 26.183089 19.14069 c 2.397621 0 4.341338 -1.943717 4.341338 -4.341338 0 -2.39762 -1.943717 -4.341337 -4.341338 -4.341337 -2.39762 0 -4.341337 1.943615 -4.341337 4.341337 0.0028 2.396502 1.944937 4.338489 4.341337 4.341338 z m 0 -6.94612 c 1.438533 0 2.604783 1.166149 2.604783 2.604782 0 1.438634 -1.16625 2.604782 -2.604783 2.604782 -1.43853 0 -2.604782 -1.16625 -2.604782 -2.604782 0.0017 -1.437921 1.166862 -2.603154 2.604782 -2.604782 z" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Name="path66" Fill="#FF196DA5" StrokeThickness="0.10175">
<Path.Data>
<PathGeometry Figures="M 32.260983 33.032827 H 30.524427 V 22.613699 c -0.0011 -0.95858 -0.777975 -1.735436 -1.736555 -1.736555 H 22.71008 c -0.95858 0.0011 -1.735435 0.777975 -1.736555 1.736555 v 1.736555 c 0.0011 0.95858 0.777975 1.735436 1.736555 1.736555 l 0.868227 -9.17e-4 v 6.946934 h -1.736555 c -0.95858 0.0011 -1.735437 0.777975 -1.736556 1.736555 v 3.473009 c 0.0011 0.95858 0.777976 1.735436 1.736556 1.736555 h 10.419129 c 0.95858 -0.0011 1.735435 -0.777873 1.736554 -1.736555 v -3.473008 c -0.001 -0.95858 -0.777872 -1.735436 -1.736452 -1.736555 z m 0 5.209564 H 21.841854 v -3.473009 h 1.736555 c 0.95858 -0.0011 1.735436 -0.777975 1.736555 -1.736555 v -6.94612 c -0.0011 -0.95858 -0.777873 -1.735436 -1.736555 -1.736555 h -0.868227 v -1.736453 h 6.077792 v 10.419128 c 0.0011 0.95858 0.777873 1.735436 1.736555 1.736555 h 1.736555 v 3.473009 z" FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Name="path70" Fill="#FF196DA5" StrokeThickness="0.10175">
<Path.Data>
<PathGeometry Figures="m 26.183089 5.248553
c -11.508659 0 -20.8382564 9.329596 -20.8382564 20.838256 0.01312 11.503165 9.3350914 20.825131 20.8382564 20.838257 11.50866 0 20.838258 -9.329597 20.838258 -20.838257 0 -11.50866 -9.329598 -20.838256 -20.838258 -20.838256
z m 0 39.939957
c -10.54957 0 -19.1017004 -8.55213 -19.1017004 -19.101701 0.0119 -10.544686 8.5571154 -19.089898 19.1017004 -19.101803 10.549571 0 19.101702 8.552131 19.101702 19.101701 0 10.549571 -8.552028 19.101803 -19.101702 19.101803 z"
FillRule="NonZero"/>
</Path.Data>
</Path>
<Path Name="path705" Fill="#FF196DA5" StrokeThickness="0.122099">
<Path.Data>
<PathGeometry Figures="m 26.279765 1.274252
c -13.810391 0 -25.0059075 11.195516 -25.0059075 25.005908 0.01575 13.803799 11.2021095 24.990158 25.0059075 25.005908 13.810393 0 25.005909 -11.195516 25.005909 -25.005908 0 -13.810392 -11.195516 -25.005908 -25.005909 -25.005908
z m 0 47.92795
C 13.62028 49.202202 3.3577238 38.939645 3.3577238 26.28016 3.3720088 13.626535 13.626264 3.372282 26.279765 3.357996
c 12.659486 0 22.922042 10.262557 22.922042 22.922042 0 12.659485 -10.262433 22.922164 -22.922042 22.922164 z"
FillRule="NonZero"/>
</Path.Data>
</Path>
</Canvas>
</Canvas>
</Canvas>
</Canvas>
</Canvas>
</Viewbox>
</Grid>
</UserControl>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Shapes;
using SysFlowDirection = System.Windows.FlowDirection;
using System.ComponentModel;
using System.Globalization;
using System.Windows.Documents;
namespace DemoWPF.Controls
{
[ContentProperty("Text")]
public class TextPath : Shape
{
Geometry _textGeometry = Geometry.Empty;
#region dependency properties
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(TextPath));
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
// Using a DependencyProperty as the backing store for FontFamily. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontFamilyProperty =
DependencyProperty.Register("FontFamily", typeof(FontFamily), typeof(TextPath));
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
// Using a DependencyProperty as the backing store for FontStyle. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontStyleProperty =
DependencyProperty.Register("FontStyle", typeof(FontStyle), typeof(TextPath));
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
// Using a DependencyProperty as the backing store for FontWeight. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontWeightProperty =
DependencyProperty.Register("FontWeight", typeof(FontWeight), typeof(TextPath));
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
// Using a DependencyProperty as the backing store for FontStretch. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontStretchProperty =
DependencyProperty.Register("FontStretch", typeof(FontStretch), typeof(TextPath));
public int FontSize
{
get { return (int)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
// Using a DependencyProperty as the backing store for FontSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty FontSizeProperty =
DependencyProperty.Register("FontSize", typeof(int), typeof(TextPath));
#endregion
#region shape members overrides
protected override Size MeasureOverride(Size availableSize)
{
if (_textGeometry == null) createTextGeometry();
if (_textGeometry.Bounds == Rect.Empty)
return new Size(0, 0);
// return the desired size
return new Size(Math.Min(availableSize.Width, _textGeometry.Bounds.Width),
Math.Min(availableSize.Height, _textGeometry.Bounds.Height));
}
protected override Geometry DefiningGeometry => _textGeometry ?? Geometry.Empty;
private void createTextGeometry()
{
var origin = new Point(0, 0);
var currentCulture = Thread.CurrentThread.CurrentUICulture;
var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch);
var formattedText = new FormattedText(Text, currentCulture, SysFlowDirection.LeftToRight, typeface, FontSize, Brushes.Black);
_textGeometry = formattedText.BuildGeometry(origin);
}
/// <summary>
/// OnRender override draws the geometry of the text and optional highlight.
/// </summary>
/// <param name="drawingContext">Drawing context of the OutlineText control.</param>
protected override void OnRender(DrawingContext drawingContext)
{
drawingContext.DrawGeometry(Fill, new System.Windows.Media.Pen(Stroke, StrokeThickness), _textGeometry);
}
private static void OnPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) => ((TextPath)d).createTextGeometry();
#endregion
}
[ContentProperty("Text")]
public class OutlinedTextBlock : FrameworkElement
{
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(double),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextInvalidated));
public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
"TextAlignment",
typeof(TextAlignment),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(
"TextDecorations",
typeof(TextDecorationCollection),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
"TextTrimming",
typeof(TextTrimming),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
"TextWrapping",
typeof(TextWrapping),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(TextWrapping.NoWrap, OnFormattedTextUpdated));
private FormattedText formattedText;
private Geometry textGeometry;
public OutlinedTextBlock()
{
this.TextDecorations = new TextDecorationCollection();
}
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public TextAlignment TextAlignment
{
get { return (TextAlignment)GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
public TextDecorationCollection TextDecorations
{
get { return (TextDecorationCollection)this.GetValue(TextDecorationsProperty); }
set { this.SetValue(TextDecorationsProperty, value); }
}
public TextTrimming TextTrimming
{
get { return (TextTrimming)GetValue(TextTrimmingProperty); }
set { SetValue(TextTrimmingProperty, value); }
}
public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
this.EnsureGeometry();
drawingContext.DrawGeometry(this.Fill, new Pen(this.Stroke, this.StrokeThickness), this.textGeometry);
}
protected override Size MeasureOverride(Size availableSize)
{
this.EnsureFormattedText();
// constrain the formatted text according to the available size
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
// the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
this.formattedText.MaxTextWidth = Math.Min(3579139, availableSize.Width);
this.formattedText.MaxTextHeight = Math.Max(0.0001d, availableSize.Height);
// return the desired size
return new Size(this.formattedText.Width, this.formattedText.Height);
}
protected override Size ArrangeOverride(Size finalSize)
{
this.EnsureFormattedText();
// update the formatted text with the final size
this.formattedText.MaxTextWidth = finalSize.Width;
this.formattedText.MaxTextHeight = finalSize.Height;
// need to re-generate the geometry now that the dimensions have changed
this.textGeometry = null;
return finalSize;
}
private static void OnFormattedTextInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock.formattedText = null;
outlinedTextBlock.textGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private static void OnFormattedTextUpdated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock.UpdateFormattedText();
outlinedTextBlock.textGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private void EnsureFormattedText()
{
if (this.formattedText != null || this.Text == null)
{
return;
}
this.formattedText = new FormattedText(
this.Text,
CultureInfo.CurrentUICulture,
this.FlowDirection,
new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, FontStretches.Normal),
this.FontSize,
Brushes.Black);
this.UpdateFormattedText();
}
private void UpdateFormattedText()
{
if (this.formattedText == null)
{
return;
}
this.formattedText.MaxLineCount = this.TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
this.formattedText.TextAlignment = this.TextAlignment;
this.formattedText.Trimming = this.TextTrimming;
this.formattedText.SetFontSize(this.FontSize);
this.formattedText.SetFontStyle(this.FontStyle);
this.formattedText.SetFontWeight(this.FontWeight);
this.formattedText.SetFontFamily(this.FontFamily);
this.formattedText.SetFontStretch(this.FontStretch);
this.formattedText.SetTextDecorations(this.TextDecorations);
}
private void EnsureGeometry()
{
if (this.textGeometry != null)
{
return;
}
this.EnsureFormattedText();
this.textGeometry = this.formattedText.BuildGeometry(new Point(0, 0));
}
}
}
private static DrawingVisual CreateVisual()
{
var noBorderPen = new Pen(new SolidColorBrush(Colors.Transparent), 0);
var selectionPen = new Pen(new SolidColorBrush(Colors.Yellow), 20 * 2);
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
StreamGeometry geometry = new StreamGeometry();
using (var ctx = geometry.Open())
{
ctx.BeginFigure(new Point(100, 100), true, true);
ctx.LineTo(new Point(300, 100), true, false);
ctx.LineTo(new Point(300, 200), true, false);
}
// Draw the geometry
dc.DrawGeometry(new SolidColorBrush(Colors.Fuchsia), noBorderPen, geometry);
// Apply a clip path to only draw the geometry and hide everything outside
dc.PushClip(geometry);
// Draw the geometry on top with the selection border
dc.DrawGeometry(Brushes.Transparent, selectionPen, geometry);
// Remove the clip path
dc.Pop();
geometry.Freeze();
}
return drawingVisual;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment