I Have Some Fun With FormattedText

|

Recently I've been trying the rich text sample code in the WPF documentation, the following custom control is something I come up with when playing with the FormattedText class:

using System;
using System.Xml;
using System.Text;

using System.Threading;
using System.Windows;
using System.Windows.Media;
using System.Windows.Markup;
using System.Windows.Shapes;
using System.Windows.Controls;
using System.Windows.Documents;

using Sheva.Internal;

namespace Sheva.Windows.Controls
{
    public class OutlinedText : FrameworkElement
    {
        private FormattedText formattedText;

        public static DependencyProperty FillProperty;
        public static DependencyProperty StrokeProperty;
        public static DependencyProperty StrokeThicknessProperty;
        public static DependencyProperty FontFamilyProperty;
        public static DependencyProperty FontSizeProperty;
        public static DependencyProperty FontStretchProperty;
        public static DependencyProperty FontStyleProperty;
        public static DependencyProperty FontWeightProperty;
        public static DependencyProperty RenderHighlightProperty;
        public static DependencyProperty TextProperty;
        public static DependencyProperty TextGeometryProperty;
        public static DependencyProperty TextHighlightGeometryProperty;
        private static DependencyPropertyKey TextGeometryPropertyKey;
        public static DependencyPropertyKey TextHighlightGeometryPropertyKey;

        static OutlinedText()
        {
            FillProperty = DependencyProperty.Register(
                "Fill",
                typeof(Brush),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));

            StrokeProperty = DependencyProperty.Register(
                "Stroke",
                typeof(Brush),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(Brushes.Black, FrameworkPropertyMetadataOptions.AffectsRender));

            StrokeThicknessProperty = DependencyProperty.Register(
                "StrokeThickness",
                typeof(Double),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    0.0d,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure));

            FontFamilyProperty = DependencyProperty.Register(
                "FontFamily",
                typeof(FontFamily),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    SystemFonts.MessageFontFamily,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure),
                    new ValidateValueCallback(IsValidFontFamily));

            FontStyleProperty = DependencyProperty.Register(
                "FontStyle",
                typeof(FontStyle),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    SystemFonts.MessageFontStyle,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure));

            FontWeightProperty = DependencyProperty.Register(
                "FontWeight",
                typeof(FontWeight),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    SystemFonts.MessageFontWeight,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure));

            FontStretchProperty = DependencyProperty.Register(
                "FontStretch",
                typeof(FontStretch),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    FontStretches.Normal,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure));

            FontSizeProperty = DependencyProperty.Register(
                "FontSize",
                typeof(Double),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    SystemFonts.MessageFontSize,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure));

            TextProperty = DependencyProperty.Register(
                "Text",
                typeof(String),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                    String.Empty,
                    FrameworkPropertyMetadataOptions.AffectsRender |
                    FrameworkPropertyMetadataOptions.AffectsMeasure));

            RenderHighlightProperty = DependencyProperty.Register(
                "RenderHighlight",
                typeof(Boolean),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(
                BooleanBoxes.FalseBox,
                FrameworkPropertyMetadataOptions.AffectsRender |
                FrameworkPropertyMetadataOptions.AffectsMeasure));

            TextGeometryPropertyKey = DependencyProperty.RegisterReadOnly(
                "TextGeometry",
                typeof(Geometry),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(null));
            TextGeometryProperty = TextGeometryPropertyKey.DependencyProperty;

            TextHighlightGeometryPropertyKey = DependencyProperty.RegisterReadOnly(
                "TextHighlightGeometry",
                typeof(Geometry),
                typeof(OutlinedText),
                new FrameworkPropertyMetadata(null));
            TextHighlightGeometryProperty = TextHighlightGeometryPropertyKey.DependencyProperty;
        }

        public Brush Fill
        {
            get { return (Brush)base.GetValue(FillProperty); }
            set { base.SetValue(FillProperty, value); }
        }

        public Brush Stroke
        {
            get { return (Brush)base.GetValue(StrokeProperty); }
            set { base.SetValue(StrokeProperty, value); }
        }

        public Double StrokeThickness
        {
            get { return (Double)base.GetValue(StrokeThicknessProperty); }
            set { base.SetValue(StrokeThicknessProperty, value); }
        }

        public FontFamily FontFamily
        {
            get { return (FontFamily)base.GetValue(FontFamilyProperty); }
            set { base.SetValue(FontFamilyProperty, value); }
        }

        public Double FontSize
        {
            get { return (Double)base.GetValue(FontSizeProperty); }
            set { base.SetValue(FontSizeProperty, value); }
        }

        public FontStyle FontStyle
        {
            get { return (FontStyle)base.GetValue(FontStyleProperty); }
            set { base.SetValue(FontStyleProperty, value); }
        }

        public FontStretch FontStretch
        {
            get { return (FontStretch)base.GetValue(FontStretchProperty); }
            set { base.SetValue(FontStretchProperty, value); }
        }

        public FontWeight FontWeight
        {
            get { return (FontWeight)base.GetValue(FontWeightProperty); }
            set { base.SetValue(FontWeightProperty, value); }
        }

        public String Text
        {
            get { return (String)base.GetValue(TextProperty); }
            set { base.SetValue(TextProperty, value); }
        }

        public Boolean RenderHighlight
        {
            get { return (Boolean)base.GetValue(RenderHighlightProperty); }
            set { base.SetValue(RenderHighlightProperty, BooleanBoxes.Box(value)); }
        }

        public Geometry TextGeometry
        {
            get { return (Geometry)base.GetValue(TextGeometryProperty); }
        }

        public Geometry TextHighlightGeometry
        {
            get { return (Geometry)base.GetValue(TextHighlightGeometryProperty); }
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            EnsureTextGeometry();
            Size geometrySize = GetNaturalSize();
            Double width = availableSize.Width;
            Double Height = availableSize.Height;
            if (Double.IsInfinity(width) && Double.IsInfinity(Height))
            {
                return geometrySize;
            }

            if (Double.IsInfinity(width))
            {
                width = geometrySize.Width;
            }

            if (Double.IsInfinity(Height))
            {
                Height = geometrySize.Height;
            }

            if (width > geometrySize.Width && Height > geometrySize.Height)
            {
                return geometrySize;
            }

            return new Size(width, Height);
        }

        protected override Size ArrangeOverride(Size finalSize)
        {
            AdjustTextGeometry(finalSize);
            return finalSize;
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            if (RenderHighlight)
            {
                drawingContext.DrawGeometry(Fill, GetPen(), TextHighlightGeometry);
            }

            drawingContext.DrawGeometry(Fill, GetPen(), TextGeometry);
        }

        protected virtual void AdjustTextGeometry(Size arrangedSize)
        {
            Size geometrySize = GetNaturalSize();
            Double aspectRatio = geometrySize.Width / geometrySize.Height;
            Double scaleX = arrangedSize.Width / geometrySize.Width;
            Double scaleY = arrangedSize.Height / geometrySize.Height;
            ScaleTransform scaleTransform = new ScaleTransform(scaleX, scaleY, 0, 0);
            TextGeometry.Transform = scaleTransform;
            if (RenderHighlight)
            {
                TextHighlightGeometry.Transform = scaleTransform;
            }
        }

        protected virtual void EnsureTextGeometry()
        {
            FlowDirection flowDirection = Thread.CurrentThread.CurrentUICulture.TextInfo.IsRightToLeft ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;
            formattedText = new FormattedText(
                Text,
                Thread.CurrentThread.CurrentUICulture,
                flowDirection,
                new Typeface(FontFamily, FontStyle, FontWeight, FontStretch, new FontFamily("Segeo UI")),
                FontSize,
                Brushes.Black);

            base.SetValue(TextGeometryPropertyKey, formattedText.BuildGeometry(new Point(0, 0)));
            if (RenderHighlight)
            {
                base.SetValue(TextHighlightGeometryPropertyKey, formattedText.BuildHighlightGeometry(new Point(0, 0)));
            }
        }

        private static Boolean IsValidFontFamily(Object value)
        {
            FontFamily fontFamily = value as FontFamily;
            return (fontFamily != null);
        }

        private Size GetNaturalSize()
        {
            Geometry definingGeometry = RenderHighlight ? TextHighlightGeometry : TextGeometry;
            Pen pen = this.GetPen();
            Rect rect = definingGeometry.GetRenderBounds(pen);
            return new Size(Math.Max(rect.Right, 0), Math.Max(rect.Bottom, 0));
        }

        private Pen GetPen()
        {
            return new Pen(this.Stroke, this.StrokeThickness);
        }
    }
}

 

And here is a little programme which demonstrates how to use this custom control:

<Window x:Class="OutlinedTextDemo.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   xmlns:cc="clr-namespace:Sheva.Windows.Controls"
  
Title="OutlinedTextDemo"
    >
  <
DockPanel>
    <
CheckBox DockPanel.Dock="Top" Content="Render Highlight" Name="checkBox"/>
    <
cc:OutlinedText
    
Margin="5"
     
Text="OutlinedText"
     
FontFamily="Vineta BT"
     
FontSize="108"
     
FontWeight="Bold"
     
StrokeThickness="4"
      Fill="Gold"
      Stroke="Green"
      RenderHighlight="{Binding Path=IsChecked, ElementName=checkBox}"/>
  </
DockPanel>
</
Window>

  After compiling and running the above code, you should see something like the following:

1 comments:

fresh said...

Nice one!
Can you please throw some light how to make this class act like a textbox where I get a cursor and type in text.