.net - 編集 - テキスト ボックス テキスト ブロック 違い




ストロークをWPFのテキストブロックに適用する (11)

TextBlockをBorder ...で囲む必要があります。

    <Border BorderBrush="Purple" BorderThickness="2">
        <TextBlock>My fancy TextBlock</TextBlock>
    </Border>

オフチャンスでは、実際の文字の周りにストロークを配置する方法(TextBlock全体ではなく)を尋ねています.GlowのBitmapEffectを使用して見たい場合や、Glowのパラメータを希望するストロークの色に設定するなどですそれ以外の場合はカスタムを作成する必要があります。

どのようにWPFのxamlのテキストブロックにストローク(テキストの輪郭線)を適用しますか?


<TextBlock> has no decorative attributes itself. I would put it on a <Canvas> with a <Rectangle> and apply the stroke there.


Could just use a Label instead. It has more properties that you can play with. 例:

  <Style x:Key="LeftBorderLabel" TargetType="{x:Type Label}">
            <Setter Property="Margin"  Value="0" />
            <Setter Property="BorderThickness" Value="1,0,0,0" />
            <Setter Property="BorderBrush" Value="Blue" />
  </Style>

I was trying to achieve something similar to this as well. The classes mentioned here were great, but weren't exactly what I was looking for, because it only really looked right if the text was large enough. The text I was trying to display was around 10 - 11 font size, and the stroke was so large the letters sort of blended together.

Just to clarify, this text was supposed to be overlaid on a user-defined picture, which could have varying colors, and I wanted to ensure this text would show up.

I don't know if this is best practice or not, but this at least achieved the look I wanted (based on this article ):

<Style x:Key="OutlinedTextBlockOuter" TargetType="TextBlock">
    <Setter Property="Foreground" Value="Black" />
    <Setter Property="FontSize" Value="10"/>
    <Setter Property="Effect">
        <Setter.Value>
            <BlurEffect Radius="3.0"/>
        </Setter.Value>
    </Setter>
</Style>
<Style x:Key="OutlinedTextBlockInner" TargetType="TextBlock">
    <Setter Property="Foreground" Value="White" />
    <Setter Property="FontSize" Value="10"/>
</Style>

Then for the actual TextBlocks, I combined two Outer styled TextBlocks because one was too light, and one Inner styled TextBlock:

<Grid Margin="5">
    <TextBlock Style="{StaticResource OutlinedTextBlockOuter}" Text="This is outlined text using BlurEffect"/>
    <TextBlock Style="{StaticResource OutlinedTextBlockOuter}" Text="This is outlined text using BlurEffect"/>
    <TextBlock Style="{StaticResource OutlinedTextBlockInner}" Text="This is outlined text using BlurEffect"/>
</Grid>

また、DropShadowEffectを使用することもできます。ただし、2つのテキストボックスだけを使用しても構いません(方向を変えたり、不透明度を下げたDropShadowEffectをさらに追加する方がうまくいくかもしれません)。

<Grid Margin="5">
    <TextBlock  Text="This is my outlined text using the DropShadowEffect" FontSize="10" Foreground="White">
        <TextBlock.Effect>
            <DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.75" Direction="315"/>
        </TextBlock.Effect>
    </TextBlock>
    <TextBlock  Text="This is my outlined text using the DropShadowEffect" FontSize="10" Foreground="White">
        <TextBlock.Effect>
            <DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.75" Direction="135"/>
        </TextBlock.Effect>
    </TextBlock>
</Grid>

If applied for anyone, here a simple solution using ONLY XAML. I am not sure if it performs better or worse, but in my opinion, it looks better then any other solution above. I wrap it in a ContentControl Style (and Template), following this Old School example :) http://oldschooldotnet.blogspot.co.il/2009/02/fancy-fonts-in-xaml-silverlight-and-wpf.html

<Style x:Key="OutlinedText" TargetType="{x:Type ContentControl}">
    <!-- Some Style Setters -->
    <Setter Property="Content" Value="Outlined Text"/>
    <Setter Property="Padding" Value="0"/>
    <!-- Border Brush Must be equal '0' because TextBlock that emulate the stroke will using the BorderBrush as to define 'Stroke' color-->
    <Setter Property="BorderThickness" Value="0"/>
    <!-- Border Brush define 'Stroke' Color-->
    <Setter Property="BorderBrush" Value="White"/>
    <Setter Property="Foreground" Value="Black"/>
    <Setter Property="FontSize" Value="24"/>
    <Setter Property="FontFamily" Value="Seoge UI Bold"/>
    <Setter Property="HorizontalContentAlignment" Value="Center"/>
    <Setter Property="VerticalContentAlignment" Value="Center"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ContentControl}">
                <Canvas Width="{Binding ActualWidth, ElementName=FillText}" Height="{Binding ActualHeight, ElementName=FillText}">
                    <Canvas.Resources>
                        <!-- Style to ease the duplication of Text Blocks that emulate the stroke: Binding to one element (or to template) is the first part of the Trick -->
                        <Style x:Key="OutlinedTextStrokeTextBlock_Style" TargetType="{x:Type TextBlock}">
                            <Setter Property="Text" Value="{Binding Text, ElementName=FillText}"/>
                            <Setter Property="FontSize" Value="{Binding FontSize, ElementName=FillText}"/>
                            <Setter Property="FontFamily" Value="{Binding FontFamily, ElementName=FillText}"/>
                            <Setter Property="FontStyle" Value="{Binding FontStyle, ElementName=FillText}"/>
                            <Setter Property="FontWeight" Value="{Binding FontWeight, ElementName=FillText}"/>
                            <Setter Property="Padding" Value="{Binding TextAlignment, ElementName=Padding}"/>
                            <Setter Property="TextAlignment" Value="{Binding TextAlignment, ElementName=FillText}"/>
                            <Setter Property="VerticalAlignment" Value="{Binding VerticalAlignment, ElementName=FillText}"/>
                        </Style>
                    </Canvas.Resources>
                    <!-- Offseting the Text block will create the outline, the margin is the Stroke Width-->
                    <TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="-1,0,0,0" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
                    <TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="0,-1,0,0" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
                    <TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="0,0,-1,0" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
                    <TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="0,0,0,-1" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
                    <!-- Base TextBlock Will be the Fill -->
                    <TextBlock x:Name="FillText" Text="{TemplateBinding Content}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}"
                               FontStyle="{TemplateBinding FontStyle}" FontWeight="{TemplateBinding FontWeight}" Padding="0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" 
                               TextAlignment="{TemplateBinding HorizontalContentAlignment}"
                               Style="{DynamicResource TbMediaOverlay_Style}"/>
                </Canvas>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

In Blend you could convert the TextBlock to a Path, and then use the normal Stroke properties. But I'm assuming you wanted something that you could make dynamic...

Otherwise I would think it would have to be some sort of bitmap effect or special brush.


This helped me out tremendously! Just in case anyone needs it in the future, here's the VB version (made StrokeThickness a double and added an Underline property):

Imports System
Imports System.Windows.Media
Imports System.Globalization
Imports System.Windows
Imports System.Windows.Markup

Namespace CustomXaml

    Public Class OutlinedText
        Inherits FrameworkElement
        Implements IAddChild

        Private _textGeometry As Geometry

        Private Shared Sub OnOutlineTextInvalidated(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
            DirectCast(d, OutlinedText).CreateText()
        End Sub

        Protected Overrides Sub OnRender(drawingContext As System.Windows.Media.DrawingContext)
            CreateText()
            drawingContext.DrawGeometry(Fill, New Pen(Stroke, StrokeThickness), _textGeometry)
        End Sub

        Public Sub CreateText()
            Dim fontStyle = FontStyles.Normal
            Dim fontWeight = FontWeights.Medium
            Dim fontDecoration = New TextDecorationCollection()

            If Bold Then fontWeight = FontWeights.Bold
            If Italic Then fontStyle = FontStyles.Italic
            If Underline Then fontDecoration.Add(TextDecorations.Underline)

            Dim formattedText = New FormattedText( _
                                Text, _
                                CultureInfo.GetCultureInfo("en-us"), _
                                FlowDirection.LeftToRight, _
                                New Typeface(Font, fontStyle, fontWeight, FontStretches.Normal), _
                                FontSize, _
                                Brushes.Black _
                                )
            formattedText.SetTextDecorations(fontDecoration)

            _textGeometry = formattedText.BuildGeometry(New Point(0, 0))

            Me.MinWidth = formattedText.Width
            Me.MinHeight = formattedText.Height
        End Sub

        Public Property Bold As Boolean
            Get
                Return CType(GetValue(BoldProperty), Boolean)
            End Get
            Set(value As Boolean)
                SetValue(BoldProperty, value)
            End Set
        End Property

        Public Shared ReadOnly BoldProperty As DependencyProperty = DependencyProperty.Register( _
            "Bold", _
            GetType(Boolean), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                False, _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property Underline As Boolean
            Get
                Return CType(GetValue(UnderlineProperty), Boolean)
            End Get
            Set(value As Boolean)
                SetValue(UnderlineProperty, value)
            End Set
        End Property

        Public Shared ReadOnly UnderlineProperty As DependencyProperty = DependencyProperty.Register( _
            "Underline", _
            GetType(Boolean), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                False, _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property Fill As Brush
            Get
                Return CType(GetValue(FillProperty), Brush)
            End Get
            Set(value As Brush)
                SetValue(FillProperty, value)
            End Set
        End Property

        Public Shared ReadOnly FillProperty As DependencyProperty = DependencyProperty.Register( _
            "Fill", _
            GetType(Brush), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                New SolidColorBrush(Colors.LightSteelBlue), _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property Font As FontFamily
            Get
                Return CType(GetValue(FontProperty), FontFamily)
            End Get
            Set(value As FontFamily)
                SetValue(FontProperty, value)
            End Set
        End Property

        Public Shared ReadOnly FontProperty As DependencyProperty = DependencyProperty.Register( _
            "Font", _
            GetType(FontFamily), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                New FontFamily("Arial"), _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property FontSize As Double
            Get
                Return CType(GetValue(FontSizeProperty), Double)
            End Get
            Set(value As Double)
                SetValue(FontSizeProperty, value)
            End Set
        End Property

        Public Shared ReadOnly FontSizeProperty As DependencyProperty = DependencyProperty.Register( _
            "FontSize", _
            GetType(Double), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                CDbl(48.0), _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property Italic As Boolean
            Get
                Return CType(GetValue(ItalicProperty), Boolean)
            End Get
            Set(value As Boolean)
                SetValue(ItalicProperty, value)
            End Set
        End Property

        Public Shared ReadOnly ItalicProperty As DependencyProperty = DependencyProperty.Register( _
            "Italic", _
            GetType(Boolean), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                False, _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property Stroke As Brush
            Get
                Return CType(GetValue(StrokeProperty), Brush)
            End Get
            Set(value As Brush)
                SetValue(StrokeProperty, value)
            End Set
        End Property

        Public Shared ReadOnly StrokeProperty As DependencyProperty = DependencyProperty.Register( _
            "Stroke", _
            GetType(Brush), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                New SolidColorBrush(Colors.Teal), _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property StrokeThickness As Double
            Get
                Return CType(GetValue(StrokeThicknessProperty), Double)
            End Get
            Set(value As Double)
                SetValue(StrokeThicknessProperty, value)
            End Set
        End Property

        Public Shared ReadOnly StrokeThicknessProperty As DependencyProperty = DependencyProperty.Register( _
            "StrokeThickness", _
            GetType(Double), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                CDbl(0), _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Property Text As String
            Get
                Return CType(GetValue(TextProperty), String)
            End Get
            Set(value As String)
                SetValue(TextProperty, value)
            End Set
        End Property

        Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register( _
            "Text", _
            GetType(String), _
            GetType(OutlinedText), _
            New FrameworkPropertyMetadata( _
                "", _
                FrameworkPropertyMetadataOptions.AffectsRender, _
                New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
                Nothing _
            ) _
        )

        Public Sub AddChild(value As Object) Implements System.Windows.Markup.IAddChild.AddChild

        End Sub

        Public Sub AddText(text As String) Implements System.Windows.Markup.IAddChild.AddText
            Me.Text = text
        End Sub
    End Class
End Namespace

as already mentioned, convert text to path

FormattedText t = new FormattedText
(
    "abcxyz",
    CultureInfo.GetCultureInfo("en-us"),
    FlowDirection.LeftToRight,
    new Typeface(
    new FontFamily("Arial"),
    new FontStyle(),
    new FontWeight(),
    new FontStretch()),
    20,
    Brushes.Transparent
);

Geometry g = t.BuildGeometry(new System.Windows.Point(0, 0));

Path p = new Path();
p.Fill = Brushes.White;
p.Stroke = Brushes.Black;
p.StrokeThickness = 1;
p.Data = g;

以下は私のよりイディオム的なWPFです。フル機能のWPFです。 これは、あなたが期待するすべてのものをサポートします。

  • ストレッチとスタイルを含むフォント関連のすべてのプロパティ
  • テキストの配置(left、right、center、justify)
  • テキストラッピング
  • テキストトリミング
  • テキストの装飾(下線、ストライクなど)

ここでは、それを使って達成できるものの簡単な例を示します。

<local:OutlinedTextBlock FontFamily="Verdana" FontSize="20pt" FontWeight="ExtraBold" TextWrapping="Wrap" StrokeThickness="1" Stroke="{StaticResource TextStroke}" Fill="{StaticResource TextFill}">
    Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit
</local:OutlinedTextBlock>

結果は:

コントロールのコードは次のとおりです。

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;

[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));
    }
}

私は@Javier G.を修正しました。

  • ストロークの位置は、center、outsideまたはinsideのいずれかです。デフォルトは外側です。

  • 塗りつぶしは透明にすることができます。

センター:

外側:

内部:

コード:

using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;

namespace WpfApp2
{
    public enum StrokePosition
    {
        Center,
        Outside,
        Inside
    }

    [ContentProperty("Text")]
    public class OutlinedTextBlock : FrameworkElement
    {
        private void UpdatePen()
        {
            _Pen = new Pen(Stroke, StrokeThickness)
            {
                DashCap = PenLineCap.Round,
                EndLineCap = PenLineCap.Round,
                LineJoin = PenLineJoin.Round,
                StartLineCap = PenLineCap.Round
            };

            if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
            {
                _Pen.Thickness = StrokeThickness * 2;
            }

            InvalidateVisual();
        }

        public StrokePosition StrokePosition
        {
            get { return (StrokePosition)GetValue(StrokePositionProperty); }
            set { SetValue(StrokePositionProperty, value); }
        }

        public static readonly DependencyProperty StrokePositionProperty =
            DependencyProperty.Register("StrokePosition", 
                typeof(StrokePosition),
                typeof(OutlinedTextBlock),
                new FrameworkPropertyMetadata(StrokePosition.Outside, FrameworkPropertyMetadataOptions.AffectsRender));

        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;
        private Pen _Pen;
        private PathGeometry _clipGeometry;

        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)GetValue(TextDecorationsProperty); }
            set { 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); }
        }

        public OutlinedTextBlock()
        {
            UpdatePen();
            TextDecorations = new TextDecorationCollection();
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            EnsureGeometry();

            drawingContext.DrawGeometry(Fill, null, _TextGeometry);

            if (StrokePosition == StrokePosition.Outside)
            {
                drawingContext.PushClip(_clipGeometry);
            }
            else if (StrokePosition == StrokePosition.Inside)
            {
                drawingContext.PushClip(_TextGeometry);
            }

            drawingContext.DrawGeometry(null, _Pen, _TextGeometry);

            if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
            {
                drawingContext.Pop();
            }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            EnsureFormattedText();

            // constrain the formatted text according to the available size

            double w = availableSize.Width;
            double h = availableSize.Height;

            // 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
            _FormattedText.MaxTextWidth = Math.Min(3579139, w);
            _FormattedText.MaxTextHeight = Math.Max(0.0001d, h);

            // return the desired size
            return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            EnsureFormattedText();

            // update the formatted text with the final size
            _FormattedText.MaxTextWidth = finalSize.Width;
            _FormattedText.MaxTextHeight = Math.Max(0.0001d, finalSize.Height);

            // need to re-generate the geometry now that the dimensions have changed
            _TextGeometry = null;
            UpdatePen();

            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 (_FormattedText != null)
            {
                return;
            }

            _FormattedText = new FormattedText(
              Text ?? "",
              CultureInfo.CurrentUICulture,
              FlowDirection,
              new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
              FontSize,
              Brushes.Black);

            UpdateFormattedText();
        }

        private void UpdateFormattedText()
        {
            if (_FormattedText == null)
            {
                return;
            }

            _FormattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
            _FormattedText.TextAlignment = TextAlignment;
            _FormattedText.Trimming = TextTrimming;

            _FormattedText.SetFontSize(FontSize);
            _FormattedText.SetFontStyle(FontStyle);
            _FormattedText.SetFontWeight(FontWeight);
            _FormattedText.SetFontFamily(FontFamily);
            _FormattedText.SetFontStretch(FontStretch);
            _FormattedText.SetTextDecorations(TextDecorations);
        }

        private void EnsureGeometry()
        {
            if (_TextGeometry != null)
            {
                return;
            }

            EnsureFormattedText();
            _TextGeometry = _FormattedText.BuildGeometry(new Point(0, 0));

            if (StrokePosition == StrokePosition.Outside)
            {
                var boundsGeo = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
                _clipGeometry = Geometry.Combine(boundsGeo, _TextGeometry, GeometryCombineMode.Exclude, null);
            }           
        }
    }
}

Usege:

<Grid Margin="12" Background="Bisque">
    <local:OutlinedTextBlock Stroke="Red" 
                             ClipToBounds="False"
                             FontSize="56" 
                             Fill="Transparent"
                             StrokePosition="Inside"
                             StrokeThickness="1" Text=" abc">
    </local:OutlinedTextBlock>
</Grid>

これをMeasureOverrideに追加する必要があったので、レイアウトの丸めを使用している間に1行のテキストが表示されました。テキストが折り返してもうまくいきました。

// return the desired size
return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));






xaml