OutlinedTextBlock.cs 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. using System;
  2. using System.ComponentModel;
  3. using System.Globalization;
  4. using System.Windows;
  5. using System.Windows.Documents;
  6. using System.Windows.Markup;
  7. using System.Windows.Media;
  8. namespace DesktopClock;
  9. // https://stackoverflow.com/a/35262509
  10. [ContentProperty("Text")]
  11. public class OutlinedTextBlock : FrameworkElement
  12. {
  13. private void UpdatePen()
  14. {
  15. _Pen = new Pen(Stroke, StrokeThickness)
  16. {
  17. DashCap = PenLineCap.Round,
  18. EndLineCap = PenLineCap.Round,
  19. LineJoin = PenLineJoin.Round,
  20. StartLineCap = PenLineCap.Round
  21. };
  22. InvalidateVisual();
  23. }
  24. public static readonly DependencyProperty FillProperty = DependencyProperty.Register(
  25. "Fill",
  26. typeof(Brush),
  27. typeof(OutlinedTextBlock),
  28. new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));
  29. public static readonly DependencyProperty StrokeProperty = DependencyProperty.Register(
  30. "Stroke",
  31. typeof(Brush),
  32. typeof(OutlinedTextBlock),
  33. new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));
  34. private static void StrokePropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs dependencyPropertyChangedEventArgs)
  35. {
  36. (dependencyObject as OutlinedTextBlock)?.UpdatePen();
  37. }
  38. public static readonly DependencyProperty StrokeThicknessProperty = DependencyProperty.Register(
  39. "StrokeThickness",
  40. typeof(double),
  41. typeof(OutlinedTextBlock),
  42. new FrameworkPropertyMetadata(1d, FrameworkPropertyMetadataOptions.AffectsRender, StrokePropertyChangedCallback));
  43. public static readonly DependencyProperty FontFamilyProperty = TextElement.FontFamilyProperty.AddOwner(
  44. typeof(OutlinedTextBlock),
  45. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  46. public static readonly DependencyProperty FontSizeProperty = TextElement.FontSizeProperty.AddOwner(
  47. typeof(OutlinedTextBlock),
  48. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  49. public static readonly DependencyProperty FontStretchProperty = TextElement.FontStretchProperty.AddOwner(
  50. typeof(OutlinedTextBlock),
  51. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  52. public static readonly DependencyProperty FontStyleProperty = TextElement.FontStyleProperty.AddOwner(
  53. typeof(OutlinedTextBlock),
  54. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  55. public static readonly DependencyProperty FontWeightProperty = TextElement.FontWeightProperty.AddOwner(
  56. typeof(OutlinedTextBlock),
  57. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  58. public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
  59. "Text",
  60. typeof(string),
  61. typeof(OutlinedTextBlock),
  62. new FrameworkPropertyMetadata(OnFormattedTextInvalidated));
  63. public static readonly DependencyProperty TextAlignmentProperty = DependencyProperty.Register(
  64. "TextAlignment",
  65. typeof(TextAlignment),
  66. typeof(OutlinedTextBlock),
  67. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  68. public static readonly DependencyProperty TextDecorationsProperty = DependencyProperty.Register(
  69. "TextDecorations",
  70. typeof(TextDecorationCollection),
  71. typeof(OutlinedTextBlock),
  72. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  73. public static readonly DependencyProperty TextTrimmingProperty = DependencyProperty.Register(
  74. "TextTrimming",
  75. typeof(TextTrimming),
  76. typeof(OutlinedTextBlock),
  77. new FrameworkPropertyMetadata(OnFormattedTextUpdated));
  78. public static readonly DependencyProperty TextWrappingProperty = DependencyProperty.Register(
  79. "TextWrapping",
  80. typeof(TextWrapping),
  81. typeof(OutlinedTextBlock),
  82. new FrameworkPropertyMetadata(TextWrapping.NoWrap, OnFormattedTextUpdated));
  83. private FormattedText _FormattedText;
  84. private Geometry _TextGeometry;
  85. private Pen _Pen;
  86. public Brush Fill
  87. {
  88. get => (Brush)GetValue(FillProperty);
  89. set => SetValue(FillProperty, value);
  90. }
  91. public FontFamily FontFamily
  92. {
  93. get => (FontFamily)GetValue(FontFamilyProperty);
  94. set => SetValue(FontFamilyProperty, value);
  95. }
  96. [TypeConverter(typeof(FontSizeConverter))]
  97. public double FontSize
  98. {
  99. get => (double)GetValue(FontSizeProperty);
  100. set => SetValue(FontSizeProperty, value);
  101. }
  102. public FontStretch FontStretch
  103. {
  104. get => (FontStretch)GetValue(FontStretchProperty);
  105. set => SetValue(FontStretchProperty, value);
  106. }
  107. public FontStyle FontStyle
  108. {
  109. get => (FontStyle)GetValue(FontStyleProperty);
  110. set => SetValue(FontStyleProperty, value);
  111. }
  112. public FontWeight FontWeight
  113. {
  114. get => (FontWeight)GetValue(FontWeightProperty);
  115. set => SetValue(FontWeightProperty, value);
  116. }
  117. public Brush Stroke
  118. {
  119. get => (Brush)GetValue(StrokeProperty);
  120. set => SetValue(StrokeProperty, value);
  121. }
  122. public double StrokeThickness
  123. {
  124. get => (double)GetValue(StrokeThicknessProperty);
  125. set => SetValue(StrokeThicknessProperty, value);
  126. }
  127. public string Text
  128. {
  129. get => (string)GetValue(TextProperty);
  130. set => SetValue(TextProperty, value);
  131. }
  132. public TextAlignment TextAlignment
  133. {
  134. get => (TextAlignment)GetValue(TextAlignmentProperty);
  135. set => SetValue(TextAlignmentProperty, value);
  136. }
  137. public TextDecorationCollection TextDecorations
  138. {
  139. get => (TextDecorationCollection)GetValue(TextDecorationsProperty);
  140. set => SetValue(TextDecorationsProperty, value);
  141. }
  142. public TextTrimming TextTrimming
  143. {
  144. get => (TextTrimming)GetValue(TextTrimmingProperty);
  145. set => SetValue(TextTrimmingProperty, value);
  146. }
  147. public TextWrapping TextWrapping
  148. {
  149. get => (TextWrapping)GetValue(TextWrappingProperty);
  150. set => SetValue(TextWrappingProperty, value);
  151. }
  152. public OutlinedTextBlock()
  153. {
  154. UpdatePen();
  155. TextDecorations = new TextDecorationCollection();
  156. }
  157. protected override void OnRender(DrawingContext drawingContext)
  158. {
  159. EnsureGeometry();
  160. drawingContext.DrawGeometry(null, _Pen, _TextGeometry);
  161. drawingContext.DrawGeometry(Fill, null, _TextGeometry);
  162. }
  163. protected override Size MeasureOverride(Size availableSize)
  164. {
  165. EnsureFormattedText();
  166. // constrain the formatted text according to the available size
  167. var w = availableSize.Width;
  168. var h = availableSize.Height;
  169. // 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
  170. // the Math.Max call is to ensure we don't hit zero, which will cause MaxTextHeight to throw
  171. _FormattedText.MaxTextWidth = Math.Min(3579139, w);
  172. _FormattedText.MaxTextHeight = Math.Max(0.0001d, h);
  173. // return the desired size
  174. return new Size(Math.Ceiling(_FormattedText.Width), Math.Ceiling(_FormattedText.Height));
  175. }
  176. protected override Size ArrangeOverride(Size finalSize)
  177. {
  178. EnsureFormattedText();
  179. // update the formatted text with the final size
  180. _FormattedText.MaxTextWidth = finalSize.Width;
  181. _FormattedText.MaxTextHeight = Math.Max(0.0001d, finalSize.Height);
  182. // need to re-generate the geometry now that the dimensions have changed
  183. _TextGeometry = null;
  184. return finalSize;
  185. }
  186. private static void OnFormattedTextInvalidated(DependencyObject dependencyObject,
  187. DependencyPropertyChangedEventArgs e)
  188. {
  189. var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
  190. outlinedTextBlock._FormattedText = null;
  191. outlinedTextBlock._TextGeometry = null;
  192. outlinedTextBlock.InvalidateMeasure();
  193. outlinedTextBlock.InvalidateVisual();
  194. }
  195. private static void OnFormattedTextUpdated(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
  196. {
  197. var outlinedTextBlock = (OutlinedTextBlock)dependencyObject;
  198. outlinedTextBlock.UpdateFormattedText();
  199. outlinedTextBlock._TextGeometry = null;
  200. outlinedTextBlock.InvalidateMeasure();
  201. outlinedTextBlock.InvalidateVisual();
  202. }
  203. private void EnsureFormattedText()
  204. {
  205. if (_FormattedText != null)
  206. {
  207. return;
  208. }
  209. #pragma warning disable CS0618 // Type or member is obsolete
  210. _FormattedText = new FormattedText(
  211. Text ?? "",
  212. CultureInfo.CurrentUICulture,
  213. FlowDirection,
  214. new Typeface(FontFamily, FontStyle, FontWeight, FontStretch),
  215. FontSize,
  216. Brushes.Black);
  217. #pragma warning restore CS0618 // Type or member is obsolete
  218. UpdateFormattedText();
  219. }
  220. private void UpdateFormattedText()
  221. {
  222. if (_FormattedText == null)
  223. {
  224. return;
  225. }
  226. _FormattedText.MaxLineCount = TextWrapping == TextWrapping.NoWrap ? 1 : int.MaxValue;
  227. _FormattedText.TextAlignment = TextAlignment;
  228. _FormattedText.Trimming = TextTrimming;
  229. _FormattedText.SetFontSize(FontSize);
  230. _FormattedText.SetFontStyle(FontStyle);
  231. _FormattedText.SetFontWeight(FontWeight);
  232. _FormattedText.SetFontFamily(FontFamily);
  233. _FormattedText.SetFontStretch(FontStretch);
  234. _FormattedText.SetTextDecorations(TextDecorations);
  235. }
  236. private void EnsureGeometry()
  237. {
  238. if (_TextGeometry != null)
  239. {
  240. return;
  241. }
  242. EnsureFormattedText();
  243. _TextGeometry = _FormattedText.BuildGeometry(new Point(0, 0));
  244. }
  245. }