#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.

#231 – You Can Use a Brush for a Control’s Foreground

Though brushes in WPF are most often used to fill in the background for a window or a control,  you can also render the foreground of many controls using a brush.

For example, we can use a brush instead of a solid color for the Foreground property of a Label.  The text of the label will be rendered in a specified font and the body of the letters will be rendered using the specified brush.

		<Label Content="You can render text with a brush.." HorizontalAlignment="Center" Margin="20"
			   FontSize="24" FontWeight="Bold">
			<Label.Foreground>
				<LinearGradientBrush StartPoint="0,0.5" EndPoint="1.0,0.5">
					<GradientStop Color="DarkSlateBlue" Offset="0.0"/>
					<GradientStop Color="SkyBlue" Offset="1.0"/>
				</LinearGradientBrush>
			</Label.Foreground>
		</Label>

#230 – Changing a Radial Gradient as Mouse Moves Over a Control

Here’s an example of one way to change a radial gradient at runtime, based on mouse movements.

The radial gradient is defined in the XAML:

		<Button x:Name="btnActiveGradient" Content="Click Me" Width="120" Height="30" Margin="30"
			MouseMove="btnActiveGradient_MouseMove"
			MouseLeave="btnActiveGradient_MouseLeave"
			Style="{DynamicResource ButtonStyle1}" >
			<Button.Background>
				<RadialGradientBrush x:Name="gradRadial" RadiusX="0.25">
					<GradientStop Color="AliceBlue" Offset="0.0"/>
					<GradientStop Color="LightSteelBlue" Offset="1.0"/>
				</RadialGradientBrush>
			</Button.Background>
		</Button>

We also need to disable the default rendering of the button while the mouse is over it.  We can do this by changing the RenderMouseOver property of the ButtonChrome object–found in the button’s control template–to False.

Finally, we add some code to the button’s MouseMove and MouseLeave events, to change the gradient’s origin and center as we move the mouse.

        private void btnActiveGradient_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
        {
            Point pt = Mouse.GetPosition(btnActiveGradient);
            gradRadial.GradientOrigin = new Point(pt.X / btnActiveGradient.Width, pt.Y / btnActiveGradient.Height);
            gradRadial.Center = gradRadial.GradientOrigin;
        }

        private void btnActiveGradient_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
        {
            gradRadial.GradientOrigin = new Point(0.5, 0.5);   // Default
            gradRadial.Center = gradRadial.GradientOrigin;
        }

#229 – Using a Gradient Brush for a Window’s Border

The Window class has BorderBrush and BorderThickness properties that allow creating a thick border around the window’s client area.  (Just inside the standard window chrome).

You can use any brush you like as a border brush.  In the example below, we use a linear gradient brush that starts in the upper left corner of the window and cycles through the colors of the rainbow until it reaches the lower right corner of the window.  Note that we set the BorderThickness to 10.

<Window
	xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
	xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
	xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
	xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
	mc:Ignorable="d"
	xmlns:local="clr-namespace:WpfApplication1"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    x:Class="WpfApplication1.MainWindow"
	x:Name="Window"
	Title="Rainbow Border"
	BorderThickness="10">

	<Window.BorderBrush>
		<LinearGradientBrush StartPoint="0,0" EndPoint="1,1">
		    <GradientStop Color="Red" Offset="0.0"/>
		    <GradientStop Color="Orange" Offset="0.17"/>
		    <GradientStop Color="Yellow" Offset="0.33"/>
		    <GradientStop Color="Green" Offset="0.5"/>
		    <GradientStop Color="Blue" Offset="0.67"/>
		    <GradientStop Color="Indigo" Offset="0.83"/>
		    <GradientStop Color="Violet" Offset="1.0"/>
		</LinearGradientBrush>
	</Window.BorderBrush>

	<StackPanel VerticalAlignment="Center">
		<Button Content="Click Me" Width="100" Margin="10"/>
		<Label Content="I'm a label, you know" HorizontalAlignment="Center" Margin="10"/>
	</StackPanel>
</Window>

#228 – Starting/Ending a Gradient Fill Outside a Control

If you specify start and end points for a linear gradient that are inside the control (i.e. between 0 and 1), the start/stop colors are extended to the edge of the control based on the value of the SpreadMethod property.

You can also specify StartPoint and EndPoint positions that are outside the control, i.e. by using X/Y values that are less than 0.0 or greater than 1.0.

When you specify start/stop positions outside of a control, the control will be filled with that portion of the larger gradient that corresponds to the control’s position within the gradient.

Here’s an example with a red-blue fill that starts and finishes outside of the control.  Notice that the control is filled with a subset of the full red-blue gradient, so ends up being shades of magenta.

		<Rectangle Width="200" Height="100" Margin="30">
			<Rectangle.Fill>
				<LinearGradientBrush StartPoint="-1,-1" EndPoint="1.5,1.5">
				    <GradientStop Color="Red" Offset="0.0"/>
				    <GradientStop Color="Blue" Offset="1.0"/>
				</LinearGradientBrush>
			</Rectangle.Fill>
		</Rectangle>

#227 – You Can Specify Gradient Fills in Absolute Coordinates

When you specify a gradient fill’s starting position in 2D space, you typically use coordinates that are normalized to the size of the control, ranging from 0.0 to 1.0.

With the MappingMode property, you can instead specify the gradient’s start and stop points in absolute (device-independent) units, rather than as a ratio of the control’s size.

In the example below, we set MappingMode to Absolute, and then specify the gradients start/stop in absolute units.  This has the effect of having the gradient reach its target colors the same number of pixels into each control.

	<Window.Resources>
		<LinearGradientBrush x:Key="HappyFill" MappingMode="Absolute" StartPoint="0,10" EndPoint="50,10">
		    <GradientStop Color="AliceBlue" Offset="0.0"/>
		    <GradientStop Color="Chocolate" Offset="1.0"/>
		</LinearGradientBrush>
	</Window.Resources>

	<StackPanel>
		<Rectangle Height="100" Width="100" Fill="{StaticResource HappyFill}" Margin="30"/>
		<Button Content="!" Width="25" Background="{StaticResource HappyFill}" Height="25"/>
		<Label Content="We few, we happy few, we band of brothers.  --Hal V"
			HorizontalAlignment="Center" Margin="15"
			Background="{StaticResource HappyFill}"/>
	</StackPanel>


Note that MappingMode does not apply to the gradient stop offsets.

#226 – Gradient Fills Adjust to the Size of the Control

Because the start/stop points and gradient stops within a gradient fill are normalized to a 0.0-1.0 range, the same gradient can be used to fill controls of different sizes.  The fill changes color proportionally, relative to the size of the control, rather than at specific pixel positions.

In the example below, we define a linear gradient brush and then apply it to several controls of different sizes.

	<Window.Resources>
		<LinearGradientBrush x:Key="HappyFill" StartPoint="0,0.5" EndPoint="1,0.5">
		    <GradientStop Color="AliceBlue" Offset="0.0"/>
		    <GradientStop Color="Chocolate" Offset="1.0"/>
		</LinearGradientBrush>
	</Window.Resources>

	<StackPanel>
		<Rectangle Height="100" Width="200" Fill="{StaticResource HappyFill}" Margin="30"/>
		<Button Content="!" Width="25" Background="{StaticResource HappyFill}" Height="25"/>
		<Label Content="We few, we happy few, we band of brothers.  --Hal V"
			HorizontalAlignment="Center" Margin="15"
			Background="{StaticResource HappyFill}"/>
	</StackPanel>

Follow

Get every new post delivered to your Inbox.

Join 129 other followers