如何将一个textblock属性继承到c#中的自定义控件?

时间:2022-09-02 13:20:06

I have a custom wpf control. It is basically a textblock which has an ability to apply fill and stroke to the text. It is already inherited by a class. The problem is that it does not have some textblock properties like fontfamily. I want to inherit this control with textblock so it can use its all properties. The custom control code is given below

我有一个定制的wpf控件。它基本上是一个文本块,它具有对文本进行填充和描边的能力。它已经被一个类继承了。问题是它没有像fontfamily这样的文本块属性。我想用textblock继承这个控件,这样它就可以使用它的所有属性。自定义控制代码如下所示。

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

7 个解决方案

#1


3  

First we need to understand the requirements and, from the question as well as from various answers and comments, I list several:

首先,我们需要了解需求,从问题以及各种答案和评论中,我列出了几个:

1) I want to have an outline around my textblock text, drawn with my desired stroke thickness and color. That has been answered here: How can I extend a TextBlock to display Outlined Text? . Use DropShadowEffect on the textblock.

1)我想在我的textblock文本周围有一个大纲,用我想要的笔画厚度和颜色画出来。这里已经回答了这个问题:如何扩展一个文本块来显示所概述的文本?。在文本块上使用DropShadowEffect。

2) I want to control the outline up to the distance from the text and the brush that I am going to use, not just a simple color, etc. I basically want to draw anything I want on my textblock, while getting all of its functionality. So you need to adorn the TextBlock with your own graphics. Then use an Adorner.

2)我想要把大纲控制在我要使用的文本和画笔的距离上,而不仅仅是简单的颜色等等。我基本上想在我的textblock上画任何我想要的东西,同时得到它的所有功能。所以你需要用自己的图形来装饰TextBlock。然后使用一个装饰器。

3) The most complex requirement seems to be "a control that does everything a TextBlock does, but with a stroke that I can control completely". For this there have been several attempts: trying to recreate TextBlock from FrameworkElement, trying to inherit from TextBlock, I even copied all the miriad of internal sealed classes that are used in TextBlock and tried to rewrite it as an open control. Just inherit from TextBlock and add the Adorner code inside.

3)最复杂的需求似乎是“一种控制,它能做所有文本块做的事情,但我可以完全控制它”。为此,有几次尝试:试图从FrameworkElement重新创建TextBlock,试图从TextBlock继承,我甚至复制了TextBlock中使用的所有内部密封类的miriad,并试图将其重写为一个开放控件。从TextBlock继承,然后在里面添加装饰代码。

As a solution for 3), here is the code that I made to replicate the original code, which may now be changed as desired, and using a TextBlock:

作为对3的解决方案,这里是我用来复制原始代码的代码,现在可以根据需要修改它,并使用一个TextBlock:

public class StrokeAdorner : Adorner
{
    private TextBlock _textBlock;

    private Brush _stroke;
    private ushort _strokeThickness;

    public Brush Stroke
    {
        get
        {
            return _stroke;
        }

        set
        {
            _stroke = value;
            _textBlock.InvalidateVisual();
            InvalidateVisual();
        }
    }

    public ushort StrokeThickness
    {
        get
        {
            return _strokeThickness;
        }

        set
        {
            _strokeThickness = value;
            _textBlock.InvalidateVisual();
            InvalidateVisual();
        }
    }

    public StrokeAdorner(UIElement adornedElement) : base(adornedElement)
    {
        _textBlock = adornedElement as TextBlock;
        ensureTextBlock();
        foreach (var property in TypeDescriptor.GetProperties(_textBlock).OfType<PropertyDescriptor>())
        {
            var dp = DependencyPropertyDescriptor.FromProperty(property);
            if (dp == null) continue;
            var metadata = dp.Metadata as FrameworkPropertyMetadata;
            if (metadata == null) continue;
            if (!metadata.AffectsRender) continue;
            dp.AddValueChanged(_textBlock, (s, e) => this.InvalidateVisual());
        }
    }

    private void ensureTextBlock()
    {
        if (_textBlock == null) throw new Exception("This adorner works on TextBlocks only");
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        ensureTextBlock();
        base.OnRender(drawingContext);
        var formattedText = new FormattedText(
            _textBlock.Text,
            CultureInfo.CurrentUICulture,
            _textBlock.FlowDirection,
            new Typeface(_textBlock.FontFamily, _textBlock.FontStyle, _textBlock.FontWeight, _textBlock.FontStretch),
            _textBlock.FontSize,
             Brushes.Black // This brush does not matter since we use the geometry of the text. 
        );

        formattedText.TextAlignment = _textBlock.TextAlignment;
        formattedText.Trimming = _textBlock.TextTrimming;
        formattedText.LineHeight = _textBlock.LineHeight;
        formattedText.MaxTextWidth = _textBlock.ActualWidth - _textBlock.Padding.Left - _textBlock.Padding.Right;
        formattedText.MaxTextHeight = _textBlock.ActualHeight - _textBlock.Padding.Top;// - _textBlock.Padding.Bottom;
        while (formattedText.Extent==double.NegativeInfinity)
        {
            formattedText.MaxTextHeight++;
        }

        // Build the geometry object that represents the text.
        var _textGeometry = formattedText.BuildGeometry(new Point(_textBlock.Padding.Left, _textBlock.Padding.Top));
        var textPen = new Pen(Stroke, StrokeThickness);
        drawingContext.DrawGeometry(Brushes.Transparent, textPen, _textGeometry);
    }

}


    public class StrokeTextBlock:TextBlock
    {
        private StrokeAdorner _adorner;
        private bool _adorned=false;

        public StrokeTextBlock()
        {
            _adorner = new StrokeAdorner(this);
            this.LayoutUpdated += StrokeTextBlock_LayoutUpdated;
        }

        private void StrokeTextBlock_LayoutUpdated(object sender, EventArgs e)
        {
            if (_adorned) return;
            _adorned = true;
            var adornerLayer = AdornerLayer.GetAdornerLayer(this);
            adornerLayer.Add(_adorner);
            this.LayoutUpdated -= StrokeTextBlock_LayoutUpdated;
        }

        private static void strokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var stb = (StrokeTextBlock)d;
            stb._adorner.Stroke = e.NewValue as Brush;
        }

        private static void strokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var stb = (StrokeTextBlock)d;
            stb._adorner.StrokeThickness = DependencyProperty.UnsetValue.Equals(e.NewValue)?(ushort)0:(ushort)e.NewValue;
        }

        /// <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(StrokeTextBlock),
            new FrameworkPropertyMetadata(
                 new SolidColorBrush(Colors.Teal),
                 FrameworkPropertyMetadataOptions.AffectsRender,
                 new PropertyChangedCallback(strokeChanged),
                 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(StrokeTextBlock),
            new FrameworkPropertyMetadata(
                 (ushort)0,
                 FrameworkPropertyMetadataOptions.AffectsRender,
                 new PropertyChangedCallback(strokeThicknessChanged),
                 null
                 )
            );
    }

I hope it helps people.

我希望它能帮助人们。

Also, my advice is not to use a control that inherits from TextBlock, but instead find a way to adorn TextBlocks from XAML. For that, take a look at this: http://www.codeproject.com/Articles/54472/Defining-WPF-Adorners-in-XAML If one can encapsulate it into an attached property, then one can add the strokedtext as a Style on whatever textblocks you want. Here is how I did it:

另外,我的建议不是使用从TextBlock继承的控件,而是找到一种从XAML中修饰TextBlock的方法。为此,请查看一下这个:http://www.codeproject.com/articles/54472/definingwpf - adorners-inxaml,如果您可以将其封装到一个附加的属性中,那么您可以在您想要的任何文本块中添加strokedtext作为样式。我是这样做的:

public static class Adorning
{
    public static Brush GetStroke(DependencyObject obj)
    {
        return (Brush)obj.GetValue(StrokeProperty);
    }
    public static void SetStroke(DependencyObject obj, Brush value)
    {
        obj.SetValue(StrokeProperty, value);
    }
    // Using a DependencyProperty as the backing store for Stroke. This enables animation, styling, binding, etc...  
    public static readonly DependencyProperty StrokeProperty =
    DependencyProperty.RegisterAttached("Stroke", typeof(Brush), typeof(Adorning), new PropertyMetadata(Brushes.Transparent, strokeChanged));

    private static void strokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var stroke= e.NewValue as Brush;
        ensureAdorner(d,a=>a.Stroke=stroke);
    }

    private static void ensureAdorner(DependencyObject d, Action<StrokeAdorner> action)
    {
        var tb = d as TextBlock;
        if (tb == null) throw new Exception("StrokeAdorner only works on TextBlocks");
        EventHandler f = null;
        f = new EventHandler((o, e) =>
          {
              var adornerLayer = AdornerLayer.GetAdornerLayer(tb);
              if (adornerLayer == null) throw new Exception("AdornerLayer should not be empty");
              var adorners = adornerLayer.GetAdorners(tb);
              var adorner = adorners == null ? null : adorners.OfType<StrokeAdorner>().FirstOrDefault();
              if (adorner == null)
              {
                  adorner = new StrokeAdorner(tb);
                  adornerLayer.Add(adorner);
              }
              tb.LayoutUpdated -= f;
              action(adorner);
          });
        tb.LayoutUpdated += f;
    }

    public static double GetStrokeThickness(DependencyObject obj)
    {
        return (double)obj.GetValue(StrokeThicknessProperty);
    }
    public static void SetStrokeThickness(DependencyObject obj, double value)
    {
        obj.SetValue(StrokeThicknessProperty, value);
    }
    // Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...  
    public static readonly DependencyProperty StrokeThicknessProperty =
    DependencyProperty.RegisterAttached("StrokeThickness", typeof(double), typeof(Adorning), new PropertyMetadata(0.0, strokeThicknessChanged));

    private static void strokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ensureAdorner(d, a =>
        {
            if (DependencyProperty.UnsetValue.Equals(e.NewValue)) return;
            a.StrokeThickness = (ushort)(double)e.NewValue;
        });
    }
}

An example of use:

使用一个例子:

<TextBlock Text="Some text that needs to be outlined" Grid.Row="2"
                    local:Adorning.Stroke="Aquamarine" local:Adorning.StrokeThickness="2"
                     FontSize="30">
    <TextBlock.Foreground>
        <LinearGradientBrush EndPoint="0.504,1.5" StartPoint="0.504,0.03">
            <GradientStop Color="#FFFFC934" Offset="0"/>
            <GradientStop Color="#FFFFFFFF" Offset="0.567"/>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>

So this is what I made for this particular SO question. It is not production ready, but it should steer you on the right path towards your particular project. Good luck!

这就是我为这个特别的问题所做的。它不是生产就绪,但它应该引导您走上正确的道路,朝向您的特定项目。好运!

#2


2  

TextBlock, TextBox and Label does not have common base, but all of them have the same properties: TextElement.FontSize, TextElement.FontFamily, etc...

TextBlock、TextBox和Label没有公共基础,但是它们都具有相同的属性:TextElement。TextElement字形大小。FontFamily等等……

TextElement properties are attached properties. It's simple as that.

TextElement属性是附加的属性。这是简单。

Look at source codes of TextBlock for example. This is how they use TextElement properties:

例如,查看文本块的源代码。这就是他们如何使用TextElement属性:

    /// <summary> 
    /// DependencyProperty for <see cref="FontFamily" /> property.
    /// </summary>
    [CommonDependencyProperty]
    public static readonly DependencyProperty FontFamilyProperty = 
            TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));

    /// <summary> 
    /// The FontFamily property specifies the name of font family.
    /// </summary> 
    [Localizability(LocalizationCategory.Font)]
    public FontFamily FontFamily
    {
        get { return (FontFamily) GetValue(FontFamilyProperty); } 
        set { SetValue(FontFamilyProperty, value); }
    } 

    /// <summary>
    /// DependencyProperty setter for <see cref="FontFamily" /> property. 
    /// </summary>
    /// <param name="element">The element to which to write the attached property.</param>
    /// <param name="value">The property value to set</param>
    public static void SetFontFamily(DependencyObject element, FontFamily value) 
    {
        if (element == null) 
        { 
            throw new ArgumentNullException("element");
        } 

        element.SetValue(FontFamilyProperty, value);
    }

    /// <summary>
    /// DependencyProperty getter for <see cref="FontFamily" /> property. 
    /// </summary> 
    /// <param name="element">The element from which to read the attached property.</param>
    public static FontFamily GetFontFamily(DependencyObject element) 
    {
        if (element == null)
        {
            throw new ArgumentNullException("element"); 
        }

        return (FontFamily)element.GetValue(FontFamilyProperty); 
    }

#3


1  

The issue here is that the OnRender method in TextBlock is sealed. It sucks, but there must be a good reason for it. One which I am not aware of.

这里的问题是TextBlock中的OnRender方法是密封的。这很糟糕,但肯定有很好的理由。一个我不知道的。

An alternative would be to subscribe to the LayoutUpdated event and call your CreateText() method when the layout is updated. Here's an example:

另一种方法是订阅LayoutUpdated事件,并在更新布局时调用CreateText()方法。这里有一个例子:

public class OutlinedText : TextBlock
{
    public OutlinedText()
    {
        LayoutUpdated += OutlinedText_LayoutUpdated;
    }

    void OutlinedText_LayoutUpdated(object sender, EventArgs e)
    {
        CreateText();

        //...
    }

This is by no means the golden ticket, however LayoutUpdated gets called often and should be able to handle your text rendering requirements.

这绝不是金票,但是LayoutUpdated经常被调用,并且应该能够处理您的文本呈现需求。

Oh, and here is some documentation for it.

哦,这里有一些文件。

#4


0  

Inherit from TextBlock:

继承TextBlock:

public class OutlinedText : TextBlock, IAddChild

#5


0  

Came across this neat toolkit years ago and they have a StrokeTextBlock. I used it for my silverlight projects for more than 5 years now. They also have a WPF version. The code is a lot to post here so here is the link: (which i was surprised to see still existed on codeplex.

几年前,他们遇到了这个整洁的工具箱,他们有一个StrokeTextBlock。我用它做我的silverlight项目已经超过5年了。他们也有WPF版本。这里的代码非常多,所以这里是链接(我很惊讶地看到codeplex上仍然存在这个链接)。

Blacklight Toolkit: StrokeTextBlock.cs

Blacklight工具包:StrokeTextBlock.cs

It inherits from System.Windows.Controls.Control Which allows it to have the properties you were looking for like FontFamily,FontSize,FontWeight...etc

它从System.Windows.Controls继承。控制使它具有你所寻找的属性,如FontFamily,FontSize,FontWeight…等等。

Here is the generic.xaml for the control

这是通用的。xaml的控制

<!-- StrokeTextBlock style -->
<Style TargetType="local:StrokeTextBlock">
    <Setter Property="Text" Value="StrokeTextBlock" />
    <Setter Property="StrokeOpacity" Value="1" />
    <Setter Property="Stroke" Value="#ffffffff" />
    <Setter Property="StrokeThickness" Value="1" />
    <Setter Property="Foreground" Value="#ff000000" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:StrokeTextBlock">
                <Grid>
                    <ItemsControl x:Name="PART_ItemsControl" 
                          VerticalAlignment="Top" HorizontalAlignment="Left" 
                          Opacity="{TemplateBinding StrokeOpacity}">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <Grid  />
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>

                    <TextBlock x:Name="PART_TextBlock"
                               TextWrapping="{TemplateBinding TextWrapping}" 
                               Foreground="{TemplateBinding Foreground}"
                               FontSize="{TemplateBinding FontSize}" 
                               FontFamily="{TemplateBinding FontFamily}" 
                               FontWeight="{TemplateBinding FontWeight}"
                               VerticalAlignment="Top" HorizontalAlignment="Left"
                               UseLayoutRounding="False"
                               LineHeight="{TemplateBinding LineHeight}"
                               Text="{TemplateBinding Text}" />

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

#6


0  

You can inherit from TextBlock, but when you do this, you don't need to implement IAddChild, because TextBlock already does this, as mentioned here: in this MSDN reference page.

您可以从TextBlock继承,但是当您这样做时,您不需要实现IAddChild,因为TextBlock已经这样做了,正如这里提到的:在这个MSDN参考页面中。

What I would recommend is to create WPF UserControl and change it's inheritance from UserControl to TextBlock, then you can just extend the functionality in your class, I have personally tested this and it works fine. If you need to add any visual customisation, this can usually be done through a Itemtemplate / ControlTemplate.

我的建议是创建WPF UserControl并将它的继承从UserControl改为TextBlock,然后您可以在您的类中扩展这个功能,我亲自测试过这个,它运行良好。如果您需要添加任何可视化定制,通常可以通过Itemtemplate / ControlTemplate来完成。

Here is an article with another method that shows how to extend a WPF control which shows that it is possible: Basics of extending WPF control

本文介绍了另一种方法,它展示了如何扩展WPF控件,这表明它是可能的:扩展WPF控件的基础。

Alternatively this is another method using a user control that transforms its content to different color.

或者,这是另一种使用用户控件的方法,它将其内容转换为不同的颜色。

If you are looking to display a custom stroke fill with the TextBlock, then here is a solution to that specific problem.

如果您想要显示一个定制的描边填充文本块,那么这里有一个解决这个特定问题的方法。

At least one of these methods or a combination should be able to get what you want done.

至少其中一个方法或组合应该能够得到您想要的结果。

#7


0  

Inherit from TextBox, and apply your own Template. Don't make it editable to mimick it as a TextBlock.

从文本框中继承,并应用您自己的模板。不要把它作为一个文本块来编辑。

Your Template would be a Geometry. Construct this Geometry in the constructor of your control or Loaded event. For example, you can call CreateText() from your ctor. There are various Geometry derived classes available like LineGeometry, PathGeometry etc.

您的模板将是一个几何图形。在控件或加载事件的构造函数中构造此几何体。例如,您可以从您的ctor调用CreateText()。有各种各样的几何派生类,如线性几何、路径几何等。

** EDIT after working on a workable sample **

**在完成一个可操作的样本**后进行编辑。

Inherit from Label.

继承标签。

Change your CreateText() to :

将您的CreateText()更改为:

   public void CreateText()
    {
        FontStyle fontStyle = FontStyles.Normal;
        FontWeight fontWeight = FontWeights.Medium;

        //if (FontWeight == FontWeights.Bold) fontWeight = FontWeights.Bold;
        // if (FontStyle == FontStyles.Italic) 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(FontFamily, FontStyle, FontWeight, FontStretches.Normal, new FontFamily("Arial")),
            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(4, 4));

        //set the size of the custome control based on the size of the text
        this.MaxWidth = formattedText.Width + 100;
        this.MaxHeight = formattedText.Height + 10;
    }

And you can consider to completely remove the ControlTemplate of the parent Label. This is very easy by right-clicking the control and edit-template > create-empty.

您可以考虑完全删除父标签的ControlTemplate。通过右键单击控件和编辑模板> create-empty,这非常简单。

#1


3  

First we need to understand the requirements and, from the question as well as from various answers and comments, I list several:

首先,我们需要了解需求,从问题以及各种答案和评论中,我列出了几个:

1) I want to have an outline around my textblock text, drawn with my desired stroke thickness and color. That has been answered here: How can I extend a TextBlock to display Outlined Text? . Use DropShadowEffect on the textblock.

1)我想在我的textblock文本周围有一个大纲,用我想要的笔画厚度和颜色画出来。这里已经回答了这个问题:如何扩展一个文本块来显示所概述的文本?。在文本块上使用DropShadowEffect。

2) I want to control the outline up to the distance from the text and the brush that I am going to use, not just a simple color, etc. I basically want to draw anything I want on my textblock, while getting all of its functionality. So you need to adorn the TextBlock with your own graphics. Then use an Adorner.

2)我想要把大纲控制在我要使用的文本和画笔的距离上,而不仅仅是简单的颜色等等。我基本上想在我的textblock上画任何我想要的东西,同时得到它的所有功能。所以你需要用自己的图形来装饰TextBlock。然后使用一个装饰器。

3) The most complex requirement seems to be "a control that does everything a TextBlock does, but with a stroke that I can control completely". For this there have been several attempts: trying to recreate TextBlock from FrameworkElement, trying to inherit from TextBlock, I even copied all the miriad of internal sealed classes that are used in TextBlock and tried to rewrite it as an open control. Just inherit from TextBlock and add the Adorner code inside.

3)最复杂的需求似乎是“一种控制,它能做所有文本块做的事情,但我可以完全控制它”。为此,有几次尝试:试图从FrameworkElement重新创建TextBlock,试图从TextBlock继承,我甚至复制了TextBlock中使用的所有内部密封类的miriad,并试图将其重写为一个开放控件。从TextBlock继承,然后在里面添加装饰代码。

As a solution for 3), here is the code that I made to replicate the original code, which may now be changed as desired, and using a TextBlock:

作为对3的解决方案,这里是我用来复制原始代码的代码,现在可以根据需要修改它,并使用一个TextBlock:

public class StrokeAdorner : Adorner
{
    private TextBlock _textBlock;

    private Brush _stroke;
    private ushort _strokeThickness;

    public Brush Stroke
    {
        get
        {
            return _stroke;
        }

        set
        {
            _stroke = value;
            _textBlock.InvalidateVisual();
            InvalidateVisual();
        }
    }

    public ushort StrokeThickness
    {
        get
        {
            return _strokeThickness;
        }

        set
        {
            _strokeThickness = value;
            _textBlock.InvalidateVisual();
            InvalidateVisual();
        }
    }

    public StrokeAdorner(UIElement adornedElement) : base(adornedElement)
    {
        _textBlock = adornedElement as TextBlock;
        ensureTextBlock();
        foreach (var property in TypeDescriptor.GetProperties(_textBlock).OfType<PropertyDescriptor>())
        {
            var dp = DependencyPropertyDescriptor.FromProperty(property);
            if (dp == null) continue;
            var metadata = dp.Metadata as FrameworkPropertyMetadata;
            if (metadata == null) continue;
            if (!metadata.AffectsRender) continue;
            dp.AddValueChanged(_textBlock, (s, e) => this.InvalidateVisual());
        }
    }

    private void ensureTextBlock()
    {
        if (_textBlock == null) throw new Exception("This adorner works on TextBlocks only");
    }

    protected override void OnRender(DrawingContext drawingContext)
    {
        ensureTextBlock();
        base.OnRender(drawingContext);
        var formattedText = new FormattedText(
            _textBlock.Text,
            CultureInfo.CurrentUICulture,
            _textBlock.FlowDirection,
            new Typeface(_textBlock.FontFamily, _textBlock.FontStyle, _textBlock.FontWeight, _textBlock.FontStretch),
            _textBlock.FontSize,
             Brushes.Black // This brush does not matter since we use the geometry of the text. 
        );

        formattedText.TextAlignment = _textBlock.TextAlignment;
        formattedText.Trimming = _textBlock.TextTrimming;
        formattedText.LineHeight = _textBlock.LineHeight;
        formattedText.MaxTextWidth = _textBlock.ActualWidth - _textBlock.Padding.Left - _textBlock.Padding.Right;
        formattedText.MaxTextHeight = _textBlock.ActualHeight - _textBlock.Padding.Top;// - _textBlock.Padding.Bottom;
        while (formattedText.Extent==double.NegativeInfinity)
        {
            formattedText.MaxTextHeight++;
        }

        // Build the geometry object that represents the text.
        var _textGeometry = formattedText.BuildGeometry(new Point(_textBlock.Padding.Left, _textBlock.Padding.Top));
        var textPen = new Pen(Stroke, StrokeThickness);
        drawingContext.DrawGeometry(Brushes.Transparent, textPen, _textGeometry);
    }

}


    public class StrokeTextBlock:TextBlock
    {
        private StrokeAdorner _adorner;
        private bool _adorned=false;

        public StrokeTextBlock()
        {
            _adorner = new StrokeAdorner(this);
            this.LayoutUpdated += StrokeTextBlock_LayoutUpdated;
        }

        private void StrokeTextBlock_LayoutUpdated(object sender, EventArgs e)
        {
            if (_adorned) return;
            _adorned = true;
            var adornerLayer = AdornerLayer.GetAdornerLayer(this);
            adornerLayer.Add(_adorner);
            this.LayoutUpdated -= StrokeTextBlock_LayoutUpdated;
        }

        private static void strokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var stb = (StrokeTextBlock)d;
            stb._adorner.Stroke = e.NewValue as Brush;
        }

        private static void strokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var stb = (StrokeTextBlock)d;
            stb._adorner.StrokeThickness = DependencyProperty.UnsetValue.Equals(e.NewValue)?(ushort)0:(ushort)e.NewValue;
        }

        /// <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(StrokeTextBlock),
            new FrameworkPropertyMetadata(
                 new SolidColorBrush(Colors.Teal),
                 FrameworkPropertyMetadataOptions.AffectsRender,
                 new PropertyChangedCallback(strokeChanged),
                 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(StrokeTextBlock),
            new FrameworkPropertyMetadata(
                 (ushort)0,
                 FrameworkPropertyMetadataOptions.AffectsRender,
                 new PropertyChangedCallback(strokeThicknessChanged),
                 null
                 )
            );
    }

I hope it helps people.

我希望它能帮助人们。

Also, my advice is not to use a control that inherits from TextBlock, but instead find a way to adorn TextBlocks from XAML. For that, take a look at this: http://www.codeproject.com/Articles/54472/Defining-WPF-Adorners-in-XAML If one can encapsulate it into an attached property, then one can add the strokedtext as a Style on whatever textblocks you want. Here is how I did it:

另外,我的建议不是使用从TextBlock继承的控件,而是找到一种从XAML中修饰TextBlock的方法。为此,请查看一下这个:http://www.codeproject.com/articles/54472/definingwpf - adorners-inxaml,如果您可以将其封装到一个附加的属性中,那么您可以在您想要的任何文本块中添加strokedtext作为样式。我是这样做的:

public static class Adorning
{
    public static Brush GetStroke(DependencyObject obj)
    {
        return (Brush)obj.GetValue(StrokeProperty);
    }
    public static void SetStroke(DependencyObject obj, Brush value)
    {
        obj.SetValue(StrokeProperty, value);
    }
    // Using a DependencyProperty as the backing store for Stroke. This enables animation, styling, binding, etc...  
    public static readonly DependencyProperty StrokeProperty =
    DependencyProperty.RegisterAttached("Stroke", typeof(Brush), typeof(Adorning), new PropertyMetadata(Brushes.Transparent, strokeChanged));

    private static void strokeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        var stroke= e.NewValue as Brush;
        ensureAdorner(d,a=>a.Stroke=stroke);
    }

    private static void ensureAdorner(DependencyObject d, Action<StrokeAdorner> action)
    {
        var tb = d as TextBlock;
        if (tb == null) throw new Exception("StrokeAdorner only works on TextBlocks");
        EventHandler f = null;
        f = new EventHandler((o, e) =>
          {
              var adornerLayer = AdornerLayer.GetAdornerLayer(tb);
              if (adornerLayer == null) throw new Exception("AdornerLayer should not be empty");
              var adorners = adornerLayer.GetAdorners(tb);
              var adorner = adorners == null ? null : adorners.OfType<StrokeAdorner>().FirstOrDefault();
              if (adorner == null)
              {
                  adorner = new StrokeAdorner(tb);
                  adornerLayer.Add(adorner);
              }
              tb.LayoutUpdated -= f;
              action(adorner);
          });
        tb.LayoutUpdated += f;
    }

    public static double GetStrokeThickness(DependencyObject obj)
    {
        return (double)obj.GetValue(StrokeThicknessProperty);
    }
    public static void SetStrokeThickness(DependencyObject obj, double value)
    {
        obj.SetValue(StrokeThicknessProperty, value);
    }
    // Using a DependencyProperty as the backing store for StrokeThickness. This enables animation, styling, binding, etc...  
    public static readonly DependencyProperty StrokeThicknessProperty =
    DependencyProperty.RegisterAttached("StrokeThickness", typeof(double), typeof(Adorning), new PropertyMetadata(0.0, strokeThicknessChanged));

    private static void strokeThicknessChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ensureAdorner(d, a =>
        {
            if (DependencyProperty.UnsetValue.Equals(e.NewValue)) return;
            a.StrokeThickness = (ushort)(double)e.NewValue;
        });
    }
}

An example of use:

使用一个例子:

<TextBlock Text="Some text that needs to be outlined" Grid.Row="2"
                    local:Adorning.Stroke="Aquamarine" local:Adorning.StrokeThickness="2"
                     FontSize="30">
    <TextBlock.Foreground>
        <LinearGradientBrush EndPoint="0.504,1.5" StartPoint="0.504,0.03">
            <GradientStop Color="#FFFFC934" Offset="0"/>
            <GradientStop Color="#FFFFFFFF" Offset="0.567"/>
        </LinearGradientBrush>
    </TextBlock.Foreground>
</TextBlock>

So this is what I made for this particular SO question. It is not production ready, but it should steer you on the right path towards your particular project. Good luck!

这就是我为这个特别的问题所做的。它不是生产就绪,但它应该引导您走上正确的道路,朝向您的特定项目。好运!

#2


2  

TextBlock, TextBox and Label does not have common base, but all of them have the same properties: TextElement.FontSize, TextElement.FontFamily, etc...

TextBlock、TextBox和Label没有公共基础,但是它们都具有相同的属性:TextElement。TextElement字形大小。FontFamily等等……

TextElement properties are attached properties. It's simple as that.

TextElement属性是附加的属性。这是简单。

Look at source codes of TextBlock for example. This is how they use TextElement properties:

例如,查看文本块的源代码。这就是他们如何使用TextElement属性:

    /// <summary> 
    /// DependencyProperty for <see cref="FontFamily" /> property.
    /// </summary>
    [CommonDependencyProperty]
    public static readonly DependencyProperty FontFamilyProperty = 
            TextElement.FontFamilyProperty.AddOwner(typeof(TextBlock));

    /// <summary> 
    /// The FontFamily property specifies the name of font family.
    /// </summary> 
    [Localizability(LocalizationCategory.Font)]
    public FontFamily FontFamily
    {
        get { return (FontFamily) GetValue(FontFamilyProperty); } 
        set { SetValue(FontFamilyProperty, value); }
    } 

    /// <summary>
    /// DependencyProperty setter for <see cref="FontFamily" /> property. 
    /// </summary>
    /// <param name="element">The element to which to write the attached property.</param>
    /// <param name="value">The property value to set</param>
    public static void SetFontFamily(DependencyObject element, FontFamily value) 
    {
        if (element == null) 
        { 
            throw new ArgumentNullException("element");
        } 

        element.SetValue(FontFamilyProperty, value);
    }

    /// <summary>
    /// DependencyProperty getter for <see cref="FontFamily" /> property. 
    /// </summary> 
    /// <param name="element">The element from which to read the attached property.</param>
    public static FontFamily GetFontFamily(DependencyObject element) 
    {
        if (element == null)
        {
            throw new ArgumentNullException("element"); 
        }

        return (FontFamily)element.GetValue(FontFamilyProperty); 
    }

#3


1  

The issue here is that the OnRender method in TextBlock is sealed. It sucks, but there must be a good reason for it. One which I am not aware of.

这里的问题是TextBlock中的OnRender方法是密封的。这很糟糕,但肯定有很好的理由。一个我不知道的。

An alternative would be to subscribe to the LayoutUpdated event and call your CreateText() method when the layout is updated. Here's an example:

另一种方法是订阅LayoutUpdated事件,并在更新布局时调用CreateText()方法。这里有一个例子:

public class OutlinedText : TextBlock
{
    public OutlinedText()
    {
        LayoutUpdated += OutlinedText_LayoutUpdated;
    }

    void OutlinedText_LayoutUpdated(object sender, EventArgs e)
    {
        CreateText();

        //...
    }

This is by no means the golden ticket, however LayoutUpdated gets called often and should be able to handle your text rendering requirements.

这绝不是金票,但是LayoutUpdated经常被调用,并且应该能够处理您的文本呈现需求。

Oh, and here is some documentation for it.

哦,这里有一些文件。

#4


0  

Inherit from TextBlock:

继承TextBlock:

public class OutlinedText : TextBlock, IAddChild

#5


0  

Came across this neat toolkit years ago and they have a StrokeTextBlock. I used it for my silverlight projects for more than 5 years now. They also have a WPF version. The code is a lot to post here so here is the link: (which i was surprised to see still existed on codeplex.

几年前,他们遇到了这个整洁的工具箱,他们有一个StrokeTextBlock。我用它做我的silverlight项目已经超过5年了。他们也有WPF版本。这里的代码非常多,所以这里是链接(我很惊讶地看到codeplex上仍然存在这个链接)。

Blacklight Toolkit: StrokeTextBlock.cs

Blacklight工具包:StrokeTextBlock.cs

It inherits from System.Windows.Controls.Control Which allows it to have the properties you were looking for like FontFamily,FontSize,FontWeight...etc

它从System.Windows.Controls继承。控制使它具有你所寻找的属性,如FontFamily,FontSize,FontWeight…等等。

Here is the generic.xaml for the control

这是通用的。xaml的控制

<!-- StrokeTextBlock style -->
<Style TargetType="local:StrokeTextBlock">
    <Setter Property="Text" Value="StrokeTextBlock" />
    <Setter Property="StrokeOpacity" Value="1" />
    <Setter Property="Stroke" Value="#ffffffff" />
    <Setter Property="StrokeThickness" Value="1" />
    <Setter Property="Foreground" Value="#ff000000" />
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="local:StrokeTextBlock">
                <Grid>
                    <ItemsControl x:Name="PART_ItemsControl" 
                          VerticalAlignment="Top" HorizontalAlignment="Left" 
                          Opacity="{TemplateBinding StrokeOpacity}">
                        <ItemsControl.ItemsPanel>
                            <ItemsPanelTemplate>
                                <Grid  />
                            </ItemsPanelTemplate>
                        </ItemsControl.ItemsPanel>
                    </ItemsControl>

                    <TextBlock x:Name="PART_TextBlock"
                               TextWrapping="{TemplateBinding TextWrapping}" 
                               Foreground="{TemplateBinding Foreground}"
                               FontSize="{TemplateBinding FontSize}" 
                               FontFamily="{TemplateBinding FontFamily}" 
                               FontWeight="{TemplateBinding FontWeight}"
                               VerticalAlignment="Top" HorizontalAlignment="Left"
                               UseLayoutRounding="False"
                               LineHeight="{TemplateBinding LineHeight}"
                               Text="{TemplateBinding Text}" />

                </Grid>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

#6


0  

You can inherit from TextBlock, but when you do this, you don't need to implement IAddChild, because TextBlock already does this, as mentioned here: in this MSDN reference page.

您可以从TextBlock继承,但是当您这样做时,您不需要实现IAddChild,因为TextBlock已经这样做了,正如这里提到的:在这个MSDN参考页面中。

What I would recommend is to create WPF UserControl and change it's inheritance from UserControl to TextBlock, then you can just extend the functionality in your class, I have personally tested this and it works fine. If you need to add any visual customisation, this can usually be done through a Itemtemplate / ControlTemplate.

我的建议是创建WPF UserControl并将它的继承从UserControl改为TextBlock,然后您可以在您的类中扩展这个功能,我亲自测试过这个,它运行良好。如果您需要添加任何可视化定制,通常可以通过Itemtemplate / ControlTemplate来完成。

Here is an article with another method that shows how to extend a WPF control which shows that it is possible: Basics of extending WPF control

本文介绍了另一种方法,它展示了如何扩展WPF控件,这表明它是可能的:扩展WPF控件的基础。

Alternatively this is another method using a user control that transforms its content to different color.

或者,这是另一种使用用户控件的方法,它将其内容转换为不同的颜色。

If you are looking to display a custom stroke fill with the TextBlock, then here is a solution to that specific problem.

如果您想要显示一个定制的描边填充文本块,那么这里有一个解决这个特定问题的方法。

At least one of these methods or a combination should be able to get what you want done.

至少其中一个方法或组合应该能够得到您想要的结果。

#7


0  

Inherit from TextBox, and apply your own Template. Don't make it editable to mimick it as a TextBlock.

从文本框中继承,并应用您自己的模板。不要把它作为一个文本块来编辑。

Your Template would be a Geometry. Construct this Geometry in the constructor of your control or Loaded event. For example, you can call CreateText() from your ctor. There are various Geometry derived classes available like LineGeometry, PathGeometry etc.

您的模板将是一个几何图形。在控件或加载事件的构造函数中构造此几何体。例如,您可以从您的ctor调用CreateText()。有各种各样的几何派生类,如线性几何、路径几何等。

** EDIT after working on a workable sample **

**在完成一个可操作的样本**后进行编辑。

Inherit from Label.

继承标签。

Change your CreateText() to :

将您的CreateText()更改为:

   public void CreateText()
    {
        FontStyle fontStyle = FontStyles.Normal;
        FontWeight fontWeight = FontWeights.Medium;

        //if (FontWeight == FontWeights.Bold) fontWeight = FontWeights.Bold;
        // if (FontStyle == FontStyles.Italic) 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(FontFamily, FontStyle, FontWeight, FontStretches.Normal, new FontFamily("Arial")),
            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(4, 4));

        //set the size of the custome control based on the size of the text
        this.MaxWidth = formattedText.Width + 100;
        this.MaxHeight = formattedText.Height + 10;
    }

And you can consider to completely remove the ControlTemplate of the parent Label. This is very easy by right-clicking the control and edit-template > create-empty.

您可以考虑完全删除父标签的ControlTemplate。通过右键单击控件和编辑模板> create-empty,这非常简单。