如何在c#中继承文本块属性到自定义控件
本文关键字:属性 自定义控件 文本 继承 | 更新日期: 2023-09-27 18:06:38
我有一个自定义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
}
}
首先我们需要了解需求,从问题以及各种答案和评论中,我列出了几个:
1)我想在我的textblock文本周围有一个轮廓,用我想要的笔画厚度和颜色绘制。这里已经回答了:我如何扩展TextBlock来显示概述文本?。在文本块上使用dropshadoweeffect
2)我想控制轮廓到文本和我将要使用的笔刷的距离,而不仅仅是一个简单的颜色,等等。我基本上想在我的textblock上画任何我想要的东西,同时获得它的所有功能。所以你需要用你自己的图形来装饰TextBlock。然后使用装饰器。
3)最复杂的要求似乎是"一个控件做一切TextBlock做,但与笔画,我可以完全控制"。为此,有几个尝试:试图从FrameworkElement重新创建TextBlock,试图从TextBlock继承,我甚至复制了所有在TextBlock中使用的内部密封类的无数,并试图将其重写为一个开放的控件。只需继承TextBlock并在其中添加Adorner代码。
作为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
)
);
}
我希望它能帮助到人们。
另外,我的建议是不要使用从TextBlock继承的控件,而是找到一种从XAML中装饰TextBlock的方法。对于这一点,看看这个:http://www.codeproject.com/Articles/54472/Defining-WPF-Adorners-in-XAML如果可以将其封装到附加属性中,那么可以将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;
});
}
}
使用示例:
<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问题做的。它还不能用于生产,但它应该引导您在正确的道路上实现您的特定项目。好运!
TextBlock, TextBox和Label没有共同的基础,但它们都有相同的属性:TextElement。TextElement字形大小。FontFamily等等…
TextElement属性是附加属性。就这么简单。
看源代码的TextBlock为例。他们是这样使用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);
}
这里的问题是TextBlock
中的OnRender
方法是sealed
。这很糟糕,但肯定有很好的理由。一个我不知道的。
另一种方法是订阅LayoutUpdated
事件并在更新布局时调用CreateText()
方法。下面是一个例子:
public class OutlinedText : TextBlock
{
public OutlinedText()
{
LayoutUpdated += OutlinedText_LayoutUpdated;
}
void OutlinedText_LayoutUpdated(object sender, EventArgs e)
{
CreateText();
//...
}
这绝不是黄金票,但是LayoutUpdated
经常被调用,应该能够处理您的文本渲染需求。
哦,这里有一些文档。
继承TextBlock
:
public class OutlinedText : TextBlock, IAddChild
几年前我遇到了这个整洁的工具包,他们有一个StrokeTextBlock
。我在我的silverlight项目中使用它已经超过5年了。他们也有一个WPF版本。这里的代码很多,所以这里是链接:(我很惊讶地看到codeplex上仍然存在。
Blacklight Toolkit: StrokeTextBlock.cs
它继承自System.Windows.Controls.Control
,它允许它有你正在寻找的属性,如FontFamily
, FontSize
, FontWeight
…等
这是控件
的generic.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>
你可以从TextBlock
继承,但是当你这样做的时候,你不需要实现IAddChild
,因为TextBlock已经做到了这一点,在这里提到:在这个MSDN参考页面。
我建议的是创建WPF UserControl
并将其继承从UserControl
更改为TextBlock
,然后您可以在类中扩展功能,我已经亲自测试过了,它工作得很好。如果你需要添加任何视觉定制,这通常可以通过Itemtemplate
/ControlTemplate
来完成。
这里有一篇文章展示了如何扩展WPF控件的另一种方法,这表明这是可能的:扩展WPF控件的基础知识
或者,这是另一种方法,使用用户控件将其内容转换为不同的颜色。
如果你想用TextBlock显示自定义笔画填充,那么这里有一个解决这个特定问题的方法。
这些方法中的至少一种或组合应该能够完成您想要完成的任务。
继承TextBox
,并应用您自己的Template
。不要把它设置成可编辑的来模仿TextBlock
。
您的Template
将是Geometry
。在控件或Loaded事件的构造函数中构造这个Geometry
。例如,您可以从您的函数调用CreateText()
。有各种各样的几何派生类,如LineGeometry
, PathGeometry
等。
**编辑一个可操作的样本**
继承自Label
.
将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;
}
你可以考虑完全删除父Label
的ControlTemplate。这非常简单,只需右键单击控件并edit-template> create-empty。