Применить обводку к текстовому блоку в WPF

Как применить обводку (контур вокруг текста) к текстовому блоку в xaml в WPF?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
67
0
54 384
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

В Blend вы можете преобразовать TextBlock в Path, а затем использовать обычные свойства Stroke. Но я предполагаю, что вы хотели что-то, что можно было бы сделать динамичным ...

В противном случае я бы подумал, что это должен быть какой-то эффект растрового изображения или специальная кисть.

Сам <TextBlock> не имеет декоративных атрибутов. Я бы поместил его на <Canvas> с <Rectangle> и применил обводку там.

вы должны обернуть TextBlock рамкой ... примерно так:

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

в случае, если вы спрашиваете, как обвести фактические буквы (а не весь TextBlock), вы можете захотеть посмотреть, используя BitmapEffect of Glow и установив для параметров Glow желаемый цвет обводки и т. д. В противном случае вам, возможно, придется создать что-то свое.

Стоит отметить, что BitmapEffect теперь устарел - он был заменен классом Effect без переноса всех функций из BitmapEffect :(

bwall 02.07.2019 17:30

Нашел. По-видимому, сделать это не так просто, в WPF нет встроенного текста Stroke (что-то вроде большой недостающей функции, если вы спросите меня). Сначала создайте собственный класс:

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

namespace CustomXaml
{

public class OutlinedText : FrameworkElement, IAddChild
{
    #region Private Fields

    private Geometry _textGeometry;

    #endregion

    #region Private Methods

    /// <summary>
    /// Invoked when a dependency property has changed. Generate a new FormattedText object to display.
    /// </summary>
    /// <param name = "d">OutlineText object whose property was updated.</param>
    /// <param name = "e">Event arguments for the dependency property.</param>
    private static void OnOutlineTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((OutlinedText)d).CreateText();
    }

    #endregion


    #region FrameworkElement Overrides

    /// <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)
    {
        CreateText();
        // Draw the outline based on the properties that are set.
        drawingContext.DrawGeometry(Fill, new Pen(Stroke, StrokeThickness), _textGeometry);

    }

    /// <summary>
    /// Create the outline geometry based on the formatted text.
    /// </summary>
    public void CreateText()
    {
        FontStyle fontStyle = FontStyles.Normal;
        FontWeight fontWeight = FontWeights.Medium;

        if (Bold == true) fontWeight = FontWeights.Bold;
        if (Italic == true) fontStyle = FontStyles.Italic;

        // Create the formatted text based on the properties set.
        FormattedText formattedText = new FormattedText(
            Text,
            CultureInfo.GetCultureInfo("en-us"),                
            FlowDirection.LeftToRight,
            new Typeface(Font, fontStyle, fontWeight, FontStretches.Normal),                
            FontSize,
            Brushes.Black // This brush does not matter since we use the geometry of the text. 
            );

        // Build the geometry object that represents the text.
        _textGeometry = formattedText.BuildGeometry(new Point(0, 0));




        //set the size of the custome control based on the size of the text
        this.MinWidth = formattedText.Width;
        this.MinHeight = formattedText.Height;

    }

    #endregion

    #region DependencyProperties

    /// <summary>
    /// Specifies whether the font should display Bold font weight.
    /// </summary>
    public bool Bold
    {
        get
        {
            return (bool)GetValue(BoldProperty);
        }

        set
        {
            SetValue(BoldProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Bold dependency property.
    /// </summary>
    public static readonly DependencyProperty BoldProperty = DependencyProperty.Register(
        "Bold",
        typeof(bool),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
            false,
            FrameworkPropertyMetadataOptions.AffectsRender,
            new PropertyChangedCallback(OnOutlineTextInvalidated),
            null
            )
        );

    /// <summary>
    /// Specifies the brush to use for the fill of the formatted text.
    /// </summary>
    public Brush Fill
    {
        get
        {
            return (Brush)GetValue(FillProperty);
        }

        set
        {
            SetValue(FillProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Fill dependency property.
    /// </summary>
    public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
        "Fill",
        typeof(Brush),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
            new SolidColorBrush(Colors.LightSteelBlue),
            FrameworkPropertyMetadataOptions.AffectsRender,
            new PropertyChangedCallback(OnOutlineTextInvalidated),
            null
            )
        );

    /// <summary>
    /// The font to use for the displayed formatted text.
    /// </summary>
    public FontFamily Font
    {
        get
        {
            return (FontFamily)GetValue(FontProperty);
        }

        set
        {
            SetValue(FontProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Font dependency property.
    /// </summary>
    public static readonly DependencyProperty FontProperty = DependencyProperty.Register(
        "Font",
        typeof(FontFamily),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
            new FontFamily("Arial"),
            FrameworkPropertyMetadataOptions.AffectsRender,
            new PropertyChangedCallback(OnOutlineTextInvalidated),
            null
            )
        );

    /// <summary>
    /// The current font size.
    /// </summary>
    public double FontSize
    {
        get
        {
            return (double)GetValue(FontSizeProperty);
        }

        set
        {
            SetValue(FontSizeProperty, value);
        }
    }

    /// <summary>
    /// Identifies the FontSize dependency property.
    /// </summary>
    public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register(
        "FontSize",
        typeof(double),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
             (double)48.0,
             FrameworkPropertyMetadataOptions.AffectsRender,
             new PropertyChangedCallback(OnOutlineTextInvalidated),
             null
             )
        );


    /// <summary>
    /// Specifies whether the font should display Italic font style.
    /// </summary>
    public bool Italic
    {
        get
        {
            return (bool)GetValue(ItalicProperty);
        }

        set
        {
            SetValue(ItalicProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Italic dependency property.
    /// </summary>
    public static readonly DependencyProperty ItalicProperty = DependencyProperty.Register(
        "Italic",
        typeof(bool),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
             false,
             FrameworkPropertyMetadataOptions.AffectsRender,
             new PropertyChangedCallback(OnOutlineTextInvalidated),
             null
             )
        );

    /// <summary>
    /// Specifies the brush to use for the stroke and optional hightlight of the formatted text.
    /// </summary>
    public Brush Stroke
    {
        get
        {
            return (Brush)GetValue(StrokeProperty);
        }

        set
        {
            SetValue(StrokeProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Stroke dependency property.
    /// </summary>
    public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
        "Stroke",
        typeof(Brush),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
             new SolidColorBrush(Colors.Teal),
             FrameworkPropertyMetadataOptions.AffectsRender,
             new PropertyChangedCallback(OnOutlineTextInvalidated),
             null
             )
        );

    /// <summary>
    ///     The stroke thickness of the font.
    /// </summary>
    public ushort StrokeThickness
    {
        get
        {
            return (ushort)GetValue(StrokeThicknessProperty);
        }

        set
        {
            SetValue(StrokeThicknessProperty, value);
        }
    }

    /// <summary>
    /// Identifies the StrokeThickness dependency property.
    /// </summary>
    public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
        "StrokeThickness",
        typeof(ushort),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
             (ushort)0,
             FrameworkPropertyMetadataOptions.AffectsRender,
             new PropertyChangedCallback(OnOutlineTextInvalidated),
             null
             )
        );

    /// <summary>
    /// Specifies the text string to display.
    /// </summary>
    public string Text
    {
        get
        {
            return (string)GetValue(TextProperty);
        }

        set
        {
            SetValue(TextProperty, value);
        }
    }

    /// <summary>
    /// Identifies the Text dependency property.
    /// </summary>
    public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
        "Text",
        typeof(string),
        typeof(OutlinedText),
        new FrameworkPropertyMetadata(
             "",
             FrameworkPropertyMetadataOptions.AffectsRender,
             new PropertyChangedCallback(OnOutlineTextInvalidated),
             null
             )
        );

    public void AddChild(Object value)
    {

    }

    public void AddText(string value)
    {
        Text = value;
    }

    #endregion
}
}

Вы можете ссылаться на него в своем xaml.

<Page xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:customControls = "clr-namespace:CustomXaml;assembly=CustomXaml">
    <Grid>
        <customControls:OutlinedText x:Name = "TextContent" Fill = "#ffffffff" FontSize = "28"     
Bold = "True" Stroke = "Black" StrokeThickness = "1" Text = "Back" Margin = "10,0,10,0" 
HorizontalAlignment = "Center" VerticalAlignment = "Center" Height = "Auto" Width = "Auto" />
    </Grid>
</Page>

У меня это сработало очень хорошо, но мне также понадобился перенос слов и выравнивание текста. Это легко сделать с помощью свойств FormattedText.MaxTextWidth и FormattedText.TextAlignment.

Twelve47 30.04.2010 18:29

Потрясающие! Обратите внимание, что IAddChild устарел. Вместо этого присвойте классу [ContentProperty ("Text")], тогда вы можете установить текст непосредственно в XAML, как в: <customControls: OutlinedText ...> Текст! </ CustomControls: OutlinedText>

Skrymsli 06.12.2011 07:04

Это круто. Поскольку этот ответ немного устарел, есть ли другой способ добиться этого или это все еще лучший подход? +1

Patrick Magee 20.10.2015 12:06

Подобные вещи - это не «особенности», а «эффекты».

Ravior 29.08.2017 13:57

@PatrickMagee Я из 2019 года, и это отлично работает! (.NET Framework 4.6.1)

N. M. 22.10.2019 03:56

«Как: создать контурный текст» в MSDN содержит всю необходимую информацию.

Ссылка больше не действительна.

LawMan 02.07.2015 17:17

Попробуйте это: [ссылка (msdn.microsoft.com/en-us/library/vstudio/ms745816(v=vs‌ .90) .aspx)

Brian THOMAS 03.08.2015 19:03

Еще один новый URL: msdn.microsoft.com/en-us/library/ms745816(v=vs.85).aspx

Chris 21.08.2017 20:07

Вместо этого можно было бы просто использовать ярлык. У него больше свойств, с которыми вы можете играть. Пример:

  <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>

Верно, но BorderBrush и BorderThickness в этом случае применяются к границе метки, а не обрисовывают фактический текст.

bwall 02.07.2019 17:26

Это мне очень помогло! На всякий случай, если кому-то это понадобится в будущем, вот версия VB (StrokeThickness сделан двойным и добавлено свойство Underline):

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
Ответ принят как подходящий

Ниже приведен мой более идиоматичный полнофункциональный WPF подход к этому вопросу. Он поддерживает почти все, что вы ожидаете, в том числе:

  • все свойства, связанные со шрифтом, включая растяжение и стиль
  • выравнивание текста (по левому краю, по центру, по ширине)
  • перенос текста
  • обрезка текста
  • текстовые украшения (подчеркивание, зачеркивание и т. д.)

Вот простой пример того, чего можно достичь с его помощью:

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

это было бы фантастически, если бы обводка была за пределами букв. Спасибо, что поделился.

discorax 15.06.2012 02:02

При использовании TemplateBinding со свойством Text я получаю исключение нулевого объекта. Похоже, что это из функции MeasureOverride, где formattedText имеет значение null

Chris Klepeis 18.07.2012 21:12

Я добавил this сразу после this.EnsureFormattedText (); в MeasureOverride

Chris Klepeis 18.07.2012 21:28

похоже, что вам не хватает "using System;" так что ваша ссылка на математику разрешается.

Alan Jackson 09.08.2013 18:31

Я получаю сообщение об ошибке: значение свойства MaxTextHeight должно быть больше нуля. Доступная высота 0, это может быть проблемой.

Wouter 18.09.2013 19:11

@Wouter: да, просто добавьте вызов Math.Max при назначении MaxTextHeight. Я обновлю свой ответ.

Kent Boogaart 19.09.2013 04:16

Я пытаюсь использовать это с текстовыми блоками, которые привязаны к модели представления, похоже, когда он выполняет расчет, текст всегда равен нулю, а в ArrangeOverride выдается ошибка при попытке установить MaxTextHeight на 0. Есть мысли?

user1618236 13.03.2014 02:25

Я попробовал уловить параметр ArrangeOverride для MaxTextWidth и MaxTextHeight, и, похоже, он снова работает для меня, даже при использовании модели просмотра. Я мог бы просто сначала проверить, является ли он 0, и пропустить этот шаг в этом случае. Его вызывают несколько раз, и в какой-то момент все становится правильно.

Jon Dosmann 10.04.2014 18:08

Я напрямую расширил ваш класс в мой ответ на Другой вопрос. Спасибо за этот контрольный образец :-)

O. R. Mapper 19.08.2014 20:19

Как мне это использовать? Получаю ошибку The namespace prefix "local" is not defined.

Jason Axelrod 23.10.2014 01:54

@Jason: сопоставьте локальное пространство имен xml с пространством имен, в которое вы вставили код. Что-то вроде xmlns:local = "clr-namespace:Foo.Bar"

Kent Boogaart 23.10.2014 11:38

Хорошо, у меня есть рабочий ... вопрос ... Как установить вертикальное выравнивание контента?

Jason Axelrod 23.10.2014 12:59

Как можно настроить горизонтальное и вертикальное выравнивание содержимого? Я заметил, что эти два свойства отсутствуют ... Также; почему бы просто не создать новый объект класса на основе класса TextBlock и добавить к нему эту новую функциональность? (Я сам пытаюсь использовать то, что было предоставлено здесь, поэтому мне любопытно, есть ли что-то, что мешает вам (или мне) сделать это).

Will 08.04.2015 20:27

Не изменяя исходный код для добавления выравнивания содержимого, просто поместите текст в оболочку, которая будет использоваться для его выравнивания по мере необходимости. На ум приходит сетка: вам просто нужно что-то, что заполнит ее контейнер и сможет обеспечить выравнивание контента.

Richard Robertson 26.07.2015 06:57

Я собираюсь использовать что-то подобное, чтобы расширить метку и добиться аналогичного эффекта. Причина в основном придирчива (мне не нравится, как свойства шрифта текста выложены в пользовательском интерфейсе, и мне не нравится, что я не могу предоставить фон). В основном я просто хочу расширить класс и предоставить свойство Stroke и StrokeWidth (конечно, при поддержке соответствующих свойств зависимостей) ... Теперь мне просто нужно выяснить, как нарисовать обводку вокруг в переопределении OnRender ...

Will 12.03.2016 01:33

@ m1m1k, несмотря на почти 50 голосов за и множество благодарностей? Считаете, что, возможно, ошибка ваша?

Kent Boogaart 14.07.2016 03:32

@KentBoogaart Не могу обработать NULL-текст (что может произойти с привязкой). Мне нужно было добавить if null return string.empty в Text getter.

patrick 12.01.2017 19:05

Незначительная модификация кода Кента Бугарта, в котором, хотя и потрясающе, отсутствует небольшая деталь. Это, вероятно, немного неточно, поскольку он будет измерять только заливку, а не обводку, но добавление пары строк к OnRender()Viewbox сможет понять, что с ним делать (хотя, как и с TextBox, не в предварительном просмотре ).

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

    this.Width = this.formattedText.Width;
    this.Height = this.formattedText.Height;

    drawingContext.DrawGeometry(this.Fill, new Pen(this.Stroke, this.StrokeThickness), this.textGeometry);
}

Я использую это с двумя слоями текста, чтобы обводка выглядела только снаружи, как показано ниже. Очевидно, это не сработает сразу, поскольку относится к определенным изображениям и шрифтам.

<Viewbox Stretch = "UniformToFill" Margin = "0" Grid.Column = "1">
    <bd:OutlinedText x:Name = "LevelTitleStroke" Text = "Level" FontSize = "80pt" FontFamily = "/fonts/papercuts-2.ttf#Paper Cuts 2" Grid.Row = "1" TextAlignment = "Center" IsHitTestVisible = "False" StrokeThickness = "15">
        <bd:OutlinedText.Stroke>
            <ImageBrush ImageSource = "/WpfApplication1;component/GrungeMaps/03DarkBlue.jpg" Stretch = "None" />
        </bd:OutlinedText.Stroke>
    </bd:OutlinedText>
</Viewbox>
<Viewbox Stretch = "UniformToFill" Margin = "0" Grid.Column = "1">
    <bd:OutlinedText x:Name = "LevelTitleFill" Text = "Level" FontSize = "80pt" FontFamily = "/fonts/papercuts-2.ttf#Paper Cuts 2" Grid.Row = "1" TextAlignment = "Center" IsHitTestVisible = "False">
        <bd:OutlinedText.Fill>
            <ImageBrush ImageSource = "/WpfApplication1;component/GrungeMaps/03Red.jpg" Stretch = "None" />
        </bd:OutlinedText.Fill>
    </bd:OutlinedText>
</Viewbox>

Я использовал решение Кента в своем настраиваемом элементе управления. Это привело к нулевому исключению при использовании привязки шаблона к свойству text.

Мне пришлось изменить функцию MeasureOverride следующим образом:

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

        if (this.formattedText == null)
        {
            this.formattedText = new FormattedText(
                                (this.Text == null) ? "" : this.Text,
                                CultureInfo.CurrentUICulture,
                                this.FlowDirection,
                                new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, FontStretches.Normal),
                                this.FontSize,
                                Brushes.Black);
        }

        // 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
        this.formattedText.MaxTextWidth = Math.Min(3579139, availableSize.Width);
        this.formattedText.MaxTextHeight = availableSize.Height;

        // return the desired size
        return new Size(this.formattedText.Width, this.formattedText.Height);
    }

Следует отметить, что я не проверял это полностью.

Я тоже пытался добиться чего-то подобного. Упомянутые здесь классы были великолепны, но не совсем то, что я искал, потому что они действительно выглядели правильно, только если текст был достаточно большим. Текст, который я пытался отобразить, имел размер шрифта 10-11, а обводка была настолько большой, что буквы как бы сливались вместе.

Чтобы уточнить, этот текст должен был быть наложен на определяемое пользователем изображение, которое могло иметь разные цвета, и я хотел убедиться, что этот текст будет отображаться.

Я не знаю, является ли это наилучшей практикой или нет, но это, по крайней мере, достигло желаемого результата (на основе Эта статья):

<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>

Затем для фактических текстовых блоков я объединил два текстовых блока с внешним стилем, потому что один был слишком легким, и один текстовый блок с внутренним стилем:

<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, который выглядел нормально при использовании только двух текстовых полей (хотя добавление дополнительных эффектов 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>

Я использую DropShadowEffect с ShadowDept = "0" и BlurRadius = "1", которые идеально рисуют контур, не указывая дважды один и тот же TextBlock, как вы показали.

kurakura88 18.07.2018 13:18

Если применяется для кого-либо, вот простое решение, использующее ТОЛЬКО XAML. Я не уверен, работает оно лучше или хуже, но, на мой взгляд, оно выглядит лучше, чем любое другое решение, указанное выше. Я оборачиваю его в стиль (и шаблон) ContentControl, следуя этому примеру старой школы :) 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>

Мне пришлось добавить это в MeasureOverride, чтобы он отображал отдельные строки текста при использовании округления макета. Тем не менее, он работал нормально, когда текст переносился.

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

Я изменил ответ, получивший наибольшее количество голосов, несколькими исправлениями, в том числе:

  • Исправьте так, чтобы текст с одной строкой отображался при использовании UseLayoutRounding.

  • Контуры будут отображаться вне текста, а не в середине граница.

  • Перо создается только один раз, а не на каждом рендере.

  • Исправьте ошибку, если для текста задано значение null.

  • Исправьте так, чтобы в контуре использовались правильные круглые заглушки.

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
{
    private void UpdatePen() {
        _Pen = new Pen(Stroke, StrokeThickness) {
            DashCap = PenLineCap.Round,
            EndLineCap = PenLineCap.Round,
            LineJoin = PenLineJoin.Round,
            StartLineCap = PenLineCap.Round
        };

        InvalidateVisual();
    }

    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, StrokePropertyChangedCallback));

    private static void StrokePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) {
        (dependencyObject as OutlinedTextBlock)?.UpdatePen();
    }

    public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
      "StrokeThickness",
      typeof(double),
      typeof(OutlinedTextBlock),
      new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));

    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;

    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(null, _Pen, _TextGeometry);
        drawingContext.DrawGeometry(Fill, null, _TextGeometry);
    }

    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;

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

Спасибо за то, что поделился этим, добавляет совершенства к предыдущему решению. Почему никто не проголосовал за вас?

Luca 15.02.2016 17:10

Думаю, вопрос слишком старый, WPF уже не такой уж и популярный или сочетание того и другого? :)

Javier G. 18.02.2016 01:57

WPF ГОРЯЧИЙ! так что остается только первый шанс

Luca 18.02.2016 19:06

Проголосовали. Однако рекомендуется удалить строку using Skirmish.Util.Helpers.

Seanba 20.07.2016 20:59

Спасибо, я это пропустил.

Javier G. 21.07.2016 21:14

Я все еще получаю очертания в середине границы. <local:OutlinedTextBlock Stroke = "Red" FontSize = "22" Fill = "Transparent" StrokeThickness = "2"> abc </local:OutlinedTextBlock>

google dev 03.04.2018 14:53

Да, это потому, что вы используете прозрачную заливку, поэтому заливка не может фактически заполнить внутреннюю половину контура.

Javier G. 27.06.2018 20:14

Я модифицировал @ Хавьер Г. ответ

  • Положение обводки может быть: по центру, снаружи или внутри, по умолчанию находится снаружи.

  • Заливка может быть прозрачной.

Центр:

Снаружи:

Внутри:

Код:

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

Использование:

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

Это отличное решение. К сожалению, есть только один недостаток - левая и правая часть штриха могут быть обрезаны снаружи. Это событие, для которого ClipToBounds установлено значение false. Пример: niftymonkey.uk/hpserverpublic/Images/Http_Linked/StrokeClip.‌ png

stigzler 02.03.2020 02:35

Нашел быстрый + грязный хак. В EnsureGeometry измените на эту строку: Dim boundsGeo = New RectangleGeometry(New Rect(-(2 * StrokeThickness), -(2 * StrokeThickness), ActualWidth + (4 * StrokeThickness), ActualHeight + (4 * StrokeThickness)))

stigzler 02.03.2020 02:55

как уже упоминалось, преобразовать текст в путь

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;

Другой вариант - использовать обычный текстовый блок, но применить к нему собственный эффект.

Комбинируя эти Учебник по шейдерам и Фильтр Prewitt Edge Detection, мне удалось получить приличный эффект контура вокруг текста. Хотя у него есть преимущество отрисовки с использованием графического процессора и применение к ЛЮБОМУ UIElement, я бы сказал, что ответ @Kent Boogaart выглядит немного лучше, а EdgeResponse привередлив - мне пришлось много поиграть с ним, чтобы получить красивый контур.

Конечный результат в XAML:

<Grid>
    <Grid.Resources>
        <local:EdgeDetectionEffect x:Key = "OutlineEffect"
            x:Shared = "false"
            EdgeResponse = ".44"
            ActualHeight = "{Binding RelativeSource = {RelativeSource AncestorType=TextBlock}, Path=ActualHeight}"
            ActualWidth = "{Binding RelativeSource = {RelativeSource AncestorType=TextBlock}, Path=ActualWidth}"/>
    </Grid.Resources>
    <TextBlock Text = "The Crazy Brown Fox Jumped Over the Lazy Dog."
        FontWeight = "Bold"
        FontSize = "25"
        Foreground = "Yellow"
        Effect = "{StaticResource OutlineEffect}"/>
</Grid>

Чтобы создать собственный эффект, я сначала создал файл EdgeDetectionColorEffect.fx (hdld) - это код, который GPU использует для фильтрации изображения. Я скомпилировал его в командной строке Visual Studio с помощью команды:

fxc / T ps_2_0 / E main /Focc.ps EdgeDetectionColorEffect.fx

sampler2D Input : register(s0);
float ActualWidth : register(c0);
float ActualHeight : register(c1);
float4 OutlineColor : register(c2);
float EdgeDetectionResponse : register(c3);

float4 GetNeighborPixel(float2 pixelPoint, float xOffset, float yOffset)
{
    float2 NeighborPoint = {pixelPoint.x + xOffset, pixelPoint.y + yOffset};
    return tex2D(Input, NeighborPoint);
}

// pixel locations:
// 00 01 02
// 10 11 12
// 20 21 22
float main(float2 pixelPoint : TEXCOORD) : COLOR
{

     float wo = 1 / ActualWidth; //WidthOffset
     float ho = 1 / ActualHeight; //HeightOffset

    float4 c00 = GetNeighborPixel(pixelPoint, -wo, -ho); // color of the pixel up and to the left of me.
    float4 c01 = GetNeighborPixel(pixelPoint,  00, -ho);        
    float4 c02 = GetNeighborPixel(pixelPoint,  wo, -ho);
    float4 c10 = GetNeighborPixel(pixelPoint, -wo,   0);
    float4 c11 = tex2D(Input, pixelPoint); // this is the current pixel
    float4 c12 = GetNeighborPixel(pixelPoint,  wo,   0);
    float4 c20 = GetNeighborPixel(pixelPoint, -wo,  ho);
    float4 c21 = GetNeighborPixel(pixelPoint,   0,  ho);
    float4 c22 = GetNeighborPixel(pixelPoint,  wo,  ho);

    float t00 = c00.r + c00.g + c00.b; //total of color channels
    float t01 = c01.r + c01.g + c01.b;
    float t02 = c02.r + c02.g + c02.b;
    float t10 = c10.r + c10.g + c10.b;
    float t11 = c11.r + c11.g + c11.b;
    float t12 = c12.r + c12.g + c12.b;
    float t20 = c20.r + c20.g + c20.b;
    float t21 = c21.r + c21.g + c21.b;
    float t22 = c22.r + c22.g + c22.b;

    //Prewitt - convolve the 9 pixels with:
    //       01 01 01        01 00 -1
    // Gy =  00 00 00   Gx = 01 00 -1
    //       -1 -1 -1        01 00 -1

    float gy = 0.0;  float gx = 0.0;
    gy += t00;       gx += t00;
    gy += t01;       gx += t10;
    gy += t02;       gx += t20;
    gy -= t20;       gx -= t02;
    gy -= t21;       gx -= t12;
    gy -= t22;       gx -= t22;

    if ((gy*gy + gx*gx) > EdgeDetectionResponse)
    {
        return OutlineColor;
    }

    return c11;
}

Вот класс эффекта wpf:

public class EdgeDetectionEffect : ShaderEffect
{
    private static PixelShader _shader = new PixelShader { UriSource = new Uri("path to your compiled shader probably called cc.ps", UriKind.Absolute) };

public EdgeDetectionEffect()
{
    PixelShader = _shader;
    UpdateShaderValue(InputProperty);
    UpdateShaderValue(ActualHeightProperty);
    UpdateShaderValue(ActualWidthProperty);
    UpdateShaderValue(OutlineColorProperty);
    UpdateShaderValue(EdgeResponseProperty);
}

public Brush Input
{
     get => (Brush)GetValue(InputProperty);
     set => SetValue(InputProperty, value);
}
public static readonly DependencyProperty InputProperty = 
    ShaderEffect.RegisterPixelShaderSamplerProperty(nameof(Input), 
    typeof(EdgeDetectionEffect), 0);

public double ActualWidth
{
     get => (double)GetValue(ActualWidthProperty);
     set => SetValue(ActualWidthProperty, value);
}
public static readonly DependencyProperty ActualWidthProperty =
    DependencyProperty.Register(nameof(ActualWidth), typeof(double), typeof(EdgeDetectionEffect),
    new UIPropertyMetadata(1.0, PixelShaderConstantCallback(0)));

public double ActualHeight
{
     get => (double)GetValue(ActualHeightProperty);
     set => SetValue(ActualHeightProperty, value);
}
public static readonly DependencyProperty ActualHeightProperty =
    DependencyProperty.Register(nameof(ActualHeight), typeof(double), typeof(EdgeDetectionEffect),
    new UIPropertyMetadata(1.0, PixelShaderConstantCallback(1)));

public Color OutlineColor
{
     get => (Color)GetValue(OutlineColorProperty);
     set => SetValue(OutlineColorProperty, value);
}
public static readonly DependencyProperty OutlineColorProperty=
    DependencyProperty.Register(nameof(OutlineColor), typeof(Color), typeof(EdgeDetectionEffect),
    new UIPropertyMetadata(Colors.Black, PixelShaderConstantCallback(2)));

public double EdgeResponse
{
     get => (double)GetValue(EdgeResponseProperty);
     set => SetValue(EdgeResponseProperty, value);
}
public static readonly DependencyProperty EdgeResponseProperty =
    DependencyProperty.Register(nameof(EdgeResponse), typeof(double), typeof(EdgeDetectionEffect),
    new UIPropertyMetadata(4.0, PixelShaderConstantCallback(3)));
}

Другие вопросы по теме