[英]How to inherit a textblock properties to a custom control in c#
我有一個自定義的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以顯示Outlined Text? 。 在文本塊上使用DropShadowEffect。
2)我想控制輪廓到我要使用的文本和畫筆的距離,而不僅僅是簡單的顏色等。我基本上想要在我的文本塊上繪制任何我想要的東西,同時獲得所有的功能。 因此,您需要使用自己的圖形來裝飾TextBlock。 然后使用Adorner 。
3)最復雜的要求似乎是“一個控件,可以完成TextBlock所做的一切,但是我可以完全控制一個筆划”。 為此,有幾次嘗試:嘗試從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>
所以這就是我為這個特殊的問題所做的。 它不是生產准備好的,但它應該引導你走向你的特定項目的正確道路。 祝好運!
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);
}
這里的問題是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
。 我將它用於我的銀光項目已超過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>
從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。 右鍵單擊控件並編輯模板> create-empty,這非常簡單。
您可以從TextBlock
繼承,但是當您執行此操作時,您不需要實現IAddChild
,因為TextBlock已經執行此操作,如此處所述: 在此MSDN參考頁中。
我建議創建WPF UserControl
並將其繼承從UserControl
更改為TextBlock
,然后您可以擴展您的類中的功能,我親自測試了它,它工作正常。 如果您需要添加任何可視化自定義,通常可以通過Itemtemplate
/ ControlTemplate
來完成。
這是一篇文章,其他方法展示了如何擴展WPF控件,這表明它是可能的: 擴展WPF控件的基礎知識
或者,這是使用用戶控件的另一種方法,該用戶控件將其內容轉換為不同的顏色 。
如果您希望使用TextBlock顯示自定義筆觸填充,則此處是針對該特定問題的解決方案 。
這些方法或組合中的至少一種應該能夠得到你想要的東西。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.