我有一个自定义的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
}
}
答案 0 :(得分:3)
首先,我们需要了解要求,从问题以及各种答案和评论中,我列出了几个:
1)我希望在我的文本块文本周围有一个轮廓,用我想要的笔触粗细和颜色绘制。这已在此回答:How can I extend a TextBlock to display Outlined Text?。在文本块上使用DropShadowEffect。
2)我想控制轮廓到我要使用的文字和画笔的距离,而不仅仅是简单的颜色等。我基本上想要在我的文本块上绘制任何我想要的东西,同时获得它的所有功能。因此,您需要使用自己的图形来装饰TextBlock。然后使用Adorner。
3)最复杂的要求似乎是&#34;一个控件,可以完成TextBlock所做的一切,但是我可以完全控制一个笔画&#34;。为此,有几次尝试:尝试从FrameworkElement重新创建TextBlock,尝试从TextBlock继承,我甚至复制了TextBlock中使用的所有内部密封类的miriad,并尝试将其重写为开放控件。只需从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中装饰TextBlocks的方法。为此,请看一下: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>
所以这就是我为这个特殊的问题所做的。它不是生产准备好的,但它应该引导你走向你的特定项目的正确道路。祝你好运!
答案 1 :(得分:2)
TextBlock,TextBox和Label没有共同的基础,但它们都具有相同的属性:TextElement.FontSize,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);
}
答案 2 :(得分:1)
此处的问题是OnRender
中的TextBlock
方法是sealed
。它很糟糕,但必须有充分的理由。一个我不知道的。
另一种方法是订阅LayoutUpdated
事件,并在更新布局时调用CreateText()
方法。这是一个例子:
public class OutlinedText : TextBlock
{
public OutlinedText()
{
LayoutUpdated += OutlinedText_LayoutUpdated;
}
void OutlinedText_LayoutUpdated(object sender, EventArgs e)
{
CreateText();
//...
}
这绝不是金票,但LayoutUpdated
经常被调用,应该能够处理您的文本呈现要求。
哦,这里有一些documentation。
答案 3 :(得分:0)
继承自TextBlock
:
public class OutlinedText : TextBlock, IAddChild
答案 4 :(得分:0)
几年前遇到了这个整洁的工具包,他们有一个StrokeTextBlock
。我将它用于我的银光项目已超过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>
答案 5 :(得分:0)
继承自TextBox
,并应用您自己的Template
。不要将其模仿为TextBlock
。
您的Template
将是Geometry
。在控件或Loaded事件的构造函数中构造此Geometry
。例如,您可以从您的ctor拨打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。通过右键单击控件和编辑模板&gt;,这非常简单。创建空。
答案 6 :(得分:0)
您可以继承TextBlock
,但是当您执行此操作时,您不需要实现IAddChild
,因为TextBlock已经执行了此操作,如下所述:in this MSDN reference page.
我建议创建WPF UserControl
并将其继承从UserControl
更改为TextBlock
,然后您可以扩展您班级的功能,我亲自测试了这个和它工作正常。
如果您需要添加任何可视自定义,通常可以通过Itemtemplate
/ ControlTemplate
来完成。
这是一篇文章,其他方法展示了如何扩展WPF控件,该控件显示可能:Basics of extending WPF control
或者,这是另一种使用用户控件that transforms its content to different color的方法。
如果您希望使用TextBlock显示自定义笔触填充,则此处为a solution to that specific problem。
这些方法或组合中的至少一种应该能够得到你想要的东西。