WPF의 텍스트 블록에 스트로크 적용
WPF의 xaml 텍스트 블록에 스트로크(텍스트 주위의 아웃라인)를 적용하려면 어떻게 해야 합니까?
다음은 좀 더 관용적인 WPF의 풀기능에 대한 저의 견해입니다.다음을 포함하여 사용자가 기대하는 거의 모든 기능을 지원합니다.
- 스트레치 및 스타일을 포함한 모든 글꼴 관련 속성
- 텍스트 정렬(왼쪽, 오른쪽, 가운데, 맞춤)
- 텍스트 래핑
- 텍스트 트리밍
- 텍스트 장식(밑줄, 스트라이크 등)
이를 통해 달성할 수 있는 간단한 예를 다음에 제시하겠습니다.
<local:OutlinedTextBlock FontFamily="Verdana" FontSize="20pt" FontWeight="ExtraBold" TextWrapping="Wrap" StrokeThickness="1" Stroke="{StaticResource TextStroke}" Fill="{StaticResource TextFill}">
Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit
</local:OutlinedTextBlock>
그 결과:
컨트롤 코드는 다음과 같습니다.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
[ContentProperty("Text")]
public class OutlinedTextBlock : FrameworkElement
{
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(double),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextInvalidated));
public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
"TextAlignment",
typeof(TextAlignment),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(
"TextDecorations",
typeof(TextDecorationCollection),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
"TextTrimming",
typeof(TextTrimming),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
"TextWrapping",
typeof(TextWrapping),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(TextWrapping.NoWrap, OnFormattedTextUpdated));
private FormattedText formattedText;
private Geometry textGeometry;
public OutlinedTextBlock()
{
this.TextDecorations = new TextDecorationCollection();
}
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public TextAlignment TextAlignment
{
get { return (TextAlignment)GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
public TextDecorationCollection TextDecorations
{
get { return (TextDecorationCollection)this.GetValue(TextDecorationsProperty); }
set { this.SetValue(TextDecorationsProperty, value); }
}
public TextTrimming TextTrimming
{
get { return (TextTrimming)GetValue(TextTrimmingProperty); }
set { SetValue(TextTrimmingProperty, value); }
}
public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
protected override void OnRender(DrawingContext drawingContext)
{
this.EnsureGeometry();
drawingContext.DrawGeometry(this.Fill, new Pen(this.Stroke, this.StrokeThickness), this.textGeometry);
}
protected override Size MeasureOverride(Size availableSize)
{
this.EnsureFormattedText();
// constrain the formatted text according to the available size
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
// the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
this.formattedText.MaxTextWidth = Math.Min(3579139, availableSize.Width);
this.formattedText.MaxTextHeight = Math.Max(0.0001d, availableSize.Height);
// return the desired size
return new Size(this.formattedText.Width, this.formattedText.Height);
}
protected override Size ArrangeOverride(Size finalSize)
{
this.EnsureFormattedText();
// update the formatted text with the final size
this.formattedText.MaxTextWidth = finalSize.Width;
this.formattedText.MaxTextHeight = finalSize.Height;
// need to re-generate the geometry now that the dimensions have changed
this.textGeometry = null;
return finalSize;
}
private static void OnFormattedTextInvalidated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock.formattedText = null;
outlinedTextBlock.textGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private static void OnFormattedTextUpdated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock.UpdateFormattedText();
outlinedTextBlock.textGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private void EnsureFormattedText()
{
if (this.formattedText != null || this.Text == null)
{
return;
}
this.formattedText = new FormattedText(
this.Text,
CultureInfo.CurrentUICulture,
this.FlowDirection,
new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, FontStretches.Normal),
this.FontSize,
Brushes.Black);
this.UpdateFormattedText();
}
private void UpdateFormattedText()
{
if (this.formattedText == null)
{
return;
}
this.formattedText.MaxLineCount = this.TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
this.formattedText.TextAlignment = this.TextAlignment;
this.formattedText.Trimming = this.TextTrimming;
this.formattedText.SetFontSize(this.FontSize);
this.formattedText.SetFontStyle(this.FontStyle);
this.formattedText.SetFontWeight(this.FontWeight);
this.formattedText.SetFontFamily(this.FontFamily);
this.formattedText.SetFontStretch(this.FontStretch);
this.formattedText.SetTextDecorations(this.TextDecorations);
}
private void EnsureGeometry()
{
if (this.textGeometry != null)
{
return;
}
this.EnsureFormattedText();
this.textGeometry = this.formattedText.BuildGeometry(new Point(0, 0));
}
}
가장 많이 투표된 답변을 다음과 같은 몇 가지 수정으로 수정했습니다.
UseLayoutRounding을 사용할 때 한 줄의 텍스트가 표시되도록 수정하십시오.
윤곽선은 테두리 가운데가 아니라 텍스트 외부에 표시됩니다.
펜은 각 렌더가 아닌 한 번만 작성됩니다.
텍스트가 null로 설정될 때 충돌하지 않도록 수정하십시오.
윤곽이 적절한 둥근 대문자 모양을 사용하도록 수정하십시오.
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
[ContentProperty("Text")]
public class OutlinedTextBlock : FrameworkElement
{
private void UpdatePen() {
_Pen = new Pen(Stroke, StrokeThickness) {
DashCap = PenLineCap.Round,
EndLineCap = PenLineCap.Round,
LineJoin = PenLineJoin.Round,
StartLineCap = PenLineCap.Round
};
InvalidateVisual();
}
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));
private static void StrokePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs) {
(dependencyObject as OutlinedTextBlock)?.UpdatePen();
}
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(double),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextInvalidated));
public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
"TextAlignment",
typeof(TextAlignment),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(
"TextDecorations",
typeof(TextDecorationCollection),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
"TextTrimming",
typeof(TextTrimming),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
"TextWrapping",
typeof(TextWrapping),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(TextWrapping.NoWrap, OnFormattedTextUpdated));
private FormattedText _FormattedText;
private Geometry _TextGeometry;
private Pen _Pen;
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public TextAlignment TextAlignment
{
get { return (TextAlignment)GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
public TextDecorationCollection TextDecorations
{
get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); }
}
public TextTrimming TextTrimming
{
get { return (TextTrimming)GetValue(TextTrimmingProperty); }
set { SetValue(TextTrimmingProperty, value); }
}
public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
public OutlinedTextBlock() {
UpdatePen();
TextDecorations = new TextDecorationCollection();
}
protected override void OnRender(DrawingContext drawingContext) {
EnsureGeometry();
drawingContext.DrawGeometry(null, _Pen, _TextGeometry);
drawingContext.DrawGeometry(Fill, null, _TextGeometry);
}
protected override Size MeasureOverride(Size availableSize) {
EnsureFormattedText();
// constrain the formatted text according to the available size
double w = availableSize.Width;
double h = availableSize.Height;
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
// the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
_FormattedText.MaxTextWidth = Math.Min(3579139, w);
_FormattedText.MaxTextHeight = Math.Max(0.0001d, h);
// return the desired size
return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));
}
protected override Size ArrangeOverride(Size finalSize) {
EnsureFormattedText();
// update the formatted text with the final size
_FormattedText.MaxTextWidth = finalSize.Width;
_FormattedText.MaxTextHeight = Math.Max(0.0001d, finalSize.Height);
// need to re-generate the geometry now that the dimensions have changed
_TextGeometry = null;
return finalSize;
}
private static void OnFormattedTextInvalidated(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e) {
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock._FormattedText = null;
outlinedTextBlock._TextGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private static void OnFormattedTextUpdated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e) {
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock.UpdateFormattedText();
outlinedTextBlock._TextGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private void EnsureFormattedText() {
if (_FormattedText != null) {
return;
}
_FormattedText = new FormattedText(
Text ?? "",
CultureInfo.CurrentUICulture,
FlowDirection,
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize,
Brushes.Black);
UpdateFormattedText();
}
private void UpdateFormattedText() {
if (_FormattedText == null) {
return;
}
_FormattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
_FormattedText.TextAlignment = TextAlignment;
_FormattedText.Trimming = TextTrimming;
_FormattedText.SetFontSize(FontSize);
_FormattedText.SetFontStyle(FontStyle);
_FormattedText.SetFontWeight(FontWeight);
_FormattedText.SetFontFamily(FontFamily);
_FormattedText.SetFontStretch(FontStretch);
_FormattedText.SetTextDecorations(TextDecorations);
}
private void EnsureGeometry() {
if (_TextGeometry != null) {
return;
}
EnsureFormattedText();
_TextGeometry = _FormattedText.BuildGeometry(new Point(0, 0));
}
}
찾았다.WPF에는 스트로크 텍스트가 내장되어 있지 않은 것 같습니다(내 생각에는 큰 누락 기능인 것 같습니다).먼저 커스텀 클래스를 만듭니다.
using System;
using System.Windows.Media;
using System.Globalization;
using System.Windows;
using System.Windows.Markup;
namespace CustomXaml
{
public class OutlinedText : FrameworkElement, IAddChild
{
#region Private Fields
private Geometry _textGeometry;
#endregion
#region Private Methods
/// <summary>
/// Invoked when a dependency property has changed. Generate a new FormattedText object to display.
/// </summary>
/// <param name="d">OutlineText object whose property was updated.</param>
/// <param name="e">Event arguments for the dependency property.</param>
private static void OnOutlineTextInvalidated(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
((OutlinedText)d).CreateText();
}
#endregion
#region FrameworkElement Overrides
/// <summary>
/// OnRender override draws the geometry of the text and optional highlight.
/// </summary>
/// <param name="drawingContext">Drawing context of the OutlineText control.</param>
protected override void OnRender(DrawingContext drawingContext)
{
CreateText();
// Draw the outline based on the properties that are set.
drawingContext.DrawGeometry(Fill, new Pen(Stroke, StrokeThickness), _textGeometry);
}
/// <summary>
/// Create the outline geometry based on the formatted text.
/// </summary>
public void CreateText()
{
FontStyle fontStyle = FontStyles.Normal;
FontWeight fontWeight = FontWeights.Medium;
if (Bold == true) fontWeight = FontWeights.Bold;
if (Italic == true) fontStyle = FontStyles.Italic;
// Create the formatted text based on the properties set.
FormattedText formattedText = new FormattedText(
Text,
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface(Font, fontStyle, fontWeight, FontStretches.Normal),
FontSize,
Brushes.Black // This brush does not matter since we use the geometry of the text.
);
// Build the geometry object that represents the text.
_textGeometry = formattedText.BuildGeometry(new Point(0, 0));
//set the size of the custome control based on the size of the text
this.MinWidth = formattedText.Width;
this.MinHeight = formattedText.Height;
}
#endregion
#region DependencyProperties
/// <summary>
/// Specifies whether the font should display Bold font weight.
/// </summary>
public bool Bold
{
get
{
return (bool)GetValue(BoldProperty);
}
set
{
SetValue(BoldProperty, value);
}
}
/// <summary>
/// Identifies the Bold dependency property.
/// </summary>
public static readonly DependencyProperty BoldProperty = DependencyProperty.Register(
"Bold",
typeof(bool),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// Specifies the brush to use for the fill of the formatted text.
/// </summary>
public Brush Fill
{
get
{
return (Brush)GetValue(FillProperty);
}
set
{
SetValue(FillProperty, value);
}
}
/// <summary>
/// Identifies the Fill dependency property.
/// </summary>
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
new SolidColorBrush(Colors.LightSteelBlue),
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// The font to use for the displayed formatted text.
/// </summary>
public FontFamily Font
{
get
{
return (FontFamily)GetValue(FontProperty);
}
set
{
SetValue(FontProperty, value);
}
}
/// <summary>
/// Identifies the Font dependency property.
/// </summary>
public static readonly DependencyProperty FontProperty = DependencyProperty.Register(
"Font",
typeof(FontFamily),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
new FontFamily("Arial"),
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// The current font size.
/// </summary>
public double FontSize
{
get
{
return (double)GetValue(FontSizeProperty);
}
set
{
SetValue(FontSizeProperty, value);
}
}
/// <summary>
/// Identifies the FontSize dependency property.
/// </summary>
public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register(
"FontSize",
typeof(double),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
(double)48.0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// Specifies whether the font should display Italic font style.
/// </summary>
public bool Italic
{
get
{
return (bool)GetValue(ItalicProperty);
}
set
{
SetValue(ItalicProperty, value);
}
}
/// <summary>
/// Identifies the Italic dependency property.
/// </summary>
public static readonly DependencyProperty ItalicProperty = DependencyProperty.Register(
"Italic",
typeof(bool),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// Specifies the brush to use for the stroke and optional hightlight of the formatted text.
/// </summary>
public Brush Stroke
{
get
{
return (Brush)GetValue(StrokeProperty);
}
set
{
SetValue(StrokeProperty, value);
}
}
/// <summary>
/// Identifies the Stroke dependency property.
/// </summary>
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
new SolidColorBrush(Colors.Teal),
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// The stroke thickness of the font.
/// </summary>
public ushort StrokeThickness
{
get
{
return (ushort)GetValue(StrokeThicknessProperty);
}
set
{
SetValue(StrokeThicknessProperty, value);
}
}
/// <summary>
/// Identifies the StrokeThickness dependency property.
/// </summary>
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(ushort),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
(ushort)0,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
/// <summary>
/// Specifies the text string to display.
/// </summary>
public string Text
{
get
{
return (string)GetValue(TextProperty);
}
set
{
SetValue(TextProperty, value);
}
}
/// <summary>
/// Identifies the Text dependency property.
/// </summary>
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(OutlinedText),
new FrameworkPropertyMetadata(
"",
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnOutlineTextInvalidated),
null
)
);
public void AddChild(Object value)
{
}
public void AddText(string value)
{
Text = value;
}
#endregion
}
}
는 xaml에서 참조할 수 있습니다.
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:customControls="clr-namespace:CustomXaml;assembly=CustomXaml">
<Grid>
<customControls:OutlinedText x:Name="TextContent" Fill="#ffffffff" FontSize="28"
Bold="True" Stroke="Black" StrokeThickness="1" Text="Back" Margin="10,0,10,0"
HorizontalAlignment="Center" VerticalAlignment="Center" Height="Auto" Width="Auto" />
</Grid>
</Page>
@Javier G. answer를 수정했습니다.
스트로크 위치는 중앙, 외부 또는 내부 중 하나이며 기본값은 외부입니다.
필은 투명할 수 있습니다.
센터:
외부:
내부:
코드:
using System;
using System.ComponentModel;
using System.Globalization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Windows.Media;
namespace WpfApp2
{
public enum StrokePosition
{
Center,
Outside,
Inside
}
[ContentProperty("Text")]
public class OutlinedTextBlock : FrameworkElement
{
private void UpdatePen()
{
_Pen = new Pen(Stroke, StrokeThickness)
{
DashCap = PenLineCap.Round,
EndLineCap = PenLineCap.Round,
LineJoin = PenLineJoin.Round,
StartLineCap = PenLineCap.Round
};
if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
{
_Pen.Thickness = StrokeThickness * 2;
}
InvalidateVisual();
}
public StrokePosition StrokePosition
{
get { return (StrokePosition)GetValue(StrokePositionProperty); }
set { SetValue(StrokePositionProperty, value); }
}
public static readonly DependencyProperty StrokePositionProperty =
DependencyProperty.Register("StrokePosition",
typeof(StrokePosition),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(StrokePosition.Outside, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
"Fill",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
"Stroke",
typeof(Brush),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
"StrokeThickness",
typeof(double),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender));
public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
"Text",
typeof(string),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextInvalidated));
public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
"TextAlignment",
typeof(TextAlignment),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(
"TextDecorations",
typeof(TextDecorationCollection),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
"TextTrimming",
typeof(TextTrimming),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(OnFormattedTextUpdated));
public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
"TextWrapping",
typeof(TextWrapping),
typeof(OutlinedTextBlock),
new FrameworkPropertyMetadata(TextWrapping.NoWrap, OnFormattedTextUpdated));
private FormattedText _FormattedText;
private Geometry _TextGeometry;
private Pen _Pen;
private PathGeometry _clipGeometry;
public Brush Fill
{
get { return (Brush)GetValue(FillProperty); }
set { SetValue(FillProperty, value); }
}
public FontFamily FontFamily
{
get { return (FontFamily)GetValue(FontFamilyProperty); }
set { SetValue(FontFamilyProperty, value); }
}
[TypeConverter(typeof(FontSizeConverter))]
public double FontSize
{
get { return (double)GetValue(FontSizeProperty); }
set { SetValue(FontSizeProperty, value); }
}
public FontStretch FontStretch
{
get { return (FontStretch)GetValue(FontStretchProperty); }
set { SetValue(FontStretchProperty, value); }
}
public FontStyle FontStyle
{
get { return (FontStyle)GetValue(FontStyleProperty); }
set { SetValue(FontStyleProperty, value); }
}
public FontWeight FontWeight
{
get { return (FontWeight)GetValue(FontWeightProperty); }
set { SetValue(FontWeightProperty, value); }
}
public Brush Stroke
{
get { return (Brush)GetValue(StrokeProperty); }
set { SetValue(StrokeProperty, value); }
}
public double StrokeThickness
{
get { return (double)GetValue(StrokeThicknessProperty); }
set { SetValue(StrokeThicknessProperty, value); }
}
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
public TextAlignment TextAlignment
{
get { return (TextAlignment)GetValue(TextAlignmentProperty); }
set { SetValue(TextAlignmentProperty, value); }
}
public TextDecorationCollection TextDecorations
{
get { return (TextDecorationCollection)GetValue(TextDecorationsProperty); }
set { SetValue(TextDecorationsProperty, value); }
}
public TextTrimming TextTrimming
{
get { return (TextTrimming)GetValue(TextTrimmingProperty); }
set { SetValue(TextTrimmingProperty, value); }
}
public TextWrapping TextWrapping
{
get { return (TextWrapping)GetValue(TextWrappingProperty); }
set { SetValue(TextWrappingProperty, value); }
}
public OutlinedTextBlock()
{
UpdatePen();
TextDecorations = new TextDecorationCollection();
}
protected override void OnRender(DrawingContext drawingContext)
{
EnsureGeometry();
drawingContext.DrawGeometry(Fill, null, _TextGeometry);
if (StrokePosition == StrokePosition.Outside)
{
drawingContext.PushClip(_clipGeometry);
}
else if (StrokePosition == StrokePosition.Inside)
{
drawingContext.PushClip(_TextGeometry);
}
drawingContext.DrawGeometry(null, _Pen, _TextGeometry);
if (StrokePosition == StrokePosition.Outside || StrokePosition == StrokePosition.Inside)
{
drawingContext.Pop();
}
}
protected override Size MeasureOverride(Size availableSize)
{
EnsureFormattedText();
// constrain the formatted text according to the available size
double w = availableSize.Width;
double h = availableSize.Height;
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
// the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
_FormattedText.MaxTextWidth = Math.Min(3579139, w);
_FormattedText.MaxTextHeight = Math.Max(0.0001d, h);
// return the desired size
return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));
}
protected override Size ArrangeOverride(Size finalSize)
{
EnsureFormattedText();
// update the formatted text with the final size
_FormattedText.MaxTextWidth = finalSize.Width;
_FormattedText.MaxTextHeight = Math.Max(0.0001d, finalSize.Height);
// need to re-generate the geometry now that the dimensions have changed
_TextGeometry = null;
UpdatePen();
return finalSize;
}
private static void OnFormattedTextInvalidated(DependencyObject dependencyObject,
DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock._FormattedText = null;
outlinedTextBlock._TextGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private static void OnFormattedTextUpdated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
{
var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
outlinedTextBlock.UpdateFormattedText();
outlinedTextBlock._TextGeometry = null;
outlinedTextBlock.InvalidateMeasure();
outlinedTextBlock.InvalidateVisual();
}
private void EnsureFormattedText()
{
if (_FormattedText != null)
{
return;
}
_FormattedText = new FormattedText(
Text ?? "",
CultureInfo.CurrentUICulture,
FlowDirection,
new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
FontSize,
Brushes.Black);
UpdateFormattedText();
}
private void UpdateFormattedText()
{
if (_FormattedText == null)
{
return;
}
_FormattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
_FormattedText.TextAlignment = TextAlignment;
_FormattedText.Trimming = TextTrimming;
_FormattedText.SetFontSize(FontSize);
_FormattedText.SetFontStyle(FontStyle);
_FormattedText.SetFontWeight(FontWeight);
_FormattedText.SetFontFamily(FontFamily);
_FormattedText.SetFontStretch(FontStretch);
_FormattedText.SetTextDecorations(TextDecorations);
}
private void EnsureGeometry()
{
if (_TextGeometry != null)
{
return;
}
EnsureFormattedText();
_TextGeometry = _FormattedText.BuildGeometry(new Point(0, 0));
if (StrokePosition == StrokePosition.Outside)
{
var boundsGeo = new RectangleGeometry(new Rect(0, 0, ActualWidth, ActualHeight));
_clipGeometry = Geometry.Combine(boundsGeo, _TextGeometry, GeometryCombineMode.Exclude, null);
}
}
}
}
사용방법:
<Grid Margin="12" Background="Bisque">
<local:OutlinedTextBlock Stroke="Red"
ClipToBounds="False"
FontSize="56"
Fill="Transparent"
StrokePosition="Inside"
StrokeThickness="1" Text=" abc">
</local:OutlinedTextBlock>
</Grid>
텍스트 블록을 테두리로 묶어야 합니다.예를 들어 다음과 같습니다.
<Border BorderBrush="Purple" BorderThickness="2">
<TextBlock>My fancy TextBlock</TextBlock>
</Border>
(전체 텍스트 블록이 아닌) 실제 문자 주위에 스트로크를 배치하는 방법을 묻는 경우 Glow의 비트맵 효과를 사용하고 Glow의 파라미터를 원하는 스트로크 색상으로 설정하는 등의 방법을 검토할 수 있습니다.그렇지 않으면 사용자 정의 항목을 작성해야 할 수 있습니다.
다른 옵션은 일반 텍스트 블록을 사용하지만 사용자 정의 효과를 적용하는 것입니다.
이 Shader 튜토리얼과 Prewitt Edge Detection Filter를 조합하여 텍스트 주위에 적절한 아웃라인 효과를 얻을 수 있었습니다.GPU를 사용하여 렌더링하고 모든 UIElement에 적용할 수 있는 장점이 있지만, @Kent Boogaart의 답변은 조금 더 보기 좋고 EdgeResponse는 까다롭기 때문에 멋진 윤곽을 얻기 위해 여러 번 가지고 놀아야 했습니다.
최종 결과는 XAML입니다.
<Grid>
<Grid.Resources>
<local:EdgeDetectionEffect x:Key="OutlineEffect"
x:Shared="false"
EdgeResponse=".44"
ActualHeight="{Binding RelativeSource={RelativeSource AncestorType=TextBlock}, Path=ActualHeight}"
ActualWidth="{Binding RelativeSource={RelativeSource AncestorType=TextBlock}, Path=ActualWidth}"/>
</Grid.Resources>
<TextBlock Text="The Crazy Brown Fox Jumped Over the Lazy Dog."
FontWeight="Bold"
FontSize="25"
Foreground="Yellow"
Effect="{StaticResource OutlineEffect}"/>
</Grid>
커스텀 효과를 작성하기 위해 EdgeDetectionColorEffect.fx(hdld) 파일을 최초로 작성했습니다.이 파일은 GPU가 이미지를 필터링하기 위해 사용하는 코드입니다.Visual Studio 명령 프롬프트에서 다음 명령을 사용하여 컴파일했습니다.
fxc /T ps_2_0 /E 메인 / Focc.ps Edge Detection Color Effect.fx
sampler2D Input : register(s0);
float ActualWidth : register(c0);
float ActualHeight : register(c1);
float4 OutlineColor : register(c2);
float EdgeDetectionResponse : register(c3);
float4 GetNeighborPixel(float2 pixelPoint, float xOffset, float yOffset)
{
float2 NeighborPoint = {pixelPoint.x + xOffset, pixelPoint.y + yOffset};
return tex2D(Input, NeighborPoint);
}
// pixel locations:
// 00 01 02
// 10 11 12
// 20 21 22
float main(float2 pixelPoint : TEXCOORD) : COLOR
{
float wo = 1 / ActualWidth; //WidthOffset
float ho = 1 / ActualHeight; //HeightOffset
float4 c00 = GetNeighborPixel(pixelPoint, -wo, -ho); // color of the pixel up and to the left of me.
float4 c01 = GetNeighborPixel(pixelPoint, 00, -ho);
float4 c02 = GetNeighborPixel(pixelPoint, wo, -ho);
float4 c10 = GetNeighborPixel(pixelPoint, -wo, 0);
float4 c11 = tex2D(Input, pixelPoint); // this is the current pixel
float4 c12 = GetNeighborPixel(pixelPoint, wo, 0);
float4 c20 = GetNeighborPixel(pixelPoint, -wo, ho);
float4 c21 = GetNeighborPixel(pixelPoint, 0, ho);
float4 c22 = GetNeighborPixel(pixelPoint, wo, ho);
float t00 = c00.r + c00.g + c00.b; //total of color channels
float t01 = c01.r + c01.g + c01.b;
float t02 = c02.r + c02.g + c02.b;
float t10 = c10.r + c10.g + c10.b;
float t11 = c11.r + c11.g + c11.b;
float t12 = c12.r + c12.g + c12.b;
float t20 = c20.r + c20.g + c20.b;
float t21 = c21.r + c21.g + c21.b;
float t22 = c22.r + c22.g + c22.b;
//Prewitt - convolve the 9 pixels with:
// 01 01 01 01 00 -1
// Gy = 00 00 00 Gx = 01 00 -1
// -1 -1 -1 01 00 -1
float gy = 0.0; float gx = 0.0;
gy += t00; gx += t00;
gy += t01; gx += t10;
gy += t02; gx += t20;
gy -= t20; gx -= t02;
gy -= t21; gx -= t12;
gy -= t22; gx -= t22;
if((gy*gy + gx*gx) > EdgeDetectionResponse)
{
return OutlineColor;
}
return c11;
}
wpf 효과 클래스는 다음과 같습니다.
public class EdgeDetectionEffect : ShaderEffect
{
private static PixelShader _shader = new PixelShader { UriSource = new Uri("path to your compiled shader probably called cc.ps", UriKind.Absolute) };
public EdgeDetectionEffect()
{
PixelShader = _shader;
UpdateShaderValue(InputProperty);
UpdateShaderValue(ActualHeightProperty);
UpdateShaderValue(ActualWidthProperty);
UpdateShaderValue(OutlineColorProperty);
UpdateShaderValue(EdgeResponseProperty);
}
public Brush Input
{
get => (Brush)GetValue(InputProperty);
set => SetValue(InputProperty, value);
}
public static readonly DependencyProperty InputProperty =
ShaderEffect.RegisterPixelShaderSamplerProperty(nameof(Input),
typeof(EdgeDetectionEffect), 0);
public double ActualWidth
{
get => (double)GetValue(ActualWidthProperty);
set => SetValue(ActualWidthProperty, value);
}
public static readonly DependencyProperty ActualWidthProperty =
DependencyProperty.Register(nameof(ActualWidth), typeof(double), typeof(EdgeDetectionEffect),
new UIPropertyMetadata(1.0, PixelShaderConstantCallback(0)));
public double ActualHeight
{
get => (double)GetValue(ActualHeightProperty);
set => SetValue(ActualHeightProperty, value);
}
public static readonly DependencyProperty ActualHeightProperty =
DependencyProperty.Register(nameof(ActualHeight), typeof(double), typeof(EdgeDetectionEffect),
new UIPropertyMetadata(1.0, PixelShaderConstantCallback(1)));
public Color OutlineColor
{
get => (Color)GetValue(OutlineColorProperty);
set => SetValue(OutlineColorProperty, value);
}
public static readonly DependencyProperty OutlineColorProperty=
DependencyProperty.Register(nameof(OutlineColor), typeof(Color), typeof(EdgeDetectionEffect),
new UIPropertyMetadata(Colors.Black, PixelShaderConstantCallback(2)));
public double EdgeResponse
{
get => (double)GetValue(EdgeResponseProperty);
set => SetValue(EdgeResponseProperty, value);
}
public static readonly DependencyProperty EdgeResponseProperty =
DependencyProperty.Register(nameof(EdgeResponse), typeof(double), typeof(EdgeDetectionEffect),
new UIPropertyMetadata(4.0, PixelShaderConstantCallback(3)));
}
MSDN의 "How to: Create Outlineed Text"에는 필요한 정보가 모두 포함되어 있습니다.
만약 누구에게 신청한다면, 여기 XAML만을 사용한 간단한 솔루션이 있습니다.이 솔루션이 더 나은지 나쁜지는 모르겠지만, 제 생각에는 위의 어떤 솔루션보다 더 나은 것 같습니다.는 그것을 로 한다.ContentControl
스타일(및 템플릿), 이 Old School의 예를 따릅니다.http://oldschooldotnet.blogspot.co.il/2009/02/fancy-fonts-in-xaml-silverlight-and-wpf.html
<Style x:Key="OutlinedText" TargetType="{x:Type ContentControl}">
<!-- Some Style Setters -->
<Setter Property="Content" Value="Outlined Text"/>
<Setter Property="Padding" Value="0"/>
<!-- Border Brush Must be equal '0' because TextBlock that emulate the stroke will using the BorderBrush as to define 'Stroke' color-->
<Setter Property="BorderThickness" Value="0"/>
<!-- Border Brush define 'Stroke' Color-->
<Setter Property="BorderBrush" Value="White"/>
<Setter Property="Foreground" Value="Black"/>
<Setter Property="FontSize" Value="24"/>
<Setter Property="FontFamily" Value="Seoge UI Bold"/>
<Setter Property="HorizontalContentAlignment" Value="Center"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type ContentControl}">
<Canvas Width="{Binding ActualWidth, ElementName=FillText}" Height="{Binding ActualHeight, ElementName=FillText}">
<Canvas.Resources>
<!-- Style to ease the duplication of Text Blocks that emulate the stroke: Binding to one element (or to template) is the first part of the Trick -->
<Style x:Key="OutlinedTextStrokeTextBlock_Style" TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="{Binding Text, ElementName=FillText}"/>
<Setter Property="FontSize" Value="{Binding FontSize, ElementName=FillText}"/>
<Setter Property="FontFamily" Value="{Binding FontFamily, ElementName=FillText}"/>
<Setter Property="FontStyle" Value="{Binding FontStyle, ElementName=FillText}"/>
<Setter Property="FontWeight" Value="{Binding FontWeight, ElementName=FillText}"/>
<Setter Property="Padding" Value="{Binding TextAlignment, ElementName=Padding}"/>
<Setter Property="TextAlignment" Value="{Binding TextAlignment, ElementName=FillText}"/>
<Setter Property="VerticalAlignment" Value="{Binding VerticalAlignment, ElementName=FillText}"/>
</Style>
</Canvas.Resources>
<!-- Offseting the Text block will create the outline, the margin is the Stroke Width-->
<TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="-1,0,0,0" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
<TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="0,-1,0,0" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
<TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="0,0,-1,0" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
<TextBlock Foreground="{TemplateBinding BorderBrush}" Margin="0,0,0,-1" Style="{DynamicResource OutlinedTextStrokeTextBlock_Style}"/>
<!-- Base TextBlock Will be the Fill -->
<TextBlock x:Name="FillText" Text="{TemplateBinding Content}" FontSize="{TemplateBinding FontSize}" FontFamily="{TemplateBinding FontFamily}"
FontStyle="{TemplateBinding FontStyle}" FontWeight="{TemplateBinding FontWeight}" Padding="0" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
TextAlignment="{TemplateBinding HorizontalContentAlignment}"
Style="{DynamicResource TbMediaOverlay_Style}"/>
</Canvas>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
이미 설명한 대로 텍스트를 경로로 변환
FormattedText t = new FormattedText
(
"abcxyz",
CultureInfo.GetCultureInfo("en-us"),
FlowDirection.LeftToRight,
new Typeface(
new FontFamily("Arial"),
new FontStyle(),
new FontWeight(),
new FontStretch()),
20,
Brushes.Transparent
);
Geometry g = t.BuildGeometry(new System.Windows.Point(0, 0));
Path p = new Path();
p.Fill = Brushes.White;
p.Stroke = Brushes.Black;
p.StrokeThickness = 1;
p.Data = g;
혼합에서 텍스트 블록을 경로로 변환한 다음 일반 스트로크 특성을 사용할 수 있습니다.하지만 역동적으로 만들 수 있는 걸 원하셨겠죠
그렇지 않으면 비트맵 효과나 특수 브러시가 필요할 것 같습니다.
켄트 부가르트이 코드는 훌륭하지만 작은 디테일이 빠져 있습니다.은 필 몇 할 수 .OnRender()
Viewbox
비록 (와 ) 할지 알 수 이다.TextBox
( ( ( ( (.
protected override void OnRender(DrawingContext drawingContext)
{
this.EnsureGeometry();
this.Width = this.formattedText.Width;
this.Height = this.formattedText.Height;
drawingContext.DrawGeometry(this.Fill, new Pen(this.Stroke, this.StrokeThickness), this.textGeometry);
}
2겹의 텍스트로 사용하고 있기 때문에, 스트로크는 다음과 같이 외형적으로 밖에 보이지 않습니다.이것은 특정 이미지와 글꼴을 가리키기 때문에 즉시 작동하지는 않을 것입니다.
<Viewbox Stretch="UniformToFill" Margin="0" Grid.Column="1">
<bd:OutlinedText x:Name="LevelTitleStroke" Text="Level" FontSize="80pt" FontFamily="/fonts/papercuts-2.ttf#Paper Cuts 2" Grid.Row="1" TextAlignment="Center" IsHitTestVisible="False" StrokeThickness="15">
<bd:OutlinedText.Stroke>
<ImageBrush ImageSource="/WpfApplication1;component/GrungeMaps/03DarkBlue.jpg" Stretch="None" />
</bd:OutlinedText.Stroke>
</bd:OutlinedText>
</Viewbox>
<Viewbox Stretch="UniformToFill" Margin="0" Grid.Column="1">
<bd:OutlinedText x:Name="LevelTitleFill" Text="Level" FontSize="80pt" FontFamily="/fonts/papercuts-2.ttf#Paper Cuts 2" Grid.Row="1" TextAlignment="Center" IsHitTestVisible="False">
<bd:OutlinedText.Fill>
<ImageBrush ImageSource="/WpfApplication1;component/GrungeMaps/03Red.jpg" Stretch="None" />
</bd:OutlinedText.Fill>
</bd:OutlinedText>
</Viewbox>
저도 이와 비슷한 일을 하려고 했어요.여기에 언급된 강의는 훌륭했지만, 제가 찾던 것은 아니었습니다. 왜냐하면 텍스트가 충분히 커야 제대로 보여졌기 때문입니다.표시하려고 했던 텍스트는 10~11글자 크기였고, 스트로크가 너무 커서 글자가 섞였습니다.
참고로 이 텍스트는 사용자 정의 사진에 겹쳐져 있고, 색상은 다양할 수 있기 때문에 이 텍스트가 확실히 표시되도록 하고 싶었습니다.
이것이 베스트 프랙티스인지 아닌지는 모르겠지만 적어도 내가 원하는 모양(이 기사 기준)을 실현했습니다.
<Style x:Key="OutlinedTextBlockOuter" TargetType="TextBlock">
<Setter Property="Foreground" Value="Black" />
<Setter Property="FontSize" Value="10"/>
<Setter Property="Effect">
<Setter.Value>
<BlurEffect Radius="3.0"/>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="OutlinedTextBlockInner" TargetType="TextBlock">
<Setter Property="Foreground" Value="White" />
<Setter Property="FontSize" Value="10"/>
</Style>
그런 다음 실제 TextBlocks에 대해 Outer 스타일의 TextBlocks 2개와 Inner 스타일의 TextBlock 1개를 조합했습니다.
<Grid Margin="5">
<TextBlock Style="{StaticResource OutlinedTextBlockOuter}" Text="This is outlined text using BlurEffect"/>
<TextBlock Style="{StaticResource OutlinedTextBlockOuter}" Text="This is outlined text using BlurEffect"/>
<TextBlock Style="{StaticResource OutlinedTextBlockInner}" Text="This is outlined text using BlurEffect"/>
</Grid>
또는 DropShadowEffect를 사용할 수도 있습니다.DropShadowEffect는 2개의 텍스트 상자만 사용해도 문제가 없습니다(다만, 방향이 다양하고 불투명도가 낮은 DropShadowEffect를 추가하는 것은 훨씬 더 좋아 보일 수 있습니다).
<Grid Margin="5">
<TextBlock Text="This is my outlined text using the DropShadowEffect" FontSize="10" Foreground="White">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.75" Direction="315"/>
</TextBlock.Effect>
</TextBlock>
<TextBlock Text="This is my outlined text using the DropShadowEffect" FontSize="10" Foreground="White">
<TextBlock.Effect>
<DropShadowEffect ShadowDepth="1" BlurRadius="2" Opacity="0.75" Direction="135"/>
</TextBlock.Effect>
</TextBlock>
</Grid>
레이아웃 반올림을 사용할 때 한 줄의 텍스트가 표시되도록 MeasureOverride에 추가해야 했습니다.그래도 텍스트 포장할 때는 잘 됐어요.
// return the desired size
return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));
<TextBlock>에는 장식 속성 자체는 없습니다.<직각>으로 <캔버스>에 올려놓고, 거기에 스트로크를 가합니다.
대신 라벨을 사용할 수 있습니다.그것은 당신이 가지고 놀 수 있는 더 많은 특성들을 가지고 있습니다.예:
<Style x:Key="LeftBorderLabel" TargetType="{x:Type Label}">
<Setter Property="Margin" Value="0" />
<Setter Property="BorderThickness" Value="1,0,0,0" />
<Setter Property="BorderBrush" Value="Blue" />
</Style>
이것은 나에게 큰 도움이 되었다!나중에 필요할 경우를 대비해서 VB 버전(made Stroke)을 소개합니다.두께 a를 2배로 하고 밑줄 속성을 추가합니다).
Imports System
Imports System.Windows.Media
Imports System.Globalization
Imports System.Windows
Imports System.Windows.Markup
Namespace CustomXaml
Public Class OutlinedText
Inherits FrameworkElement
Implements IAddChild
Private _textGeometry As Geometry
Private Shared Sub OnOutlineTextInvalidated(d As DependencyObject, e As DependencyPropertyChangedEventArgs)
DirectCast(d, OutlinedText).CreateText()
End Sub
Protected Overrides Sub OnRender(drawingContext As System.Windows.Media.DrawingContext)
CreateText()
drawingContext.DrawGeometry(Fill, New Pen(Stroke, StrokeThickness), _textGeometry)
End Sub
Public Sub CreateText()
Dim fontStyle = FontStyles.Normal
Dim fontWeight = FontWeights.Medium
Dim fontDecoration = New TextDecorationCollection()
If Bold Then fontWeight = FontWeights.Bold
If Italic Then fontStyle = FontStyles.Italic
If Underline Then fontDecoration.Add(TextDecorations.Underline)
Dim formattedText = New FormattedText( _
Text, _
CultureInfo.GetCultureInfo("en-us"), _
FlowDirection.LeftToRight, _
New Typeface(Font, fontStyle, fontWeight, FontStretches.Normal), _
FontSize, _
Brushes.Black _
)
formattedText.SetTextDecorations(fontDecoration)
_textGeometry = formattedText.BuildGeometry(New Point(0, 0))
Me.MinWidth = formattedText.Width
Me.MinHeight = formattedText.Height
End Sub
Public Property Bold As Boolean
Get
Return CType(GetValue(BoldProperty), Boolean)
End Get
Set(value As Boolean)
SetValue(BoldProperty, value)
End Set
End Property
Public Shared ReadOnly BoldProperty As DependencyProperty = DependencyProperty.Register( _
"Bold", _
GetType(Boolean), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
False, _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property Underline As Boolean
Get
Return CType(GetValue(UnderlineProperty), Boolean)
End Get
Set(value As Boolean)
SetValue(UnderlineProperty, value)
End Set
End Property
Public Shared ReadOnly UnderlineProperty As DependencyProperty = DependencyProperty.Register( _
"Underline", _
GetType(Boolean), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
False, _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property Fill As Brush
Get
Return CType(GetValue(FillProperty), Brush)
End Get
Set(value As Brush)
SetValue(FillProperty, value)
End Set
End Property
Public Shared ReadOnly FillProperty As DependencyProperty = DependencyProperty.Register( _
"Fill", _
GetType(Brush), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
New SolidColorBrush(Colors.LightSteelBlue), _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property Font As FontFamily
Get
Return CType(GetValue(FontProperty), FontFamily)
End Get
Set(value As FontFamily)
SetValue(FontProperty, value)
End Set
End Property
Public Shared ReadOnly FontProperty As DependencyProperty = DependencyProperty.Register( _
"Font", _
GetType(FontFamily), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
New FontFamily("Arial"), _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property FontSize As Double
Get
Return CType(GetValue(FontSizeProperty), Double)
End Get
Set(value As Double)
SetValue(FontSizeProperty, value)
End Set
End Property
Public Shared ReadOnly FontSizeProperty As DependencyProperty = DependencyProperty.Register( _
"FontSize", _
GetType(Double), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
CDbl(48.0), _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property Italic As Boolean
Get
Return CType(GetValue(ItalicProperty), Boolean)
End Get
Set(value As Boolean)
SetValue(ItalicProperty, value)
End Set
End Property
Public Shared ReadOnly ItalicProperty As DependencyProperty = DependencyProperty.Register( _
"Italic", _
GetType(Boolean), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
False, _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property Stroke As Brush
Get
Return CType(GetValue(StrokeProperty), Brush)
End Get
Set(value As Brush)
SetValue(StrokeProperty, value)
End Set
End Property
Public Shared ReadOnly StrokeProperty As DependencyProperty = DependencyProperty.Register( _
"Stroke", _
GetType(Brush), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
New SolidColorBrush(Colors.Teal), _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property StrokeThickness As Double
Get
Return CType(GetValue(StrokeThicknessProperty), Double)
End Get
Set(value As Double)
SetValue(StrokeThicknessProperty, value)
End Set
End Property
Public Shared ReadOnly StrokeThicknessProperty As DependencyProperty = DependencyProperty.Register( _
"StrokeThickness", _
GetType(Double), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
CDbl(0), _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Property Text As String
Get
Return CType(GetValue(TextProperty), String)
End Get
Set(value As String)
SetValue(TextProperty, value)
End Set
End Property
Public Shared ReadOnly TextProperty As DependencyProperty = DependencyProperty.Register( _
"Text", _
GetType(String), _
GetType(OutlinedText), _
New FrameworkPropertyMetadata( _
"", _
FrameworkPropertyMetadataOptions.AffectsRender, _
New PropertyChangedCallback(AddressOf OnOutlineTextInvalidated), _
Nothing _
) _
)
Public Sub AddChild(value As Object) Implements System.Windows.Markup.IAddChild.AddChild
End Sub
Public Sub AddText(text As String) Implements System.Windows.Markup.IAddChild.AddText
Me.Text = text
End Sub
End Class
End Namespace
커스텀 컨트롤에 켄트의 솔루션을 사용하고 있었습니다.텍스트 속성에 대해 템플릿바인딩을 사용할 때 null 예외가 발생했습니다.
MeasureOverride 함수를 다음과 같이 수정해야 했습니다.
protected override Size MeasureOverride(Size availableSize)
{
this.EnsureFormattedText();
if (this.formattedText == null)
{
this.formattedText = new FormattedText(
(this.Text == null) ? "" : this.Text,
CultureInfo.CurrentUICulture,
this.FlowDirection,
new Typeface(this.FontFamily, this.FontStyle, this.FontWeight, FontStretches.Normal),
this.FontSize,
Brushes.Black);
}
// constrain the formatted text according to the available size
// the Math.Min call is important - without this constraint (which seems arbitrary, but is the maximum allowable text width), things blow up when availableSize is infinite in both directions
this.formattedText.MaxTextWidth = Math.Min(3579139, availableSize.Width);
this.formattedText.MaxTextHeight = availableSize.Height;
// return the desired size
return new Size(this.formattedText.Width, this.formattedText.Height);
}
주의할 점은 제가 이것을 철저히 테스트하지 않았다는 것입니다.
또 다른 답이 있으니까 제가 공식적으로 올리도록 하겠습니다.좀 덜 우아한 것 같아요. 사용하지 않고요.Geometry
,Path
그리고.FormattedText
(알고 싶은 경우) 렌더링 속도가 더 빠를 수 있습니다.기본적으로 같은 텍스트가 8번 있었지만 모든 주요 방향으로 이동했습니다.
다음은 사용자 제어 코드입니다.
/// <summary>
/// User Control to display a Text with an outline
/// </summary>
public partial class OutlinedText : UserControl, INotifyPropertyChanged
{
#region DependencyProperties
/// <summary>
/// The Text to render
/// </summary>
public string Text
{
get { return (string)GetValue(TextProperty); }
set { SetValue(TextProperty, value); }
}
// Using a DependencyProperty as the backing store for Text. This enables animation, styling, binding, etc...
public static readonly DependencyProperty TextProperty =
DependencyProperty.Register("Text", typeof(string), typeof(OutlinedText), new PropertyMetadata(""));
/// <summary>
/// The size (thickness) of the Stroke
/// </summary>
public int StrokeSize
{
get { return (int)GetValue(StrokeSizeProperty); }
set { SetValue(StrokeSizeProperty, value); }
}
// Using a DependencyProperty as the backing store for StrokeSize. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeSizeProperty =
DependencyProperty.Register("StrokeSize", typeof(int), typeof(OutlinedText), new PropertyMetadata(1));
/// <summary>
/// The Color of the Stroke
/// </summary>
public Brush StrokeColor
{
get { return (Brush)GetValue(StrokeColorProperty); }
set { SetValue(StrokeColorProperty, value); }
}
// Using a DependencyProperty as the backing store for StrokeColor. This enables animation, styling, binding, etc...
public static readonly DependencyProperty StrokeColorProperty =
DependencyProperty.Register("StrokeColor", typeof(Brush), typeof(OutlinedText), new PropertyMetadata(Brushes.Black));
#endregion
#region ctor
public OutlinedText()
{
InitializeComponent();
this.DataContext = this;
}
#endregion
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
및 XAML 측:
<UserControl x:Class="NAMESPACE.OutlinedText"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:NAMESPACE"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<ResourceDictionary>
<local:IntegerInverterConverter x:Key="IntegerInverterConverterKey"/>
</ResourceDictionary>
</UserControl.Resources>
<Grid>
<!--Bottom Right ⬊ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="{Binding StrokeSize}" Y="{Binding StrokeSize}"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Top Left ⬉ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="{Binding StrokeSize, Converter={StaticResource IntegerInverterConverterKey}}" Y="{Binding StrokeSize, Converter={StaticResource IntegerInverterConverterKey}}"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Bottom Left ⬋ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="{Binding StrokeSize, Converter={StaticResource IntegerInverterConverterKey}}" Y="{Binding StrokeSize}"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Top Right ⬈ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="{Binding StrokeSize}" Y="{Binding StrokeSize, Converter={StaticResource IntegerInverterConverterKey}}"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Top ⬆ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="0" Y="{Binding StrokeSize, Converter={StaticResource IntegerInverterConverterKey}}"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Bottom ⬇ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="0" Y="{Binding StrokeSize}"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Right ➡ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="{Binding StrokeSize}" Y="0"/>
</TextBlock.RenderTransform>
</TextBlock>
<!--Left ⬅ -->
<TextBlock Foreground="{Binding StrokeColor}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
RenderTransformOrigin="0.5, 0.5"
Text="{Binding Text}" >
<TextBlock.RenderTransform>
<TranslateTransform X="{Binding StrokeSize, Converter={StaticResource IntegerInverterConverterKey}}" Y="0"/>
</TextBlock.RenderTransform>
</TextBlock>
<TextBlock Foreground="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=Foreground}"
FontSize="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontSize}"
FontWeight="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontWeight}"
FontFamily="{Binding RelativeSource={RelativeSource AncestorType=UserControl},Path=FontFamily}"
Text="{Binding Text}" />
</Grid>
여기서 사용되는 변환기는 다른 속성을 사용하지 않기 위해 정수에 대한 단순한 *(-1)입니다.
사용방법:
<local:OutlinedText Margin="WHATEVER" HorizontalAlignment="WHATEVER" VerticalAlignment="WHATEVER"
Text="Your Text" StrokeColor="WhiteSmoke" StrokeSize="2" FontSize="20" FontWeight="Bold"
Foreground="Magenta"/>
언급URL : https://stackoverflow.com/questions/93650/apply-stroke-to-a-textblock-in-wpf
'sourcecode' 카테고리의 다른 글
WPF 애플리케이션을 재기동하려면 어떻게 해야 하나요? (0) | 2023.04.09 |
---|---|
UIBarButtonItem을 표시하거나 숨기려면 어떻게 해야 합니까? (0) | 2023.04.09 |
SQL Server에 Excel 파일을 Import하려면 어떻게 해야 합니까? (0) | 2023.04.09 |
xargs를 사용한 호출 셸 함수 (0) | 2023.04.09 |
로컬 SQL Server 인스턴스에 대한 관리자 액세스 권한을 자신에게 부여하려면 어떻게 해야 합니까? (0) | 2023.04.09 |