#242 – Drawing Text Using DrawGlyphRun

If you want an even lower-level mechanism for drawing text than provided by DrawingContext.DrawText, you can use DrawingContext.DrawGlyphRun.

DrawGlyphRun lets you draw text based on glyphs, which are individual characters within a font that typically represent a single character in a language.

Below is an example of rendering the word “Wow” using DrawGlyphRun, in a custom DrawingVisual.

    class MyDrawingVisual : DrawingVisual
    {
        public MyDrawingVisual()
        {
            using (DrawingContext dc = RenderOpen())
            {
                GlyphRun gr = new GlyphRun(
                    new GlyphTypeface(new Uri(@"C:\Windows\Fonts\BKANT.TTF")),
                    0,       // Bi-directional nesting level
                    false,   // isSideways
                    96,      // pt size
                    new ushort[] { 58, 82, 90 },   // glyphIndices
                    new Point(100.0, 100.0),           // baselineOrigin
                    new double[] { 80.0, 45.0, 0.0 },  // advanceWidths
                    null,    // glyphOffsets
                    null,    // characters
                    null,    // deviceFontName
                    null,    // clusterMap
                    null,    // caretStops
                    null);   // xmlLanguage

                // Draw the text at a location
                dc.DrawGlyphRun(Brushes.Black, gr);
            }
        }
    }

The glyphIndices parameter specifies a list of indexes into the font for the glyphs that are to be rendered.

Advertisement

#241 – Rendering Text Using DrawText

You can render text into a visual using the DrawText method of a DrawingContextDrawText renders text that is represented by an instance of FormattedText into the drawing context at a specified location.

Below is an example of drawing some text into a custom DrawingVisual.

    class MyDrawingVisual : DrawingVisual
    {
        public MyDrawingVisual()
        {
            using (DrawingContext dc = RenderOpen())
            {
                // Create formatted text--in a particular font at a particular size
                FormattedText ft = new FormattedText(
                    "When things are at their worst I find something always happens.",
                    CultureInfo.CurrentCulture,
                    FlowDirection.LeftToRight,
                    new Typeface(new FontFamily("Century"), FontStyles.Normal, FontWeights.Bold, FontStretches.Normal),
                    24,    // 36 pt type
                    Brushes.Black);

                // Draw the text at a location
                dc.DrawText(ft, new Point(10.0, 10.0));
            }
        }
    }

Once we host this DrawingVisual in an UIElement, we can see the text in a window:

#240 – Shape vs. DrawingVisual

We’ve seen two ways to render custom 2D geometries–by inheriting from DrawingVisual and hosting in an UIElement or by inheriting from Shape and instancing your object directly in XAML.

You might wonder which of these methods to use for drawing custom 2D objects.

Shape is at a higher level of abstraction than DrawingVisualShape provides the following functionality, beyond what you get with DrawingVisual:

  • Derives from FrameworkElement, so you can include your subclass directly into a logical tree as a child of a Panel
  • Takes care of things like the Pen used to render the geometry (Stroke and StrokeThickness) and the Brush used to fill the interior of the geometry

Below is an example of including several instances of a custom Shape and specifying different stroke/fill properties for each instance.

	<StackPanel Orientation="Horizontal">
		<local:MyWeirdShape Stroke="Black" StrokeThickness="2" Fill="Orange"/>
		<local:MyWeirdShape Stroke="Red" StrokeThickness="1" Fill="DimGray"/>
		<local:MyWeirdShape Stroke="Blue" StrokeThickness="10" Fill="White"/>
	</StackPanel>

#239 – Creating a Custom Shape by Overriding the Shape Class

If you need a specialized shape that you can’t use any of the Shape subclasses to draw, you can create your own custom class that inherits from Shape.

You define the shape to be drawn by overriding the Shape.DefiningGeometry property.  In the get accessor, you create and return an instance of a Geometry.  In the example below, we create a new Geometry and then use the StreamGeometryContext to draw the geometry.

    public class MyWeirdShape : Shape
    {
        protected override Geometry DefiningGeometry
        {
            get { return GenerateMyWeirdGeometry(); }
        }

        private Geometry GenerateMyWeirdGeometry()
        {
            StreamGeometry geom = new StreamGeometry();
            using (StreamGeometryContext gc = geom.Open())
            {
                // isFilled = false, isClosed = true
                gc.BeginFigure(new Point(50.0, 50.0), false, true);
                gc.ArcTo(new Point(75.0, 75.0), new Size(10.0, 20.0), 0.0, false, SweepDirection.Clockwise, true, true);
                gc.ArcTo(new Point(100.0, 100.0), new Size(10.0, 20.0), 0.0, false, SweepDirection.Clockwise, true, true);
            }

            return geom;
        }
    }

Using the new object in XAML:

	<StackPanel>
		<local:MyWeirdShape Height="150" Width="150" Stroke="Black" StrokeThickness="2"/>
	</StackPanel>

#237 – Drawing Shapes with the Shape Subclasses

You can include various geometric shapes directly in a WPF GUI using subclasses of the Shape class.  Since Shape derives from FrameworkElement, you can insert instances of the Shape subclasses directly into a Panel container.

The various shapes that you can draw are:

  • Ellipse – Draw an ellipse
  • Line – Draw a line segment
  • Path – Draw an arbitrary geometry consisting of a series of line segments
  • Polygon – Draw a closed polygon
  • Polyline – Draw a polyline–a series of connected line segments
  • Rectangle – Draw a rectangle

Here’s a simple example in XAML that demonstrates the various shapes.

		<Ellipse Height="100" Width="180" Fill="Blue" Stroke="Black" StrokeThickness="2"/>
		<Line X1="0" X2="180" Y1="20" Y2="80" Stroke="Brown" StrokeThickness="2"/>
		<Path Data="M 50,10 L 100,80 L 30,90 L 60,70 Z M 40,20 L 20,40 L 30,60" Stroke="DarkGreen" StrokeThickness="2"/>
		<Polygon Points="20,20 60,60 60,100 140,80 120,40" Stroke="DarkViolet" StrokeThickness="2"/>
		<Polyline Points="20,20 60,60 60,100 140,80 120,40" Stroke="DarkViolet" StrokeThickness="2"/>
		<Rectangle Height="100" Width="180" Stroke="Crimson" StrokeThickness="2"/>

Here’s how these shapes look when rendered:

#236 – Drawing an Arbitrary Geometry into a DrawingVisual

You can render content in a DrawingVisual object by using a DrawingContext to draw objects that you want rendered.  Use the DrawingContext‘s DrawGeometry method to draw an arbitrary geometry.

Below is an example of using DrawGeometry, where we render our object in the DrawingVisual constructor.

        public MyDrawingVisual()
        {
            // First define the geometric shape
            StreamGeometry geom = new StreamGeometry();
            using (StreamGeometryContext gc = geom.Open())
            {
                // Start new object, filled=true, closed=true
                gc.BeginFigure(new Point(150.0, 150.0), true, true);

                // isStroked=true, isSmoothJoin=true
                gc.LineTo(new Point(210.0, 105.0), true, true);
                gc.LineTo(new Point(270.0, 150.0), true, true);
                gc.LineTo(new Point(270.0, 45.0), true, true);
                gc.LineTo(new Point(210.0, 90.0), true, true);
                gc.LineTo(new Point(150.0, 45.0), true, true);
            }

            using (DrawingContext dc = RenderOpen())
            {
                // Draw our geometry
                dc.DrawGeometry(Brushes.DarkRed, new Pen(Brushes.Blue, 2), geom);
            }
        }

We create a new StreamGeometry object, which is what we’ll render into the DrawingContext using the DrawGeometry method.  To create the actual object, we use the StreamGeometryContext of the StreamGeometry and call methods like BeginFigure and LineTo.

#235 – Types of Visual Content that You Can Create Using a DrawingContext

You can render content in a DrawingVisual object by calling its RenderOpen method, which returns a DrawingContext, and then drawing objects by invoking methods on the DrawingContext.

Here are the main DrawingContext methods that you can use to create content in the DrawingVisual:

  • DrawEllipse – Draw an ellipse, specifying the center and radius
  • DrawGeometry – Draw an arbitrary geometry, passing in an instance of Geometry
  • DrawGlyphRun – Render text, passing in an instance of GlyphRun
  • DrawImage – Render an image
  • DrawLine – Draw a line, specifying the endpoints
  • DrawRectangle – Draw a rectangle, specifying the corners
  • DrawRoundedRectangle – Draw a rectangle with rounded corners
  • DrawText – Render text, passing in an instance of FormattedText
  • DrawVideo – Render a video, specifying the media source and corner points of rectangle in which to play the video

 

#234 – Getting a DrawingVisual Object Rendered in a Window

A class deriving from DrawingVisual that draws one or more 2D objects needs to be hosted in another object in order to render the DrawingVisual object in a window or a page.

You can host the lower level control in either an UIElement or a FrameworkElementUIElement has basic support for layout and is the minimum functionality required in order to host a control within a Window (or a container control).

Here’s an example of overriding FrameworkElement to host our new control.

    public class EllAndRectHost : FrameworkElement
    {
        private EllipseAndRectangle _ellAndRect = new EllipseAndRectangle();

        // EllipseAndRectangle instance is our only visual child
        protected override Visual GetVisualChild(int index)
        {
            return _ellAndRect;
        }

        protected override int VisualChildrenCount
        {
            get
            {
                return 1;
            }
        }
    }

We create a FrameworkElement with one visual child, overriding GetVisualChild and VisualChildrenCount.

Now we can use this new element directly in XAML:

		<local:EllAndRectHost Margin="30"/>

#233 – An Example of Deriving from DrawingVisual Class

You can define a new class that inherits from the DrawingVisual class when you need a low-level control to draw one or more 2D objects.

Below is an example showing a simple implementation of a class derived from DrawingVisual that draws a couple of objects.

    class EllipseAndRectangle : DrawingVisual
    {
        public EllipseAndRectangle()
        {
            using (DrawingContext dc = RenderOpen())
            {
                // Black ellipse with blue border
                dc.DrawEllipse(Brushes.Black,
                    new Pen(Brushes.Blue, 3),        // Border
                    new Point(120, 120), 20, 40);    // Center & radius

                // Red rectangle with green border
                dc.DrawRectangle(Brushes.Red,
                    new Pen(Brushes.Green, 4),       // Border
                    new Rect(new Point(10, 10), new Point(80, 80)));    // Corners
            }
        }
    }

The RenderOpen method allows us to render content into the DrawingVisual object.  RenderOpen returns a DrawingContext, which allows us to draw various kinds of 2D objects.

The DrawingContext will actually cache all commands for drawing the objects that we tell it to draw.  This means that we only have to issue our drawing commands once–in the class’ constructor.

#232 – The DrawingVisual Class

DrawingVisual is a lightweight class that can be used a base class for new controls that need to render (draw) a collection of 2D primitives on the screen.  It inherits from the Visual class, which gives it support for things like hit testing and coordinate transformations.  It also inherits from ContainerVisual, which manages a collection of separate Visual objects.

You draw in a DrawingVisual object by calling its RenderOpen method to get a DrawingContext and then by calling methods on the DrawingContext to draw the 2D content.

The Drawing property of the DrawingVisual represents the collection of 2D objects that have been drawn, each of which is a Drawing object.

In order to actually display the content of a DrawingVisual object in a window or page, it must be hosted inside an UIElement or FrameworkElement.