Windows Presentation Foundation (WPF) is a library to create the UI for smart client applications. This gives you broad information on the important concepts of WPF. It covers a large number of different controls and their categories, including how to arrange the controls with panels, customize the appearance using styles, resources, and templates, add some dynamic behavior with triggers and animations, and create 3-D with WPF. One of the main advantages of WPF is that work can be easily separated between designers and developers. The outcome from the designer’s work can directly be used by the developer. To make this possible, you need to understand eXtensible Application Markup Language, or XAML. Readers unfamiliar with XAML can read Chapter 29, “Core XAML,” for information about its syntax. The first topic of this chapter provides an overview of the class hierarchy and categories of classes that are used with WPF, including additional information to understand the principles of XAML. WPF consists of several assemblies containing thousands of classes. To help you navigate within this vast number of classes and find what you need, this section explains the class hierarchy and namespaces in WPF.
Class
Hierarchy
WPF
consists of thousands of classes within a deep hierarchy. For an overview of
the relationships between the classes, see Figure. Some classes and their
functionality are described in the following table.
Core WPF
SHAPES
Shapes
are the core elements of WPF. With shapes you can draw two-dimensional graphics
using rectangles, lines, ellipses, paths, polygons, and polylines that are
represented by classes derived from the abstract base class Shape. Shapes are
defined in the namespace System.Windows.Shapes.
The
following XAML example (code file ShapesDemo/MainWindow.xaml) draws a yellow
face consisting of an ellipse for the face, two ellipses for the eyes, two
ellipses for the pupils in the eyes, and a path for the mouth:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="300" Width="300">
Stroke="Blue"
StrokeThickness="4" Fill="Yellow" />
StrokeThickness="3"
Fill="White" />
1054 ❘ CHAPTER 35 CORE WPF
StrokeThickness="3"
Fill="White" />
Data="M 40,74
Q 57,95 80,74 " />
TRANSFORMATION
Because
WPF is vector-based, you can resize every element. In the next example, the
vector-based graphics are now scaled, rotated, and skewed. Hit testing (for
example, with mouse moves and mouse clicks) still works but without the need
for manual position calculation.
Adding
the ScaleTransform element to the LayoutTransform property of the Canvas
element, as shown here (code fi le TransformationDemo/MainWindow.xaml), resizes
the content of the complete canvas by 1.5 in the x and y axes:
Rotation can be
done in a similar way as scaling. Using the RotateTransform element you can
define the
Angle for the rotation:
For skewing, you
can use the SkewTransform element. With skewing you can assign angles for the x
and y
axes:
To rotate and skew
together, it is possible to defi ne a TransformGroup that contains both
RotateTransform and
SkewTransform. You can also defi ne a MatrixTransform whereby the Matrix
element specifi es
the properties M11 and M22 for stretch and M12 and M21 for skew:
Figure-
shows the result of all these transformations. The figures are placed inside a
StackPanel.
Starting
from the left, the first image is resized, the second image is rotated, the
third image is skewed, and the fourth image uses a matrix for its
transformation. To highlight the differences between these four images, the
Background property of the Canvas elements is set to different colors.
BRUSHES
This section
demonstrates how to use the brushes that WPF offers for drawing backgrounds
and foregrounds.
The examples in this section reference Figure, which shows the effects of using
various brushes within a Path and the Background of Button elements.
SolidColorBrush
The
first button in Figure uses the SolidColorBrush, which, as the name suggests, uses
a solid color. The complete area is drawn with the same color. You can define a
solid color just by setting the Background attribute to a string that defines a
solid color. The string is converted to a SolidColorBrush element with the help
of the BrushValueSerializer:
Of course, you will
get the same effect by setting the Background child element and adding a
SolidColorBrush
element as its content (code fi le BrushesDemo/MainWindow.xaml). The first
button in the application is using PapayaWhip as the solid background color:
LinearGradientBrush
For
a smooth color change, you can use the LinearGradientBrush, as the second
button shows. This
brush
defi nes the StartPoint and EndPoint properties. With this, you can assign
two-dimensional
coordinates
for the linear gradient. The default gradient is diagonal linear from 0,0 to
1,1. By defi ning different values, the gradient can take different directions.
For example, with a StartPoint of 0,0 and an EndPoint of 0,1, you get a
vertical gradient. The StartPoint and EndPoint value of 1,0 creates a horizontal
gradient. With the content of this brush, you can define the color values at
the specified offsets with the GradientStop element. Between the stops, the
colors are smoothed
(code file BrushesDemo/MainWindow.xaml):
RadialGradientBrush
With the
RadialGradientBrush you can smooth the color in a radial way. In Figure, the
third element is a Path that uses RadialGradientBrush. This brush defines the
color start with the GradientOrigin point (code fi le BrushesDemo/MainWindow.xaml):
DrawingBrush
The
DrawingBrush enables you to define a drawing that is created with the brush.
The drawing that is shown with the brush is defined within a GeometryDrawing
element. The GeometryGroup, which you can see within the Geometry property,
consists of the Geometry elements discussed earlier (code file
BrushesDemo/MainWindow.xaml):
Point3="150,63"
/>
Point3="45,91"
/>
ImageBrush
To
load an image into a brush, you can use the ImageBrush element. With this
element, the image defi ned by the ImageSource property is displayed. The image
can be accessed from the fi le system or from a resource within the assembly.
In the example (code file BrushesDemo/MainWindow.xaml), the image is added as a
resource to the assembly and referenced with the assembly and resource names:
Foreground="White">
VisualBrush
The
VisualBrush enables you to use other WPF elements in a brush. The following
example (code fi le BrushesDemo/MainWindow.xaml) adds a WPF element to the
Visual property. The sixth element in Figure contains a Rectangle and a Button:
You can add any
UIElement to the VisualBrush. For example, you can play a video by using the
MediaElement:
Foreground="White">
You
can also use the VisualBrush to create interesting effects such as reflection.
The button coded in
the
following example contains a StackPanel that itself contains a MediaElement
playing a video and a Border. The Border contains a Rectangle that is filled
with a VisualBrush. This brush defines an opacity value and a transformation.
The Visual property is bound to the Border element. The transformation is achieved
by setting the RelativeTransform property of the VisualBrush. This
transformation uses relative coordinates. By setting ScaleY to -1, a reflection
in the y axis is done. TranslateTransform moves the transformation in
the y axis so that the reflection is below the original object. You can see the
result in the eighth element in Figure.
Visual="{Binding
ElementName=reflected}">
CONTROLS
Because you can use
hundreds of controls with WPF, they are categorized into the following groups,
each of which is described in the following sections.
Decoration
You
can add decorations to a single element with the Decorator class. Decorator is
a base class that has derivations such as Border, Viewbox, and BulletDecorator.
Theme elements such as ButtonChrome and ListBoxChrome are also decorators. The
following example (code file DecorationsDemo/MainWindow.xaml) demonstrates a
Border, Viewbox, and BulletDecorator, as shown in Figure. The Border class
decorates the Children element by adding a border around it. You can define a
brush and the thickness of the border, the background, the radius of the
corner, and the padding of its children:
FIGURE
LAYOUT
To define the
layout of the application, you can use a class that derives from the Panel base
class. A layout container needs to do two main tasks: measure and arrange. With
measuring, the container asks
its children for the preferred sizes. Because the full size requested by the
controls might not be available, the container determines the available sizes
and arranges the positions of
its children accordingly. This section discusses several available layout
containers.
StackPanel
The
Window can contain just a single element as content, but if you want more than
one element inside it, you can use a StackPanel as a child of the Window, and
add elements to the content of the StackPanel. The StackPanel is a simple
container control that just shows one element after the other. The orientation
of the StackPanel can be horizontal or vertical. The class ToolBarPanel is
derived from StackPanel (code file LayoutDemo/StackPanelWindow.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StackPanelWindow"
Height="300" Width="300">
WrapPanel
The
WrapPanel positions the children from left to right, one after the other, as
long as they fit into the
line,
and then continues with the next line. The panel’s orientation can be
horizontal or vertical (code file LayoutDemo/WrapPanelWindow.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WrapPanelWindow"
Height="300" Width="300">
Figure
shows the output of the panel. If you resize the application, the buttons will
be rearranged accordingly so that they fit into a line.
Canvas
Canvas
is a panel that enables you to explicitly position controls. Canvas defines the
attached properties Left, Right, Top, and Bottom that can be used by the
children for positioning within the panel (code file LayoutDemo/CanvasWindow.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="CanvasWindow"
Height="300" Width="300">
DockPanel
The
DockPanel is very similar to the Windows Forms docking functionality. Here, you
can specify the area in which child controls should be arranged. DockPanel
defines the attached property Dock, which you can set in the children of the
controls to the values Left, Right, Top, and Bottom. Figure shows the outcome
of text blocks with borders that are arranged in the dock panel. For easier
differentiation, different colors are specified for the various areas (code
file LayoutDemo/
DockPanelWindow.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DockPanelWindow"
Height="300" Width="300">
Grid
Using
the Grid, you can arrange your controls with rows and columns. For every
column, you can specify a ColumnDefinition. For every row, you can specify a
RowDefinition. The following example code (code file
LayoutDemo/GridWindow.xaml) lists two columns and three rows. With each column
and row, you can specify the width or height. ColumnDefinition has a Width
dependency property; RowDefinition has a Height dependency property. You can
define the height and width in pixels, centimeters, inches, or points, or set
it to Auto to determine the size depending on the content. The grid also allows
star sizing, whereby the space
for the rows and columns is calculated according to the available space and
relative to other rows and columns. When providing the available space for a
column, you can set the Width property to *. To have the size doubled for
another column, you specify 2*. The sample code, which defines two columns and
three rows, doesn’t define additional settings with the column and row definitions;
the default is the star sizing. The grid contains several Label and TextBox
controls. Because the parent of these controls is a grid, you can set the
attached properties Column, ColumnSpan, Row, and RowSpan:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="GridWindow"
Height="300" Width="300">
VerticalAlignment="Center"
HorizontalAlignment="Center" Content="Title"
/>
Content="Firstname:"
Margin="10" />
Content="Lastname:"
Margin="10" />
The outcome of
arranging controls in a grid is shown in Figure. For easier viewing of the
columns and rows, the property ShowGridLines is set to true.
TRIGGERS
With
triggers you can change the look and feel of your controls dynamically based on
certain events or property value changes. For example, when the user moves the
mouse over a button, the button can change its look. Usually, you need to do
this with the C# code. With WPF, you can also do this with XAML, as long as
only the UI is influenced.
There
are several triggers with XAML. Property triggers are activated as soon as a
property value changes. Multi-triggers are based on multiple property values.
Event triggers fi re when an event occurs. Data triggers happen when data that
is bound is changed. This section discusses property triggers, multi-triggers,
and data triggers. Event triggers are explained later with animations.
Property
Triggers
The
Style class has a Triggers property whereby you can assign property triggers.
The following example (code fi le TriggerDemo/PropertyTriggerWindow.xaml)
includes a Button element inside a Grid panel. With the Window resources, a
default style for Button elements is defined. This style specifies that the Background
is set to LightBlue and the FontSize to 17. This is the style of the Button
elements when the application is started. Using triggers, the style of the
controls change. The triggers are defined within the Style.Triggers element,
using the Trigger element. One trigger is assigned to the property IsMouseOver;
the other trigger is assigned to the property IsPressed. Both of these
properties are defined with the Button class to which the style applies. If
IsMouseOver has a value of true, then the trigger fires and sets the Foreground
property to Red and the FontSize property to 22. If the Button is pressed, then
the property IsPressed is true, and the second trigger fi res and sets the
Foreground property of the TextBox to Yellow:
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PropertyTriggerWindow"
Height="300" Width="300">
You don’t need to reset the property values to the original values when the reason for the trigger is not valid anymore. For example, you don’t need to define a trigger for IsMouseOver=true and IsMouseOver=false. As soon as the reason for the trigger is no longer valid, the changes made by the trigger action are reset to the original values automatically. Figure shows the trigger sample application in which the foreground and font size of the button are changed from their original values when the button has the focus.
Data
Triggers
Data
triggers fire if bound data to a control fulfills specific conditions. In the
following example (code file TriggerDemo/Book.cs), a Book class is used that
has different displays depending on the publisher of the book. The Book class
defines the properties Title and Publisher and has an overload of the ToString
method:
public class Book
{
public string Title
{ get; set; }
public string
Publisher { get; set; }
public override
string ToString()
{
return Title;
}
}
In the XAML code, a
style is defined for ListBoxItem elements. The style contains DataTrigger
elements that are bound to the Publisher property of the class that is used
with the items. If the value of the Publisher property is Wrox Press, the
Background is set to Red. With the publishers Dummies and Wiley, the Background
is set to Yellow and DarkGray, respectively
(code file
TriggerDemo/DataTriggerWindow.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Data
Trigger Window" Height="300" Width="300">
In the code-behind
(code fi le TriggerDemo/DataTriggerWindow.xaml.cs), the list with the name
list1 is initialized to contain several Book objects:
public
DataTriggerWindow()
{
InitializeComponent();
list1.Items.Add(new
Book
{
Title =
"Professional C# 4.0 and .NET 4",
Publisher =
"Wrox Press"
});
list1.Items.Add(new
Book
{
Title = "C#
2010 for Dummies",
Publisher =
"For Dummies"
});
list1.Items.Add(new
Book
{
Title = "HTML
and CSS: Design and Build Websites",
Publisher =
"Wiley"
});
}
Running
the application, you can see in Figure the ListBoxItem elements that are
formatted according to the publisher value. With DataTrigger, multiple
properties must be set for
MultiDataTrigger
(similar to Trigger and MultiTrigger).
TEMPLATES
In
this, you have already seen that a Button control can contain any content. The
content can be simple text, but you can also add a Canvas element, which can contain
shapes; a Grid; or a video. In fact, you can do even more than that with a
button!
Control
Templates
Previously
in this chapter you’ve seen how the properties of a control can be styled. If
setting simple properties of the controls doesn’t give you the look you want,
you can change the Template property. With the Template property, you can
customize the complete look of the control. The next example demonstrates customizing
buttons; and later in the following sections (“Data Templates,” “Styling a ListBox,”
“ItemTemplate,” and “Control Templates for ListBox Elements”), list boxes are
customized step by step, so you can see the intermediate results of the
changes. You customize the Button type in a separate resource dictionary fi le,
Styles.xaml. Here, a style with the key name RoundedGelButton is defi ned. The
style GelButton sets the properties Background, Height,
Foreground,
and Margin, and the Template. The Template is the most interesting aspect with
this style. The Template specifi es a Grid with just one row and one column. Inside
this cell, you can find an ellipse with the name GelBackground. This ellipse
has a linear gradient brush for the stroke. The stroke that surrounds the
rectangle is very thin because the StrokeThickness is set to 0.5.
The
second ellipse, GelShine, is a small ellipse whose size is defined by the
Margin property and so is visible within the first ellipse. The stroke is
transparent, so there is no line surrounding the ellipse. This ellipse uses a
linear gradient fill brush, which transitions from a light, partly transparent
color to full transparency. This gives the ellipse a shimmering effect (code fi
le TemplateDemo/Styles.xaml):
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
From the app.xaml
fi le, the resource dictionary is referenced as shown here (code fi le TemplateDemo/App.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
Now a Button
control can be associated with the style. The new look of the button is shown
in Figure and uses code fi le TemplateDemo/StyledButtonWindow.xaml:
The
button now has a completely different look. However, the content that is defined
with the button itself is missing. The template created previously must be
extended to get the content of the Button into the new look. What needs to be
added is a ContentPresenter. The ContentPresenter is the placeholder for the control’s
content, and it defines the place where the content should be positioned. In
the code that follows (code fi le TemplateDemo/StyledButtonWindow.xaml), the
content is placed in the first row of the Grid, as are the Ellipse elements.
The Content property of the ContentPresenter defines what the content should be.
The content is set to a TemplateBinding markup expression. TemplateBinding
binds the template parent, which is the Button element in this case.
{TemplateBinding
Content} specifies that the value of the Content property of the Button control
should be placed inside the placeholder as content. Figure shows the result
with the content shown in the here:
VerticalAlignment="Center"
HorizontalAlignment="Center"
Content="{TemplateBinding
Content}" />
ANIMATIONS
Using animations
you can make a smooth transition between images by using moving elements,
color changes,
transforms, and so on. WPF makes it easy to create animations. You can animate
the value of any dependency property. Different animation classes exist to
animate the values of different properties, depending on their type.
The major elements
of animations are as follows:
➤ Timeline — Defines how a value changes over
time. Different kinds of timelines are available for
changing different
types of values. The base class for all timelines is Timeline. To animate a
double,
the class
DoubleAnimation can be used. Int32Animation is the animation class for int
values.
PointAnimation is
used to animate points, and ColorAnimation is used to animate colors.
➤ Storyboard — Used to combine animations. The
Storyboard class itself is derived from the base class TimelineGroup, which
derives from Timeline. With DoubleAnimation you can animate a double value;
with Storyboard you combine all the animations that belong together.
➤ Triggers — Used to start and stop animations.
You’ve seen property triggers previously, which fi re when a property value
changes. You can also create an event trigger. An event trigger fi res when an event
occurs.
Timeline:
A
Timeline defines how a value changes over time. The following example animates
the size of an ellipse. In the code that follows (code fi le
AnimationDemo/EllipseWindow.xaml),
a
DoubleAnimation timeline changes to a double value. The Triggers property of
the Ellipse class is set to an EventTrigger.
The
event trigger is fi red when the ellipse is loaded as defined with the
RoutedEvent property of the
EventTrigger.
BeginStoryboard is a trigger action that begins the storyboard. With the
storyboard, a
DoubleAnimation
element is used to animate the Width property of the Ellipse class. The
animation
changes
the width of the ellipse from 100 to 300 within three seconds, and reverses the
animation after three seconds. The animation ColorAnimation animates the color
from the ellipseBrush which is used to fill the ellipse:
Duration="0:0:3"
AutoReverse="True" FillBehavior="Stop"
RepeatBehavior="Forever"
AccelerationRatio="0.9"
DecelerationRatio="0.1"
From="100" To="300" />
Storyboard.TargetProperty="(SolidColorBrush.Color)"
Duration="0:0:3"
AutoReverse="True"
FillBehavior="Stop"
RepeatBehavior="Forever"
From="Yellow"
To="Red" />
Nonlinear
Animations
One
way to define nonlinear animations is by setting the speed of AccelerationRatio
and
DecelerationRatio
animation at the beginning and at the end. .NET 4.5 has more flexible
possibilities than that.
Several
animation classes have an EasingFunction property. This property accepts an
object that
implements
the interface IEasingFunction. With this interface, an easing function object
can define
how
the value should be animated over time. Several easing functions are available
to create a nonlinear animation. Examples include ExponentialEase, which uses
an exponential formula for animations; QuadraticEase, CubicEase, QuarticEase,
and QuinticEase, with powers of 2, 3, 4, or 5; and PowerEase, with a power
level that is configurable. Of special interest are SineEase, which uses a
sinusoid curve, BounceEase, which creates a bouncing effect, and ElasticEase,
which resembles animation values of a spring oscillating back and forth. Such
an ease can be specified in XAML by adding the ease to the EasingFunction
property of the animation as shown in the following code (code fi le
AnimationDemo/EllipseWindow.xaml). Adding different ease functions results in
very interesting animation effects:
Duration="0:0:3"
AutoReverse="True"
FillBehavior="
RepeatBehavior="Forever"
From="100"
To="300">
Event
Triggers
Instead
of having a property trigger, you can define an event trigger to start the
animation. The property trigger fi res when a property changes its value; the
event trigger fi res when an event occurs. Examples of such events are the Load
event from a control, the Click event from a Button, and the MouseMove event. The
next example creates an animation for the face that was created earlier with
shapes. It is now animated so that the eye moves as soon as a Click event from
a button is fi red. Inside the Window element, a DockPanel element is defined
to arrange the face and buttons to control the animation. A StackPanel that
contains three buttons is docked at the top. The Canvas element that contains
the face gets the remaining part of the DockPanel. The first button is used to
start the animation of the eye; the second button stops the animation. A third button
is used to start another animation to resize the face. The animation is defined
within the DockPanel.
Triggers
section. Instead of a property trigger, an event trigger is used. The first event
trigger is fi red as soon as the Click event occurs with the
buttonBeginMove-Eyes button defined by the RoutedEvent and SourceName
properties. The trigger action is defined by the BeginStoryboard element that
starts the containing Storyboard. BeginStoryboard has a name defined because a
name is needed to control the storyboard with pause, continue, and stop
actions. The Storyboard element contains four animations. The first two animate
the left eye; the last two animate the right eye. The
first
and third animation change the Canvas.Left position for the eyes, and the
second and fourth
animation
change Canvas.Top. The animations in the x and y axes have
different time values that make the eye movement very interesting using the
defined repeated behavior.
The
second event trigger is fi red as soon as the Click event of the
buttonStopMoveEyes button occurs. Here, the storyboard is stopped with the
StopStoryboard element, which references the started storyboard beginMoveEye. The
third event trigger is fi red by clicking the buttonResize button. With this
animation, the transformation of the Canvas element is changed. Because this
animation doesn’t run endlessly, there’s no stop.
This storyboard
also makes use of the EaseFunction explained previously (code file
AnimationDemo/EventTriggerWindow.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="EventTriggerWindow"
Height="300" Width="300">
AutoReverse="True"
By="6" Duration="0:0:1"
Storyboard.TargetName="eyeLeft"
Storyboard.TargetProperty="(Canvas.Left)"
/>
By="6"
Duration="0:0:5"
Storyboard.TargetName="eyeLeft"
Storyboard.TargetProperty="(Canvas.Top)"
/>
AutoReverse="True"
By="-6" Duration="0:0:3"
Storyboard.TargetName="eyeRight"
Storyboard.TargetProperty="(Canvas.Left)"
/>
By="6"
Duration="0:0:6"
Storyboard.TargetName="eyeRight"
Storyboard.TargetProperty="(Canvas.Top)"
/>
Storyboard.TargetName="scale1"
Storyboard.TargetProperty="(ScaleTransform.ScaleX)"
From="0.1"
To="3" Duration="0:0:5">
Storyboard.TargetName="scale1"
Storyboard.TargetProperty="(ScaleTransform.ScaleY)"
From="0.1"
To="3" Duration="0:0:5">
Stroke="Blue"
StrokeThickness="4" Fill="Yellow" />
Stroke="Blue"
StrokeThickness="3" Fill="White" />
Height="5"
Fill="Black" />
Stroke="Blue"
StrokeThickness="3" Fill="White" />
Height="5"
Fill="Black" />
Data="M 40,74
Q 57,95 80,74 " />
Business
Applications with WPF
INTRODUCTION
In
the previous chapter you read about some of the core functionality of WPF. This
chapter continues
the
journey through WPF. Here you read about important aspects for creating
complete applications,
such
as data binding and command handling, and about the DataGrid control. Data
binding is an
important
concept for bringing data from .NET classes into the user interface, and
allowing the user
to
change data. WPF not only allows binding to simple entities or lists, but also
offers binding of one
UI
property to multiple properties of possible different types with multi binding
and priority binding that you’ll learn here as well. Along with data binding it
is also important to validate data entered by a user. Here, you can read about
different ways for validation including the interface INotifyDataErrorInfo that
is new with .NET 4.5. Also covered in this chapter is commanding, which enables
mapping events from the UI to code. In contrast to the event model, this
provides a better separation between XAML and code. You will learn about using
predefined commands and creating custom commands. The TreeView and DataGrid
controls are UI controls to display bound data. You will see the TreeView control
to display data in the tree where data is loaded dynamically depending on the
selection of the user. With the DataGrid control you will learn how to using filtering,
sorting, and grouping, as well as one new
.NET 4.5 features
named live shaping that allows
changing sorting or filtering options to change in real time. To begin let’s
start with the Menu and the Ribbon controls. The Ribbon control made it into
the release of .NET 4.5.
MENU
AND RIBBON CONTROLS
Many data-driven
applications contain menus and toolbars or ribbon controls to enable users to
control actions. With WPF 4.5, ribbon controls are now available as well, so
both menu and ribbon controls are covered here. In this section, you create a
new WPF application named BooksDemo to use throughout this chapter — not only
with menu and ribbon controls but also with commanding and data binding. This
application displays a single book, a list of books, and a grid of books.
Actions are started from menu or ribbon controls to which commands associated.
Menu
Controls
Menus can easily be
created with WPF using the Menu and MenuItem elements, as shown in the
following code snippet containing two main menu items, File and Edit, and a
list of submenu entries. The _ in front of the characters marks the special
character that can be used to access the menu item easily without using the
mouse. Using the Alt key makes these characters visible and enables access to
the menu with this character. Some of these menu items have a command assigned,
as discussed in the next section (XAML file BooksDemo/MainWindow.xaml):
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:Wrox.ProCSharp.WPF"
Title="Books
Demo App" Height="400" Width="600">
BooksDemo
Application Content
In the previous
sections, a ribbon and commands have been defined with the BooksDemo
application. Now content is added. Change the XAML fi le MainWindow.xaml by
adding a ListBox, a Hyperlink, and a TabControl (XAML fi le
BooksDemo/MainWindow.xaml):
Now add a WPF user
control named BookUC. This user control contains a DockPanel, a Grid with
several rows and columns, a Label, and TextBox controls (XAML fi le
BooksDemo/BookUC.xaml):
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
HorizontalAlignment="Left"
VerticalAlignment="Center" />
Margin="10,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
/>
Margin="10,0,5,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
/>
Within the
OnShowBook handler in the MainWindow.xaml.cs, create a new instance of the user
control BookUC and add a new TabItem to the TabControl. Then change the
SelectedIndex property of the TabControl to open the new tab (code fi le
BooksDemo/MainWindow.xaml.cs):
private void
OnShowBook(object sender, ExecutedRoutedEventArgs e)
{
var bookUI = new
BookUC();
this.tabControl1.SelectedIndex
= this.tabControl1.Items.Add(
new TabItem {
Header = "Book", Content = bookUI });
}
After building the
project you can start the application and open the user control within the
TabControl by clicking the hyperlink.
Binding
with XAML
In addition to
being the target for data binding, a WPF element can also be the source. You
can bind the source property of one WPF element to the target of another WPF
element.
In the following
code example, data binding is used to resize the controls within the user
control with a slider. You add a StackPanel control to the user control BookUC,
which contains a Label and a Slider BUSINESS APPLICATIONS WITH WPF control. The
Slider control defines Minimum and Maximum values that define the scale, and an
initial value of 1 is assigned to the Value property
(XAML file
BooksDemo/BooksUC.xaml):
HorizontalAlignment="Right">
Width="150"
HorizontalAlignment="Right" />
Now you set the
LayoutTransform property of the Grid control and add a ScaleTransform element.
With the ScaleTransform element, the ScaleX and ScaleY properties are data
bound. Both properties are set with the Binding markup extension. In the
Binding markup extension, the ElementName is set to slider1 to reference the
previously created Slider control. The Path property is set to the Value
property to get the value of the slider:
ScaleX="{Binding
Path=Value, ElementName=slider1}"
ScaleY="{Binding
Path=Value, ElementName=slider1}" />
When running the
application, you can move the slider and thus resize the controls within the
Grid, as shown in Figures –
Simple
Object Binding
To bind to CLR
objects, with the .NET classes you just have to define properties, as shown in
the Book class example and the properties Title, Publisher, Isbn, and Authors.
This class is in the Data folder of the BooksDemo project (code fi le
BooksDemo/Data/Book.cs).
using
System.Collections.Generic;
namespace
Wrox.ProCSharp.WPF.Data
{
public class Book
{
public Book(string
title, string publisher, string isbn,
params string[]
authors)
{
this.Title = title;
this.Publisher =
publisher;
this.Isbn = isbn;
this.authors.AddRange(authors);
}
public Book()
:
this("unknown", "unknown", "unknown")
{
}
public string Title
{ get; set; }
public string
Publisher { get; set; }
public string Isbn
{ get; set; }
private readonly
List authors = new List();
public string[]
Authors
{
get
{
return
authors.ToArray();
}
}
public override
string ToString()
{
return Title;
}
}
}
In
the XAML code of the user control BookUC, several labels and TextBox controls
are defined to
display
book information. Using Binding markup extensions, the TextBox controls are
bound to the
properties
of the Book class. With the Binding markup extension, nothing more than the
Path property is defined to bind it to the property of the Book class. There’s
no need to define a source because the source is defined by assigning the
DataContext, as shown in the code-behind that follows. The mode is defined by
its default with the TextBox element, and this is two-way binding (XAML fi le
BooksDemo/BookUC.xaml):
With
the code-behind, a new Book object is created, and the book is assigned to the
DataContext
property
of the user control. DataContext is a dependency property that is defined with
the base class
FrameworkElement.
Assigning the DataContext with the user control means that every element in the
user control has a default binding to the same data context (code file
BooksDemo/MainWindow.xaml.cs):
private void
OnShowBook(object sender, ExecutedRoutedEventArgs e)
{
var bookUI = new
BookUC();
bookUI.DataContext
= new Book
{
Title =
"Professional C# 4 and .NET 4",
Publisher =
"Wrox Press",
Isbn =
"978-0-470-50225-9"
};
this.tabControl1.SelectedIndex
=
this.tabControl1.Items.Add(
new TabItem {
Header = "Book", Content = bookUI });
}
To
see two-way binding in action (changes to the input of the WPF element are reflected
inside
the
CLR object), the Click event handler of the button in the user control, the
OnShowBook method,
is
implemented. When implemented, a message box pops up to show the current title
and ISBN number of the book1 object. Figure 36-8 shows the output from the
message box after a change to the input was made during runtime (code fi le
BooksDemo/BookUC.xaml.cs):
private void
OnShowBook(object sender, RoutedEventArgs e)
{
Book theBook =
this.DataContext as Book;
if (theBook !=
null)
MessageBox.Show(theBook.Title,
theBook.Isbn);
}
Creating
Documents with WPF
INTRODUCTION
Creating
documents is a large part of WPF. The namespace System.Windows.Documents
supports
creating
both flow documents and fixed documents. This namespace contains elements with
which
you
can have a rich Word-like experience with flow documents, and create WYSIWYG fixed
documents.
Flow documents are geared toward screen
reading; the content of the document is arranged based
on
the size of the window and the flow of the document changes if the window is
resized. Fixed
documents are mainly used for
printing and page-oriented content and the content is always arranged
in
the same way. This chapter teaches you how to create and print flow documents
and fixed documents, and covers the namespaces System.Windows.Documents,
System.Windows.Xps, and System.IO.Packaging.
TEXT
ELEMENTS
To
build the content of documents, you need document elements. The base class of
these elements is
TextElement.
This class defines common properties for font settings, foreground and
background, and text effects. TextElement is the base class for the classes
Block and Inline, whose functionality is explored in the following sections.
Fonts
An
important aspect of text is how it looks, and thus the importance of the font.
With the TextElement, the font can be specified with the properties FontWeight,
FontStyle, FontStretch, FontSize, and FontFamily:
➤ FontWeight — Predefined values are specified by the FontWeights
class, which offers values such as UltraLight,
Light, Medium, Normal, Bold, UltraBold, and Heavy.
➤ FontStyle — Values are defined by the FontStyles class, which
offers Normal, Italic, and
Oblique.
➤ FontStretch — Enables you to specify the degrees to stretch the
font compared to the normal aspect ratio. FrontStretch defines predefined
stretches that range from 50% (UltraCondensed) to 200% (UltraExpanded). Predefined
values in between the range are ExtraCondensed (62.5%), Condensed (75%),
SemiCondensed (87.5%), Normal (100%), SemiExpanded (112.5%), Expanded (125%),
and ExtraExpanded (150%).
➤ FontSize — This is of type double and enables you to specify the
size of the font in device-independent units, inches, centimeters, and points.
➤ FontFamily — Use this to define the name of the preferred
font-family, e.g., Arial or Times New
Roman. With this
property you can specify a list of font family names so if one font is not
available,
the next one in the
list is used. (If neither the selected font nor the alternate font are
available, a flow
document falls back
to the default MessageFontFamily.) You can also reference a font family from a
resource or use a
URI to reference a font from a server. With fixed documents there’s no fallback
on a font not available because the font is available with the document.
To
give you a feel for the look of different fonts, the following sample WPF
application includes a ListBox. The ListBox defines an ItemTemplate for every
item in the list. This template uses four TextBlock elements whereby the
FontFamily is bound to the Source property of a FontFamily object. With
different TextBlock elements, FontWeight and FontStyle are set (XAML file ShowFonts/ShowFontsWindow.xaml):
FontSize="18"
Text="{Binding Path=Source}" />
FontSize="18"
FontStyle="Italic" Text="Italic" />
FontSize="18"
FontWeight="UltraBold" Text="UltraBold" />
FontSize="18"
FontWeight="UltraLight" Text="UltraLight" />
In the code-behind,
the data context is set to the result of the SystemFontFamilies property of
the
System.Windows.Media.Font class. This returns all the available fonts (code fi
le ShowFonts/
ShowFontsWindow.xaml.cs):
TextEffect
Now
let’s have a look into TextEffect, as it is also common to all document
elements. TextEffect is defined in the namespace System.Windows.Media and
derives from the base class Animatable, which enables the animation of text. TextEffect
enables you to animate a clipping region, the foreground brush, and a
transformation. With the properties PositionStart and PositionCount you specify
the position in the text to which the animation applies.
For
applying the text effects, the TextEffects property of a Run element is set.
The TextEffect
element
specifi ed within the property defines a foreground and a transformation. For
the foreground, a SolidColorBrush with the name brush1 is used that is animated
with a ColorAnimation element. The transformation makes use of a
ScaleTransformation with the name scale1, which is animated from two DoubleAnimation
elements (XAML file TextEffectsDemo/MainWindow.xaml):
From="Blue"
To="Red" Duration="0:0:16"
Storyboard.TargetName="brush1"
Storyboard.TargetProperty="Color"
/>
RepeatBehavior="Forever"
From="0.2"
To="12" Duration="0:0:16"
Storyboard.TargetName="scale1"
Storyboard.TargetProperty="ScaleX"
/>
RepeatBehavior="Forever"
From="0.2"
To="12" Duration="0:0:16"
Storyboard.TargetName="scale1"
Storyboard.TargetProperty="ScaleY"
/>
cn|elements
Running the
application, you can see the changes in size and color as shown in Figures
FLOW
DOCUMENTS
With
all the Inline and Block elements, now you know what should be put into a flow
document. The
class
Flow Document can contain Block elements, and the Block elements can contain
Block or Inline elements, depending on the type of the Block. A major
functionality of the FlowDocument class is that it is used to break up the flow
into multiple pages.
This
is done via the IDocumentPaginatorSource interface, which is implemented by
FlowDocument.
Other
options with a FlowDocument are to set up the default font and foreground and
background brushes, and to confi gure the page and column sizes.
The
following XAML code for the FlowDocument defines a default font and font size,
a column width, and a ruler between columns:
ColumnWidth="300"
FontSize="16" FontFamily="Georgia"
ColumnRuleWidth="3"
ColumnRuleBrush="Violet">
Now you just need a
way to view the documents. The following list describes several viewers:
➤ RichTextBox — A simple viewer that also allows editing (as long
as the IsReadOnly property is not set to true). The RichTextBox doesn’t display
the document with multiple columns but instead in
scroll mode. This
is similar to the Web layout in Microsoft Word. The scrollbar can be enabled by
setting the HorizontalScrollbarVisibility to ScrollbarVisibility.Auto.
➤ FlowDocumentScrollViewer — A reader that is meant only to read
but not edit documents.
This reader enables
zooming into the document. There’s also a toolbar with a slider for zooming
that can be enabled
with the property IsToolbarEnabled. Settings such as CanIncreaseZoom,
CanDecreaseZoom,
MinZoom, and MaxZoom enable setting the zoom features.
➤ FlowDocumentPageViewer — A viewer that paginates the document.
With this viewer you not only have a toolbar to zoom into the document, you can
also switch from page to page.
➤ FlowDocumentReader — A
viewer that combines the functionality of FlowDocumentScrollViewer and
FlowDocumentPageViewer. This viewer supports different viewing modes that can
be set from the toolbar or with the property ViewingMode that is of type
FlowDocumentReaderViewingMode. This enumeration has the possible values Page,
TwoPage, and Scroll. The viewing modes can also be disabled according to your
needs. The sample application to demonstrate flow documents defines several
readers such that one reader can be chosen dynamically. Within the Grid element
you can find the FlowDocumentReader, RichTextBox,
FlowDocumentScrollViewer,
and FlowDocumentPageViewer.
With
all the readers the Visibility property is set to Collapsed, so on startup none
of the readers appear. The ComboBox that is the first child element within the
grid enables the user to select the active reader. The ItemsSource property of
the ComboBox is bound to the Readers property to display the list of readers.
On selection of a reader, the method OnReaderSelectionChanged is invoked (XAML
fi le FlowDocumentsDemo/MainWindow.xaml):
Margin="4"
SelectionChanged="OnReaderSelectionChanged"
SelectedIndex="0">
Open
Document
Visibility=”Collapsed” Grid.ColumnSpan=”2” />
VerticalScrollBarVisibility=”Auto” Visibility=”Collapsed”
Grid.Row=”1” Grid.ColumnSpan=”2” />
Grid.ColumnSpan=”2” />
Grid.ColumnSpan=”2” />
The
Readers property of the MainWindow class invokes the GetReaders method to
return to return the readers to the ComboBox data binding. The GetReaders
method returns the list assigned to the variable documentReaders. In case
documentReaders was not yet assigned, the LogicalTreeHelper class is used to
get all the flow document readers within the grid grid1. As there is not a base
class for a flow document reader nor an interface implemented by all readers,
the LogialTreeHelper looks for all elements of type FrameworkElement that have
a property Document. The Document property is common to all flow document
readers. With every reader a new anonymous object is created with the
properties Name and Instance. The Name property is used to appear in the
ComboBox to enable the user to select the active reader, and the Instance
property holds a reference to the reader to show the reader if it should be
active (code file FlowDocumentsDemo/MainWindow.xaml.cs):
public
IEnumerable
{
get
{
return
GetReaders();
}
}
private
List
private
IEnumerable
{
return
documentReaders ?? (documentReaders =
LogicalTreeHelper.GetChildren(grid1).OfType()
.Where(el =>
el.GetType().GetProperties()
.Where(pi =>
pi.Name == "Document").Count() > 0)
.Select(el =>
new
{
Name =
el.GetType().Name,
Instance = el
}).Cast
}
When
the user selects a flow document reader, the method OnReaderSelectionChanged is
invoked. The XAML code that references this method was shown earlier. Within
this method the previously selected flow document reader is made invisible by
setting it to collapsed, and the variable activeDocumentReader is set to the
selected reader:
private void
OnReaderSelectionChanged(object sender,
SelectionChangedEventArgs
e)
{
dynamic item =
(sender as ComboBox).SelectedItem;
if
(activedocumentReader != null)
{
activedocumentReader.Visibility
= Visibility.Collapsed;
}
activedocumentReader
= item.Instance;
}
private dynamic
activedocumentReader = null;
When
the user clicks the button to open a document, the method OnOpenDocument is
invoked. With this method the XamlReader class is used to load the selected
XAML file. If the reader returns a FlowDocument (which is the case when the
root element of the XAML is the FlowDocument element), the Document property of
the activeDocumentReader is assigned, and the Visibility is set to visible:
private void
OnOpenDocument(object sender, RoutedEventArgs e)
{
try
{
var dlg = new
OpenFileDialog();
dlg.DefaultExt =
"*.xaml";
dlg.InitialDirectory
= Environment.CurrentDirectory;
if
(dlg.ShowDialog() == true)
{
using (FileStream
xamlFile = File.OpenRead(dlg.FileName))
{
var doc = XamlReader.Load(xamlFile) as FlowDocument;
if (doc != null)
{
activedocumentReader.Document = doc;
activedocumentReader.Visibility = Visibility.Visible;
}
}
}
}
catch
(XamlParseException ex)
{
MessageBox.Show(string.Format("Check
content for a Flow document, {0}",
ex.Message));
}
}
FIXED
DOCUMENTS
Fixed documents always
define the same look, the same pagination, and use the same fonts — no matter where
the document is copied or used. WPF defines the class FixedDocument to create
fixed documents, and the class DocumentViewer to view fixed documents. This
section uses a sample application to create a fixed document programmatically
by requesting user input for a menu plan. The data for the menu plan is the
content of the fixed document. Figure shows the main user interface of this
application, where the user can select a day with the DatePicker class, enter menus
for a week in a DataGrid, and click the Create Doc button to create a new
FixedDocument. This application uses Page objects that are navigated within a
NavigationWindow. Clicking the Create Doc button navigates to a new page that
contains the fixed document.
The
event handler for the Create Doc button, OnCreateDoc, navigates to a new page.
To do this, the
handler
instantiates the new page, DocumentPage. This page includes a handler,
NavigationService_
LoadCompleted,
that is assigned to the LoadCompleted event of the NavigationService. Within
this
handler the new page can access the content that is passed to the page. Then
the navigation is
done
by invoking the Navigate method to page2. The new page receives the object
menus that contains all the menu information needed to build the fixed page.
menus is a readonly variable of type ObservableCollection
(code fi le CreateXps/MenuPlannerPage.xaml.cs):
private void
OnCreateDoc(object sender, RoutedEventArgs e)
{
if (menus.Count ==
0)
{
MessageBox.Show("Select
a date first", "Menu Planner",
MessageBoxButton.OK);
return;
}
var page2 = new
DocumentPage();
NavigationService.LoadCompleted
+=
page2.NavigationService_LoadCompleted;
NavigationService.Navigate(page2,
menus);
}
Within
the DocumentPage, a DocumentViewer is used to provide read access to the fixed
document. The fixed document is created in the method
NavigationService_LoadCompleted. With the event handler, the data that is
passed from the first page is received with the ExtraData property of
NavigationEventArgs. The received ObservableCollection is
assigned to a menus variable that is used to build the fixed page (code file
CreateXps/DocumentPage.xaml.cs):
internal void
NavigationService_LoadCompleted(object sender,
NavigationEventArgs
e)
{
menus = e.ExtraData
as ObservableCollection;
fixedDocument = new
FixedDocument();
var pageContent1 =
new PageContent();
fixedDocument.Pages.Add(pageContent1);
var page1 = new
FixedPage();
pageContent1.Child
= page1;
page1.Children.Add(GetHeaderContent());
page1.Children.Add(GetLogoContent());
page1.Children.Add(GetDateContent());
page1.Children.Add(GetMenuContent());
viewer.Document =
fixedDocument;
NavigationService.LoadCompleted
-= NavigationService_LoadCompleted;
}
Fixed
documents are created with the FixedDocument class. The FixedDocument element
only contains PageContent elements that are accessible via the Pages property.
The PageContent elements must be added to the document in the order in which
they should appear on the page. PageContent defines the content of a single
page. PageContent has a Child property such that a FixedPage can be associated
with it. To the FixedPage you can add elements of type UIElement to the
Children collection. This is where you can add all the elements you’ve learned
about in the last two chapters, including a TextBlock element that itself can
contain Inline and Block elements. In the sample code, the children to the
FixedPage are created with helper methods GetHeaderContent, GetLogoContent,
GetDateContent, and GetMenuContent. The method GetHeaderContent creates a
TextBlock that is returned. The TextBlock has the Inline element Bold added,
which in turn has the Run element added. The Run element then contains the
header text for the document. With FixedPage.SetLeft and FixedPage.SetTop the
position of the TextBox within the fixed page is defined:
private static
UIElement GetHeaderContent()
{
var text1 = new TextBlock
{
FontFamily = new
FontFamily("Segoe UI"),
FontSize = 34,
HorizontalAlignment
= HorizontalAlignment.Center
};
text1.Inlines.Add(new
Bold(new Run("cn|elements")));
FixedPage.SetLeft(text1,
170);
FixedPage.SetTop(text1,
40);
return text1;
}
The method
GetLogoContent adds a logo in the form of an Ellipse with a RadialGradientBrush
to the
fixed document:
private static
UIElement GetLogoContent()
{
var ellipse = new
Ellipse
{
Width = 90,
Height = 40,
Fill = new
RadialGradientBrush(Colors.Yellow, Colors.DarkRed)
};
FixedPage.SetLeft(ellipse,
500);
FixedPage.SetTop(ellipse,
50);
return ellipse;
}
The method
GetDateContent accesses the menus collection to add a date range to the
document:
private UIElement
GetDateContent()
{
Contract.Requires(menus
!= null);
Contract.Requires(menus.Count
> 0);
string dateString =
String.Format("{0:d} to {1:d}",
menus[0].Day,
menus[menus.Count - 1].Day);
var text1 = new
TextBlock
{
FontSize = 24,
HorizontalAlignment
= HorizontalAlignment.Center
};
text1.Inlines.Add(new
Bold(new Run(dateString)));
FixedPage.SetLeft(text1,
130);
FixedPage.SetTop(text1,
90);
return text1;
}
Finally, the method
GetMenuContent creates and returns a Grid control. This grid contains columns
and rows that contain the date, menu, and price information:
private UIElement
GetMenuContent()
{
var grid1 = new
Grid { ShowGridLines = true };
grid1.ColumnDefinitions.Add(new
ColumnDefinition
{ Width= new
GridLength(50)});
grid1.ColumnDefinitions.Add(new
ColumnDefinition
{ Width = new
GridLength(300)});
grid1.ColumnDefinitions.Add(new
ColumnDefinition
{ Width = new
GridLength(70) });
for (int i = 0; i
< menus.Count; i++)
{
grid1.RowDefinitions.Add(new
RowDefinition
{ Height = new
GridLength(40) });
var t1 = new
TextBlock(new Run(String.Format(
"{0:ddd}",
menus[i].Day)));
var t2 = new
TextBlock(new Run(menus[i].Menu));
var t3 = new
TextBlock(new Run(menus[i].Price.ToString()));
var textBlocks =
new TextBlock[] { t1, t2, t3 };
for (int column =
0; column < textBlocks.Length; column++)
{
textBlocks[column].VerticalAlignment
= VerticalAlignment.Center;
textBlocks[column].Margin
= new Thickness(5, 2, 5, 2);
Grid.SetColumn(textBlocks[column],
column);
Grid.SetRow(textBlocks[column],
i);
grid1.Children.Add(textBlocks[column]);
}
}
FixedPage.SetLeft(grid1,
100);
FixedPage.SetTop(grid1,
140);
return grid1;
}
Run the application
to see the created fixed document shown in Figure-
XPS
DOCUMENTS
With
Microsoft Word you can save a document as a PDF or a XPS fi le. XPS is the XML Paper Specifi cation,
a subset of WPF. Windows includes an XPS reader. .NET includes classes and
interfaces to read and write XPS documents with the namespaces System .Windows.Xps,
System.Windows.Xps.Packaging, and System.IO.Packaging. XPS is packaged in the
zip fi le format, so you can easily analyze an XPS document by renaming a fi le
with an .xps extension to .zip and opening the archive.
An
XPS fi le requires a specific structure in the zipped document that is defined
by the XML Paper
Specifi
cations (which you can download from
http://www.microsoft.com/whdc/xps/xpsspec.mspx).
The
structure is based on the Open Packaging Convention (OPC) that Word documents
(OOXML or Office Open XML) are based on as well. Within such a fi le you can find
different folders for metadata, resources (such as fonts and pictures), and the
document itself. Within the document folder of an XPS document is the XAML code
representing the XPS subset of XAML. To create an XPS document, you use the
XpsDocument class from the namespace System.Windows.Xps .Packaging. To use this
class, you need to reference the assembly ReachFramework as well. With this
class
you
can add a thumbnail (AddThumbnail) and fixed document sequences (AddFixedDocumentSequence) to the document, as
well as digitally sign the document. A fixed document sequence is written by
using the interface IXpsFixedDocumentSequenceWriter, which in turn uses an
IXpsFixedDocumentWriter to write the document within the sequence.
If
a FixedDocument already exists, there’s an easier way to write the XPS
document. Instead of adding every resource and every document page, you can use
the class XpsDocumentWriter from the namespace System .Windows.Xps. For this
class the assembly System.Printing must be referenced.
With
the following code snippet you can see the handler to create the XPS document.
First, a fi lename for the menu plan is created that uses a week number in
addition to the name menuplan. The week number is calculated with the help of
the GregorianCalendar class. Then the SaveFileDialog is opened to enable the
user overwrite the created filename and select the directory where the fi le
should be stored. The SaveFileDialog class is defined in the namespace
Microsoft.Win32 and wraps the native fi le dialog. Then a new XpsDocument is
created whose filename is passed to the constructor. Recall that the XPS fi le
uses a .zip format to compress the content. With the CompressionOption you can
specify whether the compression should be optimized for time or space.
Next,
an XpsDocumentWriter is created with the help of the static method XpsDocument
.CreateXpsDocumentWriter.
The Write method of the XpsDocumentWriter is overloaded to accept
different
content or content parts to write the document. Examples of acceptable options
with the Write method are FixedDocumentSequence, FixedDocument, FixedPage,
string, and a DocumentPaginator. In the sample code, only the fixedDocument
that was created earlier is passed:
private void
OnCreateXPS(object sender, RoutedEventArgs e)
{
var c = new
GregorianCalendar();
int weekNumber =
c.GetWeekOfYear(menus[0].Day,
CalendarWeekRule.FirstFourDayWeek,
DayOfWeek.Monday);
string fileName =
String.Format("menuplan{0}", weekNumber);
var dlg = new
SaveFileDialog
{
FileName =
fileName,
DefaultExt =
"xps",
Filter = "XPS
Documents|*.xps|All Files|*.*",
AddExtension = true
};
if
(dlg.ShowDialog() == true)
{
var doc = new
XpsDocument(dlg.FileName, FileAccess.Write,
CompressionOption.Fast);
XpsDocumentWriter
writer = XpsDocument.CreateXpsDocumentWriter(doc);
writer.Write(fixedDocument);
doc.Close();
}
}
By running the
application to store the XPS document, you can view the document with an XPS
viewer, as shown in Figure
Reach us At: - 0120-4029000; 0120-4029024; 0120-4029025,
0120-4029027; 0120-4029029
Mbl: 9953584548
























No comments:
Post a Comment