Preface
This article overviews WPF's graphics
capabilities, including two-dimensional and three-dimensional shapes, fonts and
animations. The writer would like to stress that the level of this article is
for beginners. The reason why I am writing this article is because WPF
integrates drawing and animation features that were previously available only in
special libraries (such as Microsoft GDI+ and DirectX). The graphics system in
WPF is designed to your computer graphics hardware to reduce the load on the CPU
and in many cases speed up graphics rendering. So taking WPF for what it is, it
is actually a remarkable technology that, apparently, can read and assess what
your machine's graphics capabilities are via the machine's video interface card.
Moreover, WPF graphics uses a resolution-independent measurement in units to
make applications more uniform and portable across devices. For instance, the
size properties of elements in WPF are measured in resolution-independent
pixels, where one pixel represents 1/96 of an inch--but this depends on the
computer's DPI (dots per inch) setting. The graphics engine determines the
correct pixel count so that all users see elements of the same size on all
devices. To emphasize the importance of graphics sketching, I also introduce the
Microsoft Blend SDK version 4.
Graphic elements are rendered on screen using a vector-based system in which
calculations determine how to size and scale each element in order to prevent an
oversized element against a regular-size foreground. The basic 2-D shapes are
Lines, Rectangles, and Ellipses. WPF, as we all know, has controls that can be
used to create custom shapes or curves. Brushes can be used to fill an element
with solid colors, complex patterns, images, and even videos.
Tell Me What XAML Is
In Windows Presentation Foundation, the screens are built with a dialect of XML
called Extensible Markup Language (XAML). XML is data centric and is viewed as a
technology in its own right. Data on certain web pages or a web service request
(response message) is in the form of an XML document. Any data (any alphanumeric
character) is tagged, as the rules for XML are very strict. That is, controls,
images, or any item that are marked up on a page via HTML comprises the
presentation content. Alphabetic letters or numbers are separately tagged via
XML. The idea is to separate the presentation content on a web page from the
data. This outputs a more dazzling-looking page. XAML is more declarative than
imperative. By declarative, I mean that a control is specified as an XML element
with attributes and sub-elements to describe what the control will look like.
You do not have to be concerned with how this done because when you tell WPF
what you want, it will figure out how to build the controls (instantiating
objects and assigning properties) under the hood. This parallels dragging and
dropping a control onto a Windows Form. The partial classes enable code to be
generated, where the developer is only responsible for writing the event handler
code. In addition, the designer is split between a graphical design surface on
top and XAML on the bottom. The designer works the same as Windows Forms in that
you can drag and drop controls onto it and edit them in the Properties window.
But XAML is new, and we therefore must take a close look at XAML and define some
terms that are used in WPF.
In Windows Forms, everything translates directly into code. However, WPF
translates its user interface to XAML, which gets compiled into code later. This
is a key distinction: XAML compiles into a binary form called BAML. At this
point, however, it is not necessary to go into a description of BAML Just note
that when working in the UI designer for a WPF project, VS2008 produces XAML,
instead of code for all UI elements.
<Window x:Class="MainWindow1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="300">
<Grid>
</Grid>
</Window>
The XAML defines a window with a title,
dimensions, and an empty grid. A later section on layout explains the grid.
Because it's XML, you also have namespaces: xmlns and xmlns:x. Windows are often
associated with a code-behind file, which is C#. But where does the C# code come
into play? WPF uses events, just like Windows Forms, so you need a place for the
handlers to go, which is the code-behind.
using
System;
using
System.Collections.Generic;
using
System.Linq;
using
System.Text;
using
System.Windows;
using
System.Windows.Controls;
using
System.Windows.Data;
using
System.Windows.Documents;
using
System.Windows.Input;
using
System.Windows.Media;
using
System.Windows.Media.Imaging;
using
System.Windows.Navigation;
using
System.Windows.Shapes;
public
partial
class
Window1 : Window
{
public
Window1()
{
InitializeComponent();
}
}
Notice that the Window1 class in the
code-behind is partial and that its constructor calls InitializeComponent. In
Windows Forms, the InititalizeComponent was in a separate file that you could
get to. However, WPF generates InitializeComponent from the XAML. You see, WPF
will ensure that the XAML creates a partial class named Window1 with an
InitializeComponent method, and then C# will combine and compile the partials.
If you get an error message that underlines the InitializeMethod, then drag your
mouse over that underline noting the error. A box should appear that when
clicked, gives you the option to generate a "stub method" to correct the error.
Controls in XAML
Many people will use the drag-and-drop capabilities of the WPF designer to build
their UIs, but others will want to work directly in XAML. To try out the
designer, drag and drop a Label control onto the window in the designer. If you
have the split window open, you'll see the XAML for the Label come into view.
You can see the Label control in the XAML code and how the text inside of the
Label tag has been changed. There are also attributes on the Label that match
properties that you'll see in the Properties window. The Name attribute is the
variable name you use in code to access this label if you need to, and
VerticalAlignment describes how this control will align within its parent
element, which means that this Label will align inside the top border of the
grid. The Margin in a later comes later when talking about layout (it means that
the top, left, right, and bottom borders are the specified distance from the
containing control, which is the Grid). Notice that the Content property matches
the Label text, but the XAML doesn't show a Content property. The XAML we used
was a shortcut for the following:
<Label
Height="28"
Margin="80,94,78,0"
Name="label1"
VerticalAlignment="Top">
<Label.Content>
What
the Heck is XAML?
</Label.Content>
</Label>
OUTPUT:
If you want to use text only, which is the most typical content, you don't need
to wrap the text in the property-specific content element. However, one of the
cool things about XAML controls is their composability. Whether it is a label,
button, text box, or any other type of control, you can add any type of content
to them that you want. You can put only one item inside of the Content element,
but that one element can be a layout control that can hold many controls.
Managing Layout
One of the subjects in the previous section was how WPF determined where the
Label control should appear on the page, its size, and its relationship to other
controls. While Windows Forms applications have absolute positioning and layout
support via anchoring and docking, WPF is more sophisticated and has more
options. For WPF, you need to know about control alignment and sizing, borders
and margins, and layout controls for defining the relationship of containing
controls and where they appear. With WPF controls, you can control their
alignment and size. You can also manage their content, borders, and margins,
which are often referred to as the box model. The default behavior of a WPF
control is to stretch to fill its contents. If you are using a ListBox or Grid,
filling the entire area might be the desired appearance, but you might not want
this to happen with Button or Label controls. The following Label control
demonstrates stretching behavior:
<Label
Name="label1"
Background="AliceBlue">What
the Heck is XAML?</Label>
This Label doesn't have any attributes that specify its size or position, so the
default is to stretch. In any event, a common way to lay out controls is for
them to be in-line horizontally or vertically. You can use the StackPanel layout
control to accomplish this. Here's an example:
<StackPanel
Orientation="Horizontal">
<Label>One</Label>
<Label>After</Label>
<Label>the
Other</Label>
</StackPanel>
You can add numerous items to the StackPanel, as you saw previously with the
Label controls, and it will line them up. the XAML code below shows what this
looks like. The example sets the Orientation property to Horizontal, but
Orientation will default to Vertical if you omit it. The UniformGrid control
divides the size of each cell evenly for all of its controls. The following
example shows how this works:
<UniformGrid
Height="100"
Name="uniformGrid1"
Width="200">
<Label>O</Label>
<Label>X</Label>
<Label>O</Label>
<Label>X</Label>
<Label>O</Label>
<Label>X</Label>
<Label>O</Label>
<Label>X</Label>
<Label>O</Label>
</UniformGrid>
Because there are nine items, the grid will
look like a tic-tac-toe board. If you need more explicit control of grid layout,
you should use the Grid layout control. So another layout arrangement you'll
need is to place items in tabular format with rows and columns, which is the
role of the Grid layout. That Grid was empty, but you'll often want to create
rows and columns. The code below shows you how to use a Grid.
<Window x:Class="Main.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window1" Height="300" Width="400">
<Grid
ShowGridLines="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="125"/>
<ColumnDefinition Width="2*"/>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition
Height="25"
/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<Label Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" HorizontalAlignment="Center">
Grid Facts</Label>
<Label Grid.Row="1" Grid.Column="0">Width="125"</Label>
<Label
Grid.Row="1"
Grid.Column="1">Absolute
Column Width</Label>
<Label Grid.Row="1" Grid.Column="2">Grid.Column</Label>
<Label
Grid.Row="2"
Grid.Column="0">Width="2*"</Label>
<Label
Grid.Row="2"
Grid.Column="1">Proportional
Spacing</Label>
<Label
Grid.Row="2"
Grid.Column="2">Grid.Column</Label>
<Label
Grid.Row="3"
Grid.Column="0">Width="Auto"></Label>
<Label
Grid.Row="3"
Grid.Column="1">Fill
Remaining Space</Label>
<Label
Grid.Row="3"
Grid.Column="2">Grid.Column</Label>
<Label
Grid.Row="4"
Grid.Column="0">Height="25"</Label>
<Label
Grid.Row="4" Grid.Column="1">Absolute Row Height</Label>
<Label Grid.Row="4" Grid.Column="2">Grid.Row</Label>
<Label
Grid.Row="5"
Grid.Column="0">Grid.ColumnSpan="3"</Label>
<Label Grid.Row="5" Grid.Column="1">Cover 3 Columns</Label>
<Label
Grid.Row="5"
Grid.Column="2">Control
Attribute</Label>
The first two items to notice in this code are
Grid.ColumnDefinitions and Grid.RowDefinitions, which define the columns and
rows, respectively. Combined with the Label controls, the code below shows grid
lines so that you can see the effects of sizing on rows and columns. Here's the
line that makes this happen:
<Grid
ShowGridLines="True">
You can use three settings to control the size of columns: absolute,
proportional, and auto fill. Here's how they're used:
<Grid.ColumnDefinitions>
<ColumnDefinition Width="125" />
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
Absolute sets an explicit width, and auto fills
in the rest of the width of the Grid layout's containing control. Proportional
is a little different because it is proportional to the size of the other
controls. The second column in the preceding example is two times the current
average size of a column. Therefore, if the width of the Grid's container is 400
and there are three columns, the average size of a column is approximately 133.
Because the proportional coefficient is 2, the width of the second column is 2 x
133 = 266. You can use absolute, proportional, and auto-fill spacing on the
Height property of RowDefinition elements, too. After you've defined columns and
rows, you must specify which column and row that controls go into. This example
uses all Label controls, but you can put any WPF controls into the grid. Here's
the control for the first column of the second row:
<Label
Grid.Row="1"
Grid.Column="0">Width="125"</Label>
See how the attributes Grid.Row and Grid.Column
reference the row and column to put this control into. You can look at each of
these controls and see how they appear. The first row has a single Label that
spans all columns of the Grid, shown here:
<Label
Grid.Row="0"
Grid.Column="0"
Grid.ColumnSpan="3"
HorizontalAlignment="Center">
Grid
Stuff
</Label>
Notice the Grid.ColumnSpan, which means that
this control covers all three columns in the grid. If you need a control to
cover multiple rows, you can use Grid.RowSpan the same way. You can also set
horizontal alignment of content with HorizontalAlignment or set vertical
alignment with VerticalAlignment. With IntelliSense and auto-formatting in
VS2008, it's a lot easier. One useful feature is whenever a block of XAML gets
out of whack, you can highlight the block and press Ctrl+K+F to auto-format. If
you don't like the default auto-format, you can change it yourself. Just select
Tools, Options, Text Editor, and then select the XAML branch. You'll notice a
few sub-branches that give you many options for how to format XAML. If you've
been using Windows Forms and have grown accustomed to docking layout, WPF keeps
you in your comfort zone with the DockPanel. You can use the DockPanel to
position controls onto the top, left, bottom, right, and center of the client
area. Here's an example:
<DockPanel>
<Label DockPanel.Dock="Top"
Background="CornflowerBlue">Top</Label>
<Label DockPanel.Dock="Bottom"
Background="Fuchsia">Bottom</Label>
<Label DockPanel.Dock="Left">Left</Label>
<Label DockPanel.Dock="Right">Right</Label>
<Label Background="Chartreuse">Center</Label>
</DockPanel>
Each control, except Center, in the DockPanel has its DockPanel.Dock set to the side of the client area it occupies. Controls, such as Center, without DockPanel.Dock will fill in the remaining space. The image figure below shows what this looks like. Background colors show what space is occupied by each control. Not too good but not bad. Top and Bottom span the width of the client area because they are the first controls entered. Left and Right occupy remaining space. If Right had been placed in the listing before Bottom, it would have extended downward to the bottom of the client area, and Bottom would have stopped at Right's left border. The concept of managing layout is sort of similar to web page design, in the HTML (markup) places the presentation content within the borders of the web page that loads within the frame of the web browser. In WPF, we are merely managing the layout that will determine where either data or other controls can be placed, etc.
So Where Are We Now? Let's Start with Fonts
This section of code introduces how to control fonts by modifying the font properties of a TextBlock control in the XAML code. The XAML code shows how to use TextBlocks and how to change the properties to control the appearance of the displayed text. The manipulations shown at this point can also be applied to other text-based controls such as a TextBox.
<Window x:Class="UsingFonts.UsingFontsWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="UsingFonts" Height="120" Width="400">
<StackPanel>
<TextBlock FontFamily="Arial" FontSize="12" FontWeight="Bold">Arial 12 point bold.</TextBlock>
<TextBlock FontFamily="Times New Roman">Times New Roman plain, default size.</TextBlock>
<TextBlock FontFamily="Courier New" FontSize="16" FontStyle="Italic" FontWeight="Bold">Courier New 16 point bold and italic.</TextBlock> <TextBlock> <TextBlock.TextDecorations>
<TextDecoration Location="Overline" /> <TextDecoration Location="Baseline" />
</TextBlock.TextDecorations>Default font with overline and baseline.</TextBlock>
<TextBlock>
<TextBlock.TextDecorations>
<TextDecoration Location="Strikethrough" />
<TextDecoration Location="Underline" />
</TextBlock.TextDecorations>Default font with strikethrough and underline.</TextBlock>
</StackPanel>
</Window>
The code behind is the same as the above: there are no event handlers, or more importantly, no "routed events".
With this output, we can see why it works, but we
should know how it works. The text that you want to display in the TextBlock is
placed between the TextBlock tags. The FontFamily property defines the font of
the displayed text. This property can be set to any font. The lines define (that
separate the TextBlock fonts) to be Arial, Times New Roman, and Courier New,
respectively. The FontSize property defines the text size measured in points.
When no FontSize is specified, the property is set to the system's default
value. TextBlocks have various properties to further modify the font. The FontWeight property can be set to bold to make the font thicker. This property
can be set either to a numeric value (1-999) or to a predefined descriptive
value--such as Light or UltraBold--to define the thickness of the text. You can
use the FontStyle property to make the text Italic or Oblique (which is a more
emphasized italic. Finally, you can also define TextDecorations for a TextBlock to draw a horizontal line through the text. Overline and Baseline, shown in the
fourth TextBlock create lines above and below the text, respectively.
With this output, we can see why it works, but we should know how it works. The
text that you want to display in the TextBlock is placed between the TextBlock tags. The FontFamily property defines the font of the displayed text. This
property can be set to any font. The lines define (that separate the TextBlock fonts) to be Arial, Times New Roman, and Courier New, respectively. The FontSize property defines the text size measured in points. When no FontSize is
specified, the property is set to the system's default value. TextBlocks have
various properties to further modify the font. The FontWeight property can be
set to bold to make the font thicker. This property can be set either to a
numeric value (1-999) or to a predefined descriptive value--such as Light or UltraBold--to define the thickness of the text. You can use the FontStyle property to make the text Italic or Oblique (which is a more emphasized italic.
Finally, you can also define TextDecorations for a TextBlock to draw a
horizontal line through the text. Overline and Baseline, shown in the fourth TextBlock create lines above and below the text, respectively.
With this output, we can see why it works, but we should know how it works. The
text that you want to display in the TextBlock is placed between the TextBlock tags. The FontFamily property defines the font of the displayed text. This
property can be set to any font. The lines define (that separate the TextBlock fonts) to be Arial, Times New Roman, and Courier New, respectively. The FontSize property defines the text size measured in points. When no FontSize is
specified, the property is set to the system's default value. TextBlocks have
various properties to further modify the font. The FontWeight property can be
set to bold to make the font thicker. This property can be set either to a
numeric value (1-999) or to a predefined descriptive value--such as Light or UltraBold--to define the thickness of the text. You can use the FontStyle property to make the text Italic or Oblique (which is a more emphasized italic.
Finally, you can also define TextDecorations for a TextBlock to draw a
horizontal line through the text. Overline and Baseline, shown in the fourth TextBlock create lines above and below the text, respectively.
WPF has several built-in shapes. The example XAML code will demonstrate how to display Lines, Rectangles, and Ellipses. Examine the XAML code:
<Window
x:Class="BasicShapes.BasicShapesWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="BasicShapes"
Height="200"
Width="500">
<Canvas>
<Rectangle
Canvas.Left="90"
Canvas.Top="30"
Width="150"
Height="90"
Fill="Blue"/>
<Line
X1="90"
Y1="30"
X2="110"
Y2="40"
Stroke="Black"
/>
<Line
X1="90"
Y1="120"
X2="110"
Y2="130"
Stroke="Black"
/>
<Line
X1="240"
Y1="30"
X2="260"
Y2="40"
Stroke="Black"
/>
<Line
X1="240"
Y1="120"
X2="260"
Y2="130"
Stroke="Black"
/>
<Rectangle
Canvas.Left="110"
Canvas.Top="40"
Width="150"
Height="90"
Stroke="Black"
/>
<Ellipse
Canvas.Left="280"
Canvas.Top="75"
Width="100"
Height="50"
Fill="Red"
/>
<Line
X1="380"
Y1="55"
X2="380"
Y2="100"
Stroke="Black"
/>
<Line
X1="280"
Y1="55"
X2="280"
Y2="100"
Stroke="Black"
/>
<Ellipse
Canvas.Left="280"
Canvas.Top="30"
Width="100"
Height="50"
Stroke="Black"
/>
</Canvas>
</Window>
For the sake being complete here is the standard code-behind file that is placed here (not to fill space up but to exemplify the class construction of BasicShapes):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace BasicShapes
{
public partial class BasicShapesWindow : Window
{
public BasicShapesWindow()
{
InitializeComponent();
}
}
}
This XAML code outputs some basic shapes:
Notice that we used the Rectangle element to create a filled rectangle in the window. Also notice that layout control is a Canvas allowing us to use coordinates to position the shapes. To specify the upper-left corner of the Rectangle, we set the attached properties Canvas.Left and Canvas.Top to 90 and 30, respectively. We then set the Width and Height properties to 150 and 90, respectively, to specify the size. To define the Rectangle's color, we use the Fill property.
A Vector Based Drawing via WPF
A transform can be applied to any element to reposition or reorient the graphic. There are four types of transforms: TranslateTransform, RotateTransform, SkewTransform, and ScaleTransform. A TranslateTransform moves an object to another location. A RotateTransform rotates the object around a point and by a specified RotationAngle. A SkewTransform shears (or skews) the object. A ScaleTransform scales the object's x and y coordinate points by different specified amounts. A star shape is a polygon. If we want to draw a start, we use the Polygon element and use RotateTransforms to create a circle of randomly colored stars. Notice that we use the word random. This is the class used to generate random numbers, and therefore uses the same methods. Now the points property of the Polygon in this example is defined in a new syntax. Examine the XAML:
<Window
x:Class="DrawStars.DrawStarsWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DrawStars"
Height="330"
Width="330"
Name="DrawStars">
<Canvas
Name="mainCanvas">
<Polygon
Name="star"
Fill="Green"
Points="205,150
217,186 259,186
223,204 233,246 205,222 177,246
187,204 151,186 193,186"
/>
</Canvas>
</Window>
And here is the code-behind file that uses the Random class of the System
namespace to generate the random number of coordinates. This, in combination
with rotating the stars, creates an pretty nice on image on a user interface:
using
System;
using
System.Windows;
using
System.Windows.Media;
using
System.Windows.Shapes;
namespace
DrawStars
{
public
partial
class
DrawStarsWindow : Window
{
public
DrawStarsWindow()
{
InitializeComponent();
Random random =
new
Random();
// now let's
create 18 more random stars
for
(
int
count =
0;
count <
18;
count++ )
{
Polygon newStar =
new
Polygon();
newStar.Points = star.Points;
byte[]
colorValues =
new
byte[
4
];
random.NextBytes( colorValues );
newStar.Fill =
new
SolidColorBrush( Color.FromArgb(colorValues[
0
], colorValues[
1
], colorValues[
2
],colorValues[
3
] ) );
RotateTransform rotate =new
RotateTransform( count *
20,
150,
150
);
newStar.RenderTransform = rotate;
mainCanvas.Children.Add( newStar );
}
}
}
}
Here is the output:
Admittedly, this was a very basic look at the WPF engine and the XAML markup. This article is strictly meant for the beginner, who may not have grasped the basics of XAML.