1. Trang chủ
  2. » Công Nghệ Thông Tin

Pro C# 2008 and the .NET 3.5 Platform, Fourth Edition phần 9 doc

140 386 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 140
Dung lượng 5,27 MB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

■ Source Code The SimpleDockPanel.xamlfile can be found under the Chapter 29 subdirectory.Enabling Scrolling for Panel Types It is worth pointing out the WPF supplies a type, which prov

Trang 1

Figure 29-23 Grid types containing splitters

■ Source Code The GridWithSplitter.xamlfile can be found under the Chapter 29 subdirectory

Positioning Content Within DockPanel Panels

DockPanel is typically used as a master panel that contains any number of additional panels forgrouping of related content DockPanels make use of attached property syntax as seen with theCanvas type, to control where their upper-left corner (the default) will attach itself within the panel.Here is a very simple DockPanel definition, which results in the output shown in Figure 29-24:

<DockPanel LastChildFill ="True">

<! Dock items to the panel >

<Label DockPanel.Dock ="Top" Name="lblInstruction"

FontSize="15">Enter Car Information</Label>

<Label DockPanel.Dock ="Left" Name="lblMake">Make</Label>

<Label DockPanel.Dock ="Right" Name="lblColor">Color</Label>

<Label DockPanel.Dock ="Bottom" Name="lblPetName">Pet Name</Label>

<Button Name="btnOK">OK</Button>

</DockPanel>

Figure 29-24.A simple DockPanel

■ Note If you add multiple elements to the same side of a DockPanel, they will be stacked along the specifiededge in the order that they are declared

The benefit of using DockPanel types is that as the user resizes the window, each elementremains “connected” to the specified side of the panel (via DockPanel.Dock) Also notice that theopening <DockPanel> element sets the LastChildFill attribute to true Given that the Button typehas not specified any DockPanel.Dock value, it will therefore be stretched within the remainingspace

www.free-ebooks-download.org

Trang 2

■ Source Code The SimpleDockPanel.xamlfile can be found under the Chapter 29 subdirectory.

Enabling Scrolling for Panel Types

It is worth pointing out the WPF supplies a <ScrollViewer> type, which provides automatic scrolling

behaviors for nested panel types:

<ScrollViewer>

<StackPanel>

<Button Content ="First" Background = "Green" Height ="40"/>

<Button Content ="Second" Background = "Red" Height ="40"/>

<Button Content ="Third" Background = "Pink" Height ="40"/>

<Button Content ="Fourth" Background = "Yellow" Height ="40"/>

<Button Content ="Fifth" Background = "Blue" Height ="40"/>

</StackPanel>

</ScrollViewer>

The result of the previous XAML definition is shown in Figure 29-25

Figure 29-25.Working with the ScrollViewer type

■ Source Code The ScrollViewer.xamlfile can be found under the Chapter 29 subdirectory

As you would expect, each panel provides numerous members that allow you to fine-tune tent placement On a related note, WPF controls all support two properties of interest (Padding and

con-Margin) that allow the control itself to inform the panel how it wishes to be treated Specifically, the

Padding property controls how much extra space should surround the interior control, while Margin

controls the extra space around the exterior of a control

This wraps up our look at the major panel types of WPF, and the various ways they positiontheir content Next, we will see an example using nested panels to create a layout system for a main

window To do so, we will enhance the functionality of the TextControls project (e.g., the

spell-checker app) to support a main menu, a status bar, and a toolbar

Building a Window’s Frame Using Nested Panels

This updated version of the application (which we will assume is a new Visual Studio 2008 WPF

Application project named MySpellChecker) will be extended and finalized over the pages to come,

so for the time being, you will construct the core layout and base functionality

Our goal is to construct a layout where the main window has a topmost menu system, a bar, and a status bar mounted on the bottom of the window The status bar will contain a pane to

tool-hold text prompts that are displayed when the user selects a menu item (or toolbar button), while

www.free-ebooks-download.org

Trang 3

the menu system and toolbar will offer UI triggers to close the application and display spelling gestions in an Expander widget Figure 29-26 shows the initial layout we are shooting for, displayingspelling suggestions for “XAML.”

sug-Figure 29-26.Using nested panels to establish a window’s UI

Notice that our two toolbar buttons are not supporting an expected image, but a simple textvalue While this would not be sufficient for a production-level application, assigning images totoolbar buttons typically involves using embedded resources, a topic that you will examine inChapter 30 (so text data will do for now) Also note that as the mouse button is placed over theCheck button, the mouse cursor changes and the single pane of the status bar displays a useful UImessage

To begin building this UI, update the initial XAML definition for your Window type to make use

of a <DockPanel> child element, rather than the default <Grid>:

Building the Menu System

Menu systems in WPF are represented by the Menu type, which maintains a collection of MenuItemobjects When building a menu system in XAML, each MenuItem may handle various events, mostnotably Click, which occurs when the end user selects a subitem In our example, we will build twotopmost menu items (File and Tools), which expose Exit and Spelling Hints subitems (respectively)

In addition to handling the Click event for each subitem, we will also handle the MouseEnter andMouseExit events, which will be used to set the status bar text in a later step Add the followingmarkup within your <DockPanel> scope:

www.free-ebooks-download.org

Trang 4

<! Doc menu system on the top >

<Menu DockPanel.Dock ="Top"

HorizontalAlignment="Left" Background="White" BorderBrush ="Black">

<MenuItem Header="_File" Click ="FileExit_Click" >

<Separator/>

<MenuItem Header ="_Exit" MouseEnter ="MouseEnterExitArea"

MouseLeave ="MouseLeaveArea" Click ="FileExit_Click"/>

</MenuItem>

<MenuItem Header="_Tools">

<MenuItem Header ="_Spelling Hints" MouseEnter ="MouseEnterToolsHintsArea"

MouseLeave ="MouseLeaveArea" Click ="ToolsSpellingHints_Click"/>

</MenuItem>

</Menu>

Notice that we have docked the menu system to the top of the DockPanel As well, the

<Separator> element has been used to insert a thin horizontal line in the menu system, directly

before the Exit option Also notice that the Header values for each MenuItem contain an embedded

underbar token (for example, _Exit) This is used to establish which letter will be underlined when

the end user presses the Alt key (for keyboard shortcuts)

The complete the menu system definition, we now need to implement the various event

han-dlers First, we have the File ➤ Exit handler, FileExit_Click(), which will simply terminate the

application via Application.Current.Shutdown() The MouseEnter and MouseExit event handlers for

each subitem will eventually update our status bar; however, for now, we will simply provide shells

Finally, the ToolsSpellingHints_Click() handler for the Tools ➤ Spelling Hints menu item will also

be a shell for the time being Here are the current updates to your code-behind file:

public partial class MainWindow : System.Windows.Window

Trang 5

Building the ToolBar Type

Toolbars (represented by the ToolBar type in WPF) typically provide an alternative manner to vate a menu option Add the following markup directly after the closing scope of your <Menu>definition:

acti-<! Put Toolbar under the Menu >

<ToolBar DockPanel.Dock ="Top" >

<Button Content ="Exit" MouseEnter ="MouseEnterExitArea"

MouseLeave ="MouseLeaveArea" Click ="FileExit_Click"/>

<Separator/>

<Button Content ="Check" MouseEnter ="MouseEnterToolsHintsArea"

MouseLeave ="MouseLeaveArea" Click ="ToolsSpellingHints_Click"

Cursor="Help" />

</ToolBar>

Our <ToolBar> type consists of two Button types, which just so happen to handle the sameevents and are handled by the same methods in our code file Using this technique, we are able todouble-up our handlers to serve both menu items and toolbar buttons Although this toolbar ismaking use of the typical push buttons, do know that the ToolBar type “is-a” ContentControl, andtherefore you are free to embed any types into its surface (drop-down lists, images, graphics, etc.).The only other point of interest is that the Check button supports a custom mouse cursor via theCursor property

■ Note The ToolBartype may optionally be wrapped within a <ToolBarTray>element, which controls layout,docking, and drag-and-drop operations for a set of ToolBarobjects Consult the NET Framework 3.5 SDK docu-mentation for details

Building the StatusBar Type

The StatusBar type will be docked to the lower portion of the <DockPanel> and contain a single

<TextBlock> type, which up until this point in the chapter we have not made use of Like a TextBox,

a TextBlock can be used to hold text In addition, TextBlock types honor the use of numerous tual annotations such as bold text, underlined text, line breaks, and so forth While our StatusBardoes not technically need this support, another benefit of a TextBlock type is that it is optimized forsmall blurbs of text, such as UI prompts in a status bar pane Add the following markup directlyafter the previous ToolBar definition:

tex-<! Put a StatusBar at the bottom >

<StatusBar DockPanel.Dock ="Bottom" Background="Beige" >

Trang 6

Figure 29-27.The current user interface of our spell-checker application

Finalizing the UI Design

The final aspect of our UI design is to define a splittable Grid type that defines two columns On

the left will be the Expander type that will display a list of spelling suggestions, wrapped within a

<StackPanel> On the right will be a TextBox type that supports multiple lines and has enabled spell

checking The entire <Grid> will be mounted to the left of the parent <DockPanel> Add the following

XAML markup to complete the definition of our Window’s UI:

<Grid DockPanel.Dock ="Left" Background ="AliceBlue">

<! Define the rows and columns >

<Grid.ColumnDefinitions>

<ColumnDefinition />

<ColumnDefinition />

</Grid.ColumnDefinitions>

<GridSplitter Grid.Column ="0" Width ="5" Background ="Gray" />

<StackPanel Grid.Column="0" VerticalAlignment ="Stretch" >

<Label Name="lblSpellingInstructions" FontSize="14" Margin="10,10,0,0">

Spelling Hints

</Label>

www.free-ebooks-download.org

Trang 7

<Expander Name="expanderSpelling" Header ="Try these!" Margin="10,10,10,10">

<! This will be filled programmatically >

<Label Name ="lblSpellingHints" FontSize ="12"/>

Finalizing the Implementation

At this point, your UI is complete The only remaining tasks are to provide an implementation forthe remaining event handlers Here is the relevant code in question, which requires little comment

by this point in the chapter:

public partial class MainWindow : System.Windows.Window

{

protected void ToolsSpellingHints_Click(object sender, RoutedEventArgs args)

{

string spellingHints = string.Empty;

// Try to get a spelling error at the current caret location.

SpellingError error = txtData.GetSpellingError(txtData.CaretIndex);

if (error != null){

// Build a string of spelling suggestions.

foreach (string s in error.Suggestions){

protected void MouseEnterExitArea(object sender, RoutedEventArgs args)

Trang 8

understanding of control commands.

Understanding WPF Control Commands

The next major discussion of this chapter is to examine the topic of control commands Windows

Presentation Foundation provides support for what might be considered “control-agnostic events”

via control commands As you know, a typical NET event is defined within a specific base class and

can only be used by that class or a derivative thereof Furthermore, normal NET events are tightly

coupled to the class in which they are defined

In contrast, WPF control commands are event-like entities that are independent from a specificcontrol and in many cases can be successfully applied to numerous (and seemingly unrelated) con-

trol types By way of a few examples, WPF supports Copy, Paste, and Cut commands, which can be

applied to a wide variety of UI elements (menu items, toolbar buttons, custom buttons) as well as

keyboard shortcuts (Ctrl+C, Ctrl+V, etc.)

While other UI toolkits (such as Windows Forms) provided standard events for such purposes,the end result was typically redundant and hard to maintain code Under the WPF model, com-

mands can be used as an alternative The end result typically yields a smaller and more flexible

code base

The Intrinsic Control Command Objects

WPF ships with numerous built-in control commands, all of which can be configured with

associ-ated keyboard shortcuts (or other input gestures) Programmatically speaking, a WPF control

command is any object that supports a property (often called Command) that returns an object

imple-menting the ICommand interface, shown here:

public interface ICommand

{

// Occurs when changes occur that affect whether

// or not the command should execute.

event EventHandler CanExecuteChanged;

// Defines the method that determines whether the command

// can execute in its current state.

bool CanExecute(object parameter);

// Defines the method to be called when the command is invoked.

void Execute(object parameter);

}

While you could provide your own implementation of this interface to account for a controlcommand, the chances that you will need to are slim, given functionality provided by the five WPF

command objects out of the box These static classes define numerous properties that expose

objects that implement ICommand, most commonly the RoutedUICommand type, which adds support

for the WPF routed event model

Table 29-4 documents some core properties exposed by each of the intrinsic command objects(be sure to consult the NET Framework 3.5 SDK documentation for complete details)

www.free-ebooks-download.org

Trang 9

Table 29-4.The Intrinsic WPF Control Command Objects

WPF Control Command Object Example Control Command Properties Meaning in Life

ApplicationCommands Close, Copy, Cut, Delete, Find, Open, Defines properties that

Paste, Save, SaveAll, Redo, Undo represent

application-level commandsComponentsCommands MoveDown, MoveFocusBack, MoveLeft, Defines properties that

UI elementsMediaCommands BoostBase, ChannelUp, ChannelDown, Defines properties that

FastForward, NextTrack, Play, allow various

common commands

Favorites, LastPage, NextPage, properties that are used

utilize the WPFnavigation model

DecreaseFontSize, EnterLineBreak, properties typically used EnterParagraphBreak, MoveDownByLine, when programming with

WPF document API

Connecting Commands to the Command Property

If you wish to connect any of these command properties to a UI element that supports the Commandproperty (such as a Button or MenuItem), you have very little work to do To see how to do so, updatethe current menu system to support a new topmost menu item named Edit and three subitems toaccount for copying, pasting, and cutting of textual data:

<Menu DockPanel.Dock ="Top"

HorizontalAlignment="Left" Background="White" BorderBrush ="Black">

<MenuItem Header="_File" Click ="FileExit_Click" >

<Separator/>

<MenuItem Header ="_Exit" MouseEnter ="MouseEnterExitArea"

MouseLeave ="MouseLeaveArea" Click ="FileExit_Click"/>

</MenuItem>

<! New menu item with commands! >

<MenuItem Header="_Edit">

<MenuItem Command ="ApplicationCommands.Copy"/>

<MenuItem Command ="ApplicationCommands.Cut"/>

<MenuItem Command ="ApplicationCommands.Paste"/>

</MenuItem>

<MenuItem Header="_Tools">

<MenuItem Header ="_Spelling Hints" MouseEnter ="MouseEnterToolsHintsArea"

MouseLeave ="MouseLeaveArea" Click ="ToolsSpellingHints_Click"/>

</MenuItem>

</Menu>

www.free-ebooks-download.org

Trang 10

Notice that each subitem has a value assigned to the Command property By doing so, the menuitems automatically receive the correct name and shortcut key (for example, Ctrl+C for a cut

operation) in the menu item UI and the application is now “copy, cut, and paste” aware with no

procedural code Thus, if you were to run the application and select some of your text, you will

be able to use your new menu items out of the box, as shown in Figure 29-28

Figure 29-28.Command objects provide a good deal of canned functionality for free.

Connection Commands to Arbitrary UI Elements

If you wish to connect a command to a UI element that does not support the Command property,

doing so requires you to drop down to procedural code Doing so is certainly not complex, but it

does involve a bit more logic than you see in XAML For example, what if you wished to have the

entire window respond to the F1 key, so that when the end user presses this key, he or she would

activate an associated help system?

Assume your code file for the main window defines a new method namedSetF1CommandBinding(), which is called within the constructor after the call to InitializeComponent()

This new method will programmatically create a new CommandBinding object, which is configured to

operate with the ApplicationCommands.Help option, which is automatically F1-aware:

private void SetF1CommandBinding()

Executed event (which is where you can author the content that should occur once the command

occurs) Add the following event handlers to your Window-derived type (take note of the format of

each method as required by the associated delegates):

private void CanHelpExecute(object sender, CanExecuteRoutedEventArgs e)

{

// Here, you can set CanExecute to false if you wish to prevent the

// command from executing if you desire.

www.free-ebooks-download.org

Trang 11

Figure 29-29.Our custom help system

■ Source Code The MySpellChecker project can be found under the Chapter 29 subdirectory

Understanding the WPF Data-Binding Model

Controls are often the target of various data-binding operations Simply put, data binding is the act

of connecting control properties to data values that may change over the course of your tion’s lifetime By doing so, a user interface element can display the state of a variable in your code;for example:

applica-• Checking a CheckBox control based on a Boolean property of a given object

• Displaying data in TextBox types from a relational database table

• A Label connected to an integer representing the number of files in a folder

www.free-ebooks-download.org

Trang 12

When using the intrinsic WPF data-binding engine, you must be aware of the distinction

between the source and the destination of the binding operation As you might expect, the source of

a data-binding operation is the data itself (a Boolean property, relational data, etc.), while the

desti-nation (or target) is the UI control property that will use the data content (a CheckBox, TextBox, and

so on)

■ Note The target property of a data-binding operation must be a dependency property of the UI control

Truth be told, using the WPF data-binding infrastructure is always optional If a developer were

to roll his or her own data-binding logic, the connection between a source and destination typically

would involve handling various events and authoring procedural code to connect the source and

destination For example, if you had a ScrollBar on a window that needed to display its value on a

Label type, you might handle the ScrollBar’s ValueChange event and update the Label’s content

accordingly

However, using WPF data binding, you can connect the source and destination directly inXAML (or using C# code in your code file) without the need to handle various events or hard-code

the connections between the source/destination As well, based on how you set up your data-binding

logic, you can ensure that the source and destination stay in sync if either of their values change

A First Look at Data Binding

To begin examining WPF’s data-binding capabilities, assume you have a new WPF Application

proj-ect (named SimpleDataBinding) that defines the following markup for a Window type:

<Label Content="Move the scroll bar to see the current value"/>

<! The scrollbar's value is the source of this data bind >

<ScrollBar Orientation="Horizontal" Height="30" Name="mySB"

Maximum = "100" LargeChange="1" SmallChange="1"/>

<! The label's content value is the target of the data bind >

<Label Height="30" BorderBrush="Blue" BorderThickness="2"

Content = "{Binding ElementName=mySB, Path=Value}"

arrow), the Label will be automatically updated with the current value The “glue” that makes this

happen is the {Binding} markup extension that has been assigned to the Label’s Content property

Here, the ElementName value represents the source of the data-binding operation (the ScrollBar

object), while the Path value represents (in this case) the property of the element to obtain

www.free-ebooks-download.org

Trang 13

■ Note ElementNameand Pathmay seem oddly named, as you might expect to find more intuitive names such

as “Source” and “Destination.” However, as you will see later in this chapter, XML documents can be the source of

a data-binding operation (typically using XPath) In this case, the names ElementNameand Pathfit the bill

As an alternative format, it is possible to break out the values specified by the {Binding}markup extension by explicitly setting the DataContext property to the source of the binding opera-tion as follows:

<! Breaking object/value apart via DataContext >

<Label Height="30" BorderBrush="Blue" BorderThickness="2"

DataContext = "{Binding ElementName=mySB}"

Content = "{Binding Path=Value}"

/>

In either case, if you were to run this application, you would be pleased to find this Labelupdating without the need to write any procedural C# code (see Figure 29-30)

Figure 29-30.Binding the ScrollBar value to a Label

The DataContext Property

In the current example, you have seen two approaches to establish the source and destination of adata-binding operation, both of which resulted in the same output Given this point, you mightwonder when you would want to explicitly set the DataContext property This property can be veryhelpful in that it is a dependency property, and therefore its value can be inherited by subelements

In this way, you can easily set the same data source to a family of controls, rather than having torepeat a bunch of redundant "{Binding ElementName=X, Path=Y}" XAML values to multiple con-trols Consider the following updated XAML definition for our current <StackPanel>:

<! Note the StackPanel sets the DataContext property >

<StackPanel Width="250" DataContext = "{Binding ElementName=mySB}">

<Label Content="Move the scroll bar to see the current value"/>

<ScrollBar Orientation="Horizontal" Height="30" Name="mySB"

Maximum = "100" LargeChange="1" SmallChange="1"/>

<! Now both UI elements use the scrollbar's value in unique ways >

<Label Height="30" BorderBrush="Blue" BorderThickness="2"

Content = "{Binding Path=Value}"/>

<Button Content="Click" Height="200"

FontSize = "{Binding Path=Value}"/>

</StackPanel>

www.free-ebooks-download.org

Trang 14

Here, the DataContext property has been set on the <StackPanel> directly Therefore, as wemove the thumb, not only will we see the current value on the Label, but we will also find the font

size of the Button grow and shrink accordingly based on the same value Figure 29-31 shows one

possible output

Figure 29-31.Binding the ScrollBar value to a Label and a Button

The Mode Property

When establishing a data-binding operation, you are able to choose among various modes of

oper-ation by setting a value to the Mode property at the time you establish the Path value By default, the

Mode property is set to the value OneWay, which specifies that changes in the target do not affect the

source In our example, changing the Content property of the Label does not set the position of the

ScrollBar’s thumb

If you wish to keep changes between the source and the target in sync, you can set the Modeproperty to TwoWay Thus, changing the value of the Label’s content changes the value of the scroll-

bar’s thumb position Of course, the end user would be unable to change the content of the Label,

as the content is presented in a read-only manner (we could of course change the value

program-matically)

To illustrate the use of the TwoWay mode, assume we have replaced the Label displaying the rent scrollbar value with the following TextBox (note the value of the Text property) In this case,

cur-when you type a new value into the text area, the thumb position (and font of the Button type)

auto-matically update when you tab off the TextBox object:

<TextBox Height="30" BorderBrush="Blue"

BorderThickness="2" Text = "{Binding Path=Value}"/>

■ Note You may also set the Modeproperty to OneTime This option sets the target when initialized but does not

track further changes

www.free-ebooks-download.org

Trang 15

Data Conversion Using IValueConverter

The ScrollBar type uses a double to represent the value of the thumb, rather than an expectedwhole number (e.g., an integer) Therefore, as you drag the thumb, you will find various floating-point numbers displayed within the TextBox (such as 61.0576923076923), which would be ratherunintuitive to the end user, who is most likely expecting to see whole numbers (such as 61, 62, 63,and so on)

When you wish to convert the value of a data-binding operation into an alternative format, oneway to do so is to create a custom class type that implements the IValueConverter interface of theSystem.Windows.Data namespace This interface defines two members that allow you to perform theconversion to and from the target and destination Once you define this class, you can use it to fur-ther qualify the processing of your data-binding operation

■ Note While any data-binding operation can be achieved entirely using procedural code, the following exampleswill make use of XAML to convert between data types Doing so involves the use of custom resources, which will

be fully examined in Chapter 30 Therefore, don’t fret if some of the markup appears unfamiliar

Assuming that you wish to display whole numbers within the TextBox control, you could buildthe following class type (be sure you import the System.Windows.Data namespace in the definingfile):

class MyDoubleConverter : IValueConverter

{

public object Convert(object value, Type targetType, object parameter,

System.Globalization.CultureInfo culture){

// Convert the double to an int.

// Return the incoming value directly.

// This will be used for 2-way bindings.

// In our example, when the user tabs // off the TextBlock.

The ConvertBack() method will be called when the value is passed from the destination to thesource (if you have enabled a two-way binding mode) Here, we simply return the value straight-away By doing so, we are able to type a floating-point value into the TextBox (such as 99.9) and have

it automatically convert to a whole number value (99) when the user tabs off the control This “free”conversion happens due to the fact that the Convert() method is called once again after a call to

www.free-ebooks-download.org

Trang 16

ConvertBack() If you were to simply return null from ConvertBack(), your binding would appear to

be out of sync, as the text box would still be displaying a floating-point number!

With this class in place, consider the following XAML updates, which will leverage our customconverter class to display data in the TextBox:

<! Resource dictionaries allow us to define objects that can

be obtained by their key More details in Chapter 30 >

<Window.Resources>

<myConverters:MyDoubleConverter x:Key="DoubleConverter"/>

</Window.Resources>

<! The panel is setting the data context to the scrollbar object >

<StackPanel Width="250" DataContext = "{Binding ElementName=mySB}">

<Label Content="Move the scroll bar to see the current value"/>

<ScrollBar Orientation="Horizontal" Height="30" Name="mySB"

Maximum = "100" LargeChange="1" SmallChange="1"/>

<! Notice that the {Binding} extension now sets the Converter property >

<TextBox Height="30" BorderBrush="Blue" BorderThickness="2" Name="txtThumbValue"

Text = "{Binding Path=Value, Converter={StaticResource DoubleConverter}}"/>

<Button Content="Click" Height="200"

FontSize = "{Binding Path=Value}"/>

</StackPanel>

</Window>

Once we define a custom XML namespace that maps to our project’s root namespace (seeChapter 28), we add to the Window’s resource dictionary an instance of our MyDoubleConverter type,

which we can obtain later in the XAML file by the key name DoubleConverter The Text property of

the TextBox has been modified to make use of our MyDoubleConverter type, assigning the Converter

property to yet another markup extension named StaticResource Again, full details of the WPF

resource system can be found in Chapter 30 In any case, if you were to run your application, you

would find that only whole numbers will be displayed in the TextBox

Converting Between Diverse Data Types

An implementation of the IValueConverter interface can be used to convert between any data

types, even if they do not seem related on the surface In reality, you are able to use the current

value of the ScrollBar’s thumb to return any object type to connect to a dependency property

Con-sider the following ColorConverter type, which uses the value of the thumb to return a new green

SolidColorBrush (with a green value between 155 and 255):

www.free-ebooks-download.org

Trang 17

class MyColorConverter : IValueConverter

{

public object Convert(object value, Type targetType, object parameter,

System.Globalization.CultureInfo culture){

// Use value of thumb to build a varied green brush.

we could then use the key name to set the Background property of our Button type as follows:

<Button Content="Click" Height="200"

FontSize = "{Binding Path=Value}"

Background= "{Binding Path=Value, Converter={StaticResource ColorConverter}}"/>Sure enough, if you run your application once again, you’ll find the color of the Button changebased on the scrollbar’s position To wrap up our look at WPF data binding, let’s check out how tomap custom objects and XML document data to our UI layer

■ Source Code The SimpleDataBinding project can be found under the Chapter 29 subdirectory

Binding to Custom Objects

The next flavor of data binding we will examine is how to connect the properties of custom objects

to your UI layer Begin by creating a new WPF Application project named CarViewerApp and, usingthe steps outlined in Chapter 28, change the name of your initial Window1 type to MainWindow Next,handle the Loaded event of MainWindow, and update the <Grid> definition to contain two rows andtwo columns:

Trang 18

sub-that we are assigning an “input gesture” to the Exit menu to allow the item to be activated when the

user presses the Alt+F4 keystroke Finally, notice the value of Grid.ColumnSpan has been set to 2,

allowing the menu system to be positioned within each cell of the first row

the destination for a data-binding operation involving a collection of custom objects, so set the

ItemsSource property to the {Binding} markup extension (the source of the binding will be specified

in code in just a bit) As the user selects one of the items in the ListBox, we will capture the

SelectionChanged event in order to update the content within the TextBlock Here is the definition

of these remaining types:

<! Left pane of grid >

<ListBox Grid.Column="0"

Grid.Row="2" Name="allCars" SelectionChanged="ListItemSelected"

Background="LightBlue" ItemsSource="{Binding}">

</ListBox>

<! Right pane of grid >

<TextBlock Name="txtCarStats" Background="LightYellow"

Grid.Column="1" Grid.Row="2"/>

At this point, the UI of your window should look like what you see in Figure 29-32

Before we implement the data-binding logic, finalize the File ➤ Exit menu handler as follows:

private void ExitApplication(object sender, RoutedEventArgs e)

{

Application.Current.Shutdown();

}

www.free-ebooks-download.org

Trang 19

Figure 29-32.The UI of our main window

Working with the ObservableCollection<T> Type

.NET 3.0 introduced a new collection type within the System.Collections.ObjectModel namespacenamed ObservableCollection<T> The benefit of working with this type is that when its contents areupdated, it will send notifications to interested listeners, such as the destination of a data-bindingoperation Insert a new C# file into your application that defines a class named CarList that extendsObservableCollection<T>, where T is of type Car This iteration of the Car type makes use of C# auto-matic properties to establish some basic state data (which can be set using a custom constructor),and provides a fitting implementation of ToString():

// Add a few entries to the list.

Add(new Car(40, "BMW", "Black", "Sidd"));

Add(new Car(55, "VW", "Black", "Mary"));

Add(new Car(100, "Ford", "Tan", "Mel"));

Add(new Car(0, "Yugo", "Green", "Clunker"));

}}

public class Car

{

public int Speed { get; set; }public string Make { get; set; }public string Color { get; set; }public string PetName { get; set; }

www.free-ebooks-download.org

Trang 20

public Car(int speed, string make, string color, string name){

Speed = speed; Make = make; Color = color; PetName = name;

}public Car(){}

public override string ToString(){

return string.Format("{0} the {1} {2} is going {3} MPH",PetName, Color, Make, Speed);

}}

}

Now, open the code file for your MainWindow class and define a member variable of type CarListnamed myCars Within the Loaded event handler of your Window type, set the DataContext property

of the allCars ListBox to the myCars object (recall we did not set this value via XAML with the

{Binding} extension, therefore for a change of pace, we will do so using procedural code):

private void Window_Loaded(object sender, RoutedEventArgs e)

Figure 29-33.The initial data-binding operation

Creating a Custom Data Template

Currently, ListBox is displaying each item in the CarList object; however, because we have not

specified a binding path, each list entry is simply the result of calling ToString() on the subobjects

As we have already examined how to establish simple binding paths, this time we will construct a

custom data template Simply put, a data template can be used to inform the destination of a

data-binding operation how to display the data connected to it Our template will fill each item in the

ListBox with a <StackPanel> that consists of an Ellipse object and a TextBlock that has been bound

to the PetName property of each item in the CarList type Here is the modified markup of the

ListBox type

www.free-ebooks-download.org

Trang 21

<Ellipse Height="10" Width="10" Fill="Blue"/>

<TextBlock FontStyle="Italic" FontSize="14" Text="{Binding Path=PetName}"/>

ele-{

// Get correct car from the ObservableCollection based

// on the selected item in the list box Then call toString().

txtCarStats.Text = myCars[allCars.SelectedIndex].ToString();

}

With this update, you should now see a more stylized display of our data, as shown Figure 29-34

Figure 29-34.Data binding with a custom data template

Binding UI Elements to XML Documents

The next task is to build a custom dialog box that will use data binding to display the content of anexternal XML file within a stylized ListView object First, insert the Inventory.xml file you created in

Chapter 24 during the NavigationWithLinqToXml project using the Project ➤ Add Existing Item

menu option Select this item within the Solution Explorer, and using the Properties window, set theCopy to Output Directory option to Copy Always This will ensure that when you compile yourapplication, the Inventory.xml file will be copied to your \bin\Debug folder

www.free-ebooks-download.org

Trang 22

Building a Custom Dialog

Insert a new WPF Window type into your project (named AddNewCarDialog) using the Project ➤ Add

Window menu option of Visual Studio 2008 This new Window will display the content of the

Inventory.xml file within a customized ListView type, via data binding The first step is to author

the XAML to define the look and feel of this new window Here is the full markup, with analysis to

<ListView Name="lstCars" Grid.Row="0"ItemsSource=

"{Binding Source={StaticResource CarsXmlDoc}, XPath=/Inventory/Car}"

<Label Content="Select a Row to Add to your car collection" Margin="10" />

<Button Name="btnOK" Content="OK" Width="80" Height="25"

Margin="10" IsDefault="True" TabIndex="1" Click="btnOK_Click"/>

<Button Name="btnCancel" Content="Cancel" Width="80" Height="25"

Margin="10" IsCancel="True" TabIndex="2"/>

Trang 23

Beyond carving up our <Grid> into two rows of a given size, the next point of interest is that weare placing into the grid’s resource dictionary a new object of type XmlDataProvider This type can

be connected to an external *.xml file (or an XML data island within the XAML file) via the Sourceattribute As we have configured the Inventory.xml file to be located within the application direc-tory of our current project, we have no need to worry about hard-coding a fixed path

The real bulk of this markup takes place within the definition of the ListView type First of all,notice that the ItemsSource attribute has been assigned to the CarsXmlDoc resource, which is quali-fied using the XPath attribute Based on your experience, you may know that XPath is an XMLtechnology that allows you to navigate within an XML document using a query-like syntax Here weare saying that our initial data-binding path begins with the <Car> element of the <Inventory> root

To inform the ListView type to display a grid-like front end, we next make use of the

<ListView.View> element to define a <GridView> consisting of four <GridViewColumns> Each of thesetypes specifies a Header value (for display purposes) and most importantly a DisplayMemberBindingdata-binding value Given that the <ListView> itself has already specified the initial path within theXML document to be the <Car> subelement of <Inventory>, each of the XPath bindings for the col-umn types use this as a starting point

The first <GridViewColumn> is displaying the ID attribute of the <Car> element using an specific syntax for plucking our attribute values (@caID) The remaining columns simply furtherqualify the path within the XML document by appending the next subelement using the XPath qual-ifier of the {Binding} markup extension

XPath-Last but not least, the final row of the <Grid> contains a <WrapPanel> that contains two Buttons(and a descriptive Label) to complete the UI The only points of interest here would be that we arehandling the Click event of the OK button and the use of the IsDefault and IsCancel properties.These establish which button on a window should respond to the Click event when the Enter key orEsc key is pressed

Finally, note that these Button types specify a TabIndex value and a Margin value, the latter ofwhich allows you to define spacing around each item in the <WrapPanel>

Assigning the DialogResult Value

Before we display this new dialog box, we need to implement the Click handler for the OK button.Similar to Windows Forms (see Chapter 27), WPF dialog boxes can inform the caller which button

has been clicked via the DialogResult property However, unlike the DialogResult property found in

Windows Forms, in the WPF model, this property operates on a nullable Boolean value, rather than

a strongly typed enumeration Thus, if you wish to inform the caller the user wishes to employ thedata in the dialog box for use within the program (typically indicated by clicking an OK, a Yes, or anAccept button), set the inherited DialogResult property to true in the Click handler of said button:private void btnOK_Click(object sender, RoutedEventArgs e)

Obtaining the Current Selection

Finally, add a custom read-only property to your AddNewCarDialog named SelectedCar, whichreturns a new Car object to the caller based on the values of the selected row of the grid:

public Car SelectedCar

{

get

www.free-ebooks-download.org

Trang 24

// Cast selected item on grid to an XmlElement.

System.Xml.XmlElement carRow = (System.Xml.XmlElement)lstCars.SelectedItem;

// Make sure the user selected something!

if (carRow == null){

return null;

}else{

// Generate a random speed.

Random r = new Random();

int speed = r.Next(100);

// Return new Car based on the data in selected XmlElement/speed.

return new Car(speed, carRow["Make"].InnerText,carRow["Color"].InnerText, carRow["PetName"].InnerText);

}}

}

Notice we cast the return value of the SelectedItem property (which is of type System.Object)into an XmlElement type This is possible because our ListView is indeed connected to the

Inventory.xml file via our data-binding operation Once we nab the current XmlElement, we are able

to access the Make, Color, and PetName elements (using the type indexer) and extract out the values

by calling InnerText

■ Note If you have never worked with the types of the System.Xmlnamespace, simply know that the

InnerTextproperty obtains the value between the opening and closing elements of an XML node For example,

the inner text of <Make>Ford</Make>would be Ford

Displaying a Custom Dialog Box

Now that our dialog box is complete, we are able to launch it from the Click handler of the File ➤

Add New Car menu option:

private void AddNewCarWizard(object sender, RoutedEventArgs e)

myCars.Add(dlg.SelectedCar);

}}

}

Like Windows Forms, a WPF dialog box may be shown as a modal dialog box (by callingShowDialog()) or as a modaless dialog (by calling Show()) If the return value of ShowDialog() is true,

we ask the dialog box for the new Car object and add it to our ObservableCollection<T> Because

this collection type sends out notifications when its contents are altered, you will find your ListBox

www.free-ebooks-download.org

Trang 25

will automatically refresh itself as you insert new items Figure 29-35 shows the UI of our customdialog box.

Figure 29-35.A custom grid of data, bound to an XML document

■ Source Code The CarViewerApp project can be found under the Chapter 29 subdirectory

That wraps up our look at the WPF data-binding engine and the core controls found within this

UI API In the next chapter, you will complete your investigation of Windows Presentation tion by examining the role of graphical rendering, resource management, and the construction ofcustom themes

Founda-Summary

This chapter examined several aspects of WPF controls, beginning with a discussion of dependencyproperties and routed events These WPF mechanisms are very important for several aspects of WPFprogramming including data binding, animation services, and a slew of other features Over thecourse of this chapter, you have had a chance to configure and tweak several controls and learned

to arrange your UI content in various panel types

More importantly, you examined the use of WPF commands Recall that these control-agnosticevents can be attached to a UI element or an input gesture to automatically inherit out-of-the-boxservices (such as clipboard operations) You also dove into the mechanics of the WPF data-bindingengine and learned how to bind property values, custom objects, and XML documents to your UIlayer At this time, you also learned how to build WPF dialog boxes and discovered the role of theIValueConverter and ObservableCollection<T> types

www.free-ebooks-download.org

Trang 26

WPF 2D Graphical Rendering,

Resources, and Themes

The purpose of this chapter is to examine three ultimately independent, yet interrelated topics,

which will enable you to build more sophisticated Windows Presentation Foundation (WPF)

appli-cations than shown in the previous two chapters The first order of business is to investigate the

WPF 2D graphical programming APIs Here you will examine numerous ways to render 2D

geomet-ric images (via shapes, drawings, and visuals) and work with graphical primitives such as brushes

and pens Along the way, you will also be introduced to the topic of WPF animation services and the

types of the System.Windows.Media.Animation namespace

Once you understand the basic 2D graphical rendering/animation primitives of WPF, we willthen move on to an examination of how WPF allows you to define, embed, and reference applica-

tion resources Generally speaking, the term “application resources” refers to string tables, image

files, icons, and other non-code-based entities used by an application While this is still true under

WPF, a “resource” can also represent custom graphical and UI objects you wish to embed into an

assembly for later use

The final topic of this chapter closes the gap between these two seemingly unrelated topics byexamining how to define styles and templates for your control types As you will see, creating styles

and templates almost always involves making extensive use of WPF’s graphical rendering/animation

services and packaging them into your assembly as application resources

■ Note You may recall from Chapter 28 that WPF provides comprehensive support for 3D graphics programming,

which is beyond the scope of this text If you require details regarding this aspect of WPF, check out Pro WPF in C#

2008: Windows Presentation Foundation with NET 3.5, Second Edition by Matthew MacDonald (Apress, 2008).

The Philosophy of WPF Graphical Rendering

Services

WPF makes use of a particular flavor of graphical rendering that goes by the term retained mode

graphics Simply put, this means that as you are using XAML or procedural code to generate

graphi-cal renderings, it is the responsibility of WPF to persist these visual items and ensure they are

correctly redrawn and refreshed in an optimal manner Thus, when you render graphical data, it is

always present regardless of whether the end user hides the image by resizing the window,

minimiz-ing the window, coverminimiz-ing the window with another, and so forth

In stark contrast, previous Microsoft graphical rendering APIs (including GDI+) were ate mode graphics systems Under this model, it is up to the programmer to ensure that rendered 1117

immedi-C H A P T E R 3 0

www.free-ebooks-download.org

Trang 27

visuals are correctly “remembered” and updated during the life of the application For example,recall from Chapter 27 that under GDI+, rendering a rectangle involves handling the Paint event (oroverriding the virtual OnPaint() method), obtaining a Graphics object to draw the rectangle and,most important, adding the infrastructure to ensure that the image is persisted when the userresizes the window (e.g., create member variables to represent the position of the rectangle, callInvalidate() throughout your program, etc.) This conceptual shift from immediate mode toretained mode graphics is indeed a good thing, as programmers have much less grungy graphicscode to author and maintain

However, this point is not to suggest that the WPF graphics API is completely different from

ear-lier rendering toolkits For example, like GDI+, WPF supports various brush types and pen types, theability to render graphics at runtime through code, techniques for hit-testing support, and so forth

So to this end, if you currently have a background in GDI+ (or C/C++-based GDI), you already know

a good deal about how to perform basic renderings under WPF

WPF Graphical Rendering Options

Like other aspects of WPF development, you have a number of choices regarding how you will

per-form your graphical rendering, above and beyond the decision to do so via XAML or procedural C#code Specifically, WPF provides three distinct ways to render graphical data:

• System.Windows.Shapes: This namespace defines a number of types used to render 2D metric objects (rectangles, ellipses, polygons, etc.) While these types are very simple to use,they do come with a good deal of overhead

geo-• System.Windows.Media.Drawing: This abstract base class defines a more lightweight set ofservices for a number of derived types (GeometryDrawing, ImageDrawing, etc.)

• System.Windows.Media.Visual: This abstract base class provides the most lightweightapproach to render graphical data; however, it also requires authoring a fair amount ofprocedural code

The motivation behind offering a number of different ways to do the exact same thing (e.g.,render graphical data) has to do with memory usage and ultimately application performance Giventhat WPF is such a graphically intensive system, it is not unreasonable for an application to renderhundreds of different images upon a window’s surface, and your choice of implementation (shapes,drawings, or visuals) could have a huge impact

To set the stage for the sections to come, let’s begin with a brief overview of each option, fromthe “heaviest” to the “lightest.” If you wish to try out each option firsthand, create a new WPF Win-dows Application named WPFGraphicsOptions, change the name of your initial Window type toMainWindow, and replace the window’s initial XAML <Grid> definition with a <StackPanel> type:

Use of the Shape-Derived Types

The members of the System.Windows.Shapes namespace (Ellipse, Line, Path, Polygon, Polyline, andRectangle) are the absolute simplest way to render a 2D image and are appropriate when you need

www.free-ebooks-download.org

Trang 28

to render infrequently used images (such as a region of a stylized button) or you need a graphical

image that can support user interaction Using these types typically entails selecting a “brush” for

the interior fill and a “pen” for the border outline (you’ll examine WPF’s brush and pen options

later in this chapter) To illustrate the basic use of shape types, if you add the following to your

<StackPanel>, you will find a simple light blue rectangle with a blue outline:

<! Draw a rectangle using Shape types >

<Rectangle Height="55" Width="105" Stroke="Blue"

StrokeThickness="5" Fill="LightBlue"/>

While these types are ridiculously simple to use, they are a bit on the bloated side due to thefact that their parent class, Sysem.Windows.Shapes.Shape, attempts to be all things to all people (if

you will) Shape inherits a ton of services from its long list of parents in the inheritance chain: Shape

is-a FrameworkElement, which is-a UIElement, which is-a Visual, which is-a DependencyObject,

DispatcherObject, and Object!

Collectively, these base classes provide the derived types with support for styles and temples,data binding support, resource management, the ability to send numerous events, the ability to

monitor keyboard and mouse input, complex layout management, and animation services Figure

30-1 illustrates the inheritance of the Shape type, as seen through the eyes of the Visual Studio objectbrowser

Figure 30-1 Shape-derived types gain a ton of functionality from their parent types.

While each parent adds various bits of functionality, UIElement is a key culprit For example,

UIElement defines over 80 events to handle numerous forms of input (mouse, keyboard, and stylus

for Tablet PCs) FrameworkElement is the other suspect, in that this type provides members for

changing the mouse cursor, various events representing object lifetime, context menus support,

and so on Given this, the Shape-derived types can be as interactive as other UI elements such as

Buttons, ProgressBars, and other widgets

The bottom line is that while the Shape-derived types are very simple to use and quite powerful,this very fact makes them the heaviest option for rendering 2D graphics Again, using these types is

just fine for “occasional rendering” (the definition of which can be more of a gut feel than a hard

sci-ence) or when you honestly do need a graphical rendering that is responsive to user interaction

However, if you are designing a more graphically intensive application, you will likely find some

performance gains by using the Drawing-derived types

Use of the Drawing-Derived Types

The System.Windows.Media.Drawing abstract base class represents the bare bones of a

two-dimensional surface The derived types (such as GeometryDrawing, ImageDrawing, and VideoDrawing)

are more lightweight than the Shape-derived types in that neither UIElement nor FrameworkElement is

www.free-ebooks-download.org

Trang 29

in the inheritance chain Given this, Drawing-derived types do not have intrinsic support for dling input events (although it is possible to programmatically perform hit-testing logic); however,they can be animated due to the fact that Animatable is in the family (see Figure 30-2).

han-Figure 30-2 Drawing-derived types are significantly more lightweight than Shape-derived types.

Another key difference between Drawing-derived types and Shape-derived types is that Drawing-derived types have no ability to render themselves, as they do not derive from UIElement!Rather, derived types must be placed within a hosting object (such as DrawingImage, DrawingBrush,

or DrawingVisual) to display their content This decoupling of composition from display makes theDrawing-derived types much more lightweight than the Shape-derived types, while still retainingkey services

Without getting too hung up on the details for the time being, consider how the previousRectangle could be rendered using the drawing-centric types (add this markup directly after yourprevious <Rectangle> if you are following along):

<! Draw a rectangle using Drawing types >

<Image Height="55" Width="105">

Use of the Visual-Derived Types

The abstract System.Windows.Media.Visual class type provides a minimal and complete set of ices to render a derived type (rendering, hit testing, transformation), but it does not provide supportfor addition nonvisual services, which can lead to code bloat (input events, layout services, styles,and data binding) Given this, the Visual-derived types (DrawingVisual, Viewport3DVisual, and

serv-www.free-ebooks-download.org

Trang 30

ContainerVisual) are the most lightweight of all graphical rendering options and offer the best

per-formance Notice the simple inheritance chain of the Visual type, as shown in Figure 30-3

Figure 30-3.The Visual type provides basic hit testing, coordinate transformation, and bounding box

calculations.

Because the Visual type exposes the lowest level of functionality, it has limited support for

direct XAML definitions (unless you use a Visual within a type that can be expressed in XAML).

Using these types feels a bit closer to making use of GDI/GDI+ rendering APIs, in that they are often

manipulated through procedural code When doing so, you are manually populating the object

graph representing your window with custom Visual-derived types Furthermore, you are required

to override various virtual methods that will be called by the WPF graphics system to figure out how

many items you are rendering, and the Visual item itself to be rendered

To illustrate how you can use the Visual-derived types to render 2D data, open the code file foryour main window type and comment out the entire definition (so you can restore it shortly, with

will be ignored and not displayed):

public partial class MainWindow : System.Windows.Window

{

// Our single drawing visual

private DrawingVisual rectVisual = new DrawingVisual();

private const int NumberOfVisualItems = 1;

Trang 31

Rect rect = new Rect(50, 50, 105, 55);

drawCtx.DrawRectangle(Brushes.AliceBlue, new Pen(Brushes.Blue, 5), rect);

// Necessary overrides The WPF graphics system

// will call these to figure out how many items to

// render and what to render

protected override int VisualChildrenCount

Last but not least, you are required to override the virtual VisualChildrenCount read-only erty and GetVisualChild() method These members are called by the WPF graphics engine todetermine exactly what to render (a single DrawingVisual in this example)

prop-As you can see, as soon as you move into the realm of working with Visual-derived types, youare knee-deep in procedural code and therefore have a great deal of control and power (and theassociated complexity that follows)

Building a Custom Visual Rendering Agent

Your current custom Visual rendering operation was set up in such a way that the window’s content(e.g., the <StackPanel>) was blown away and therefore not rendered, in favor of the hard-codedDrawingVisual Just to dig a bit deeper into the Visual programming layer, what if you wished to useXAML descriptions for a majority of your window’s rendering and dip into the Visual layer for just asmall portion of the overall UI?

One approach to do so is to define a custom class deriving from FrameworkElement and overridethe virtual OnRender() method This method (which is in fact what the Shape-derived types use torender their output) can contain the same sort of code found in our previous CreateRectVisual()helper method Once you have defined this custom class, you can then refer to your custom classtype from within a window’s XAML description

www.free-ebooks-download.org

Trang 32

To illustrate, to your current project add a new class named MyCustomRenderer, which extendsthe FrameworkElement base class (be sure to import the System.Windows and System.Windows.Media

namespaces into your new file) Now, implement your type as so:

public class MyCustomRenderer : FrameworkElement

{

// Default size for our rectangle

double rectWidth = 105, rectHeight = 55;

// Allow user to override the defaults.

public double RectHeight

{

set { rectHeight = value; }get { return rectHeight; }}

public double RectWidth

{

set { rectWidth = value; }get { return rectWidth; }}

protected override void OnRender(DrawingContext drawCtx)

{

// Do parent rendering first.

base.OnRender(drawCtx);

// Add our custom rendering.

Rect rect = new Rect();

which, while not necessary, allow the creator to define the overall size of the image

Gaining access to this type in XAML is simply a matter of defining a custom XML namespacethat maps to the name of our type and using this prefix to create our type Here are the relevant

updates to the <StackPanel> type (recall from Chapter 28 that XML namespaces that map to your

own NET namespaces must be defined using the clr-namespace token):

<StackPanel xmlns:custom = "clr-namespace:WPFGraphicsOptions">

Trang 33

Figure 30-4.Three rectangles, three approaches

Picking Your Poison

At this point you have seen three different approaches to interacting with the WPF 2D graphical dering services (shapes, drawings, and visuals) By and large, the need to render graphics using theVisual-derived types is only necessary if you are building custom controls, or you need a great deal

ren-of control over the rendering process This is a good thing, as working with Visual and friendsentails a healthy amount of effort compared to simple XAML descriptions Given this, I will notdive into the Visual rending APIs beyond this point in the chapter (do feel free to consult the NETFramework 3.5 SDK documentation for further details if you are interested)

Using the Drawing-derived types is a perfect middle-of-the-road approach, as these types stillsupport core non-UI services (such as hit testing, etc.) at a lower cost than the Shape types Whilethis approach does entail more markup than required by the Shape types, you end up with an appli-cation using less overhead We will examine more details of the Drawing-derived types a bit later inthis chapter

That being said, however, the Shape types are still a perfectly valid approach when you need torender a handful of 2D images within a given window Recall that if you truly do need 2D shapesthat are just about as capable as traditional UI elements, the Shape types are a perfect choice, giventhat the required infrastructure is already in place

■ Note Always remember that your choice of rendering services can affect your application’s performance.Thankfully, we are provided with a collection of various WPF profiling utilities to monitor your current application.Look up the topic “Performance Profiling Tools for WPF” within the NET Framework 3.5 SDK documentation forfurther details

Exploring the Shape-Derived Types

To continue exploring 2D graphical rendering, let’s start by digging into the details of the members

of the System.Windows.Shapes namespace Recall these types provide the most straightforward,yet most bloated, way to render a two-dimensional image This namespace (defined in the

PresentationFramework.dll assembly) is quite small and consists of only six sealed types thatextend the abstract Shape base class: Ellipse, Line, Path, Polygon, Polyline, and Rectangle

www.free-ebooks-download.org

Trang 34

Because the Shape base class is-a FrameworkElement, you are able to assign derived types ascontent using XAML or procedural C# code without the additional complexities of working with

<! A window with a circle as content >

<Ellipse Height = "100" Width = "100" Fill = "Black" />

</Window>

Like any other UI element, if you are building a window that requires multiple containedwidgets, you will need to define your 2D types within a panel type, as described in Chapter 29

The Functionality of the Shape Base Class

While most of Shape’s functionality comes from its long list of parent classes, this type does define

some specific properties (most of which are dependency properties) that are common to the child

types, some of the more interesting of which are shown in Table 30-1

Table 30-1.Key Properties of the Shape Base Class

Fill Allows you to specify a brush type to render the interior part of a

derived type

GeometryTransform Allows you to apply a transformation to the rendering of the derived

type

Stretch Describes how to fill a shape within its allocated space This is

controlled using the corresponding System.Windows.Media.Stretchenumeration

Stroke, StrokeDashArray, These (and other) stroke-centric properties control how lines are

StrokeEndLineCap, configured when drawing the border of a shape

StrokeThickness

Also recall that Shape-derived types have support for hit testing, themes and styles, tool tips,and numerous services

Working with Rectangles, Ellipses, and Lines

To check out some of the derived types firsthand, create a new Visual Studio WPF Windows

Applica-tion named FunWithSystemWindowsShapes Declaring Rectangle, Ellipse, and Line types in XAML

is quite straightforward and requires little comment One interesting feature of the Rectangle type is

that it defines RadiusX and RadiusY properties to allow you to render curved corners if you require

Line represents its starting and end points using the X1, X2, Y1, and Y2 properties (given that “height”and “width” make little sense when describing a line) Without belaboring the point, consider the

following <StackPanel>:

<StackPanel>

<! A line that monitors the mouse entering its area >

<Line Name ="SimpleLine" X1 ="0" X2 ="50" Y1 ="0" Y2 ="50"

Stroke ="DarkOliveGreen" StrokeThickness ="5"

ToolTip ="This is a line!" MouseEnter ="SimpleLine_MouseEnter"/>

www.free-ebooks-download.org

Trang 35

<! A rectangle with curved corners >

<Rectangle RadiusX ="20" RadiusY ="50"

Fill ="DarkBlue" Width ="150" Height ="50"/>

Working with Polylines, Polygons, and Paths

The Polyline type allows you define a collection of (x, y) coordinates (via the Points property) to

draw a series of connected line segments that do not require connecting ends The Polygon type issimilar; however, it is programmed in such a way that it will always close the starting and endingpoints Consider the following additions to the current <StackPanel>:

<! Polyline types do not have connecting ends >

<Polyline Stroke ="Red" StrokeThickness ="20" StrokeLineJoin ="Round"

Points ="10,10 40,40 10,90 300,50"/>

<! A Polygon always closes the end points >

<Polygon Fill ="AliceBlue" StrokeThickness ="5" Stroke ="Green"

Points ="40,10 70,80 10,50" />

Figure 30-5 shows the rendered output for each of these Shape-derived types

Figure 30-5.Rendered Shape-derived types

The final type, Path (not examined here), can be considered the superset of Rectangle, Ellipse,Polyline, and Polygon in that Path can render any of these types In fact, all 2D types could be ren-dered using nothing but Path (however, doing so would require additional work)

www.free-ebooks-download.org

Trang 36

■ Source Code The FunWithSystemWindowsShapes project can be found under the Chapter 30 subdirectory.

Working with WPF Brushes

Each of the WPF graphical rendering options (shape types, drawing types, and visual types) makes

extensive use of brushes, which allow you to control how the interior of a 2D surface is filled WPF

provides six different brush types, all of which extend System.Windows.Media.Brush While Brush is

abstract, the descendents described in Table 30-2 can be used to fill a region with just about any

conceivable option

Table 30-2.WPF Brush-Derived Types

DrawingBrush Paints an area with a Drawing-derived object (GeometryDrawing,

ImageDrawing, or VideoDrawing)ImageBrush Paints an area with an image (represented by an ImageSource object)

LinearGradientBrush A brush used to paint an area with a linear gradient

RadialGradientBrush A brush used to paint an area with a radial gradient

SolidColorBrush A brush consisting of a single color, set with the Color property

VisualBrush Paints an area with a Visual-derived object (DrawingVisual,

Viewport3DVisual, and ContainerVisual)

The DrawingBrush and VisualBrush types allow you to build a brush based on the Drawing- orVisual-derived types examined at the beginning of this chapter The remaining brush types are

quite straightforward to make use of and are very close in functionality to similar types found

within GDI+ The following sections present a quick overview of SolidColorBrush,

LinearGradientBrush, RadialGradientBrush, and ImageBrush

■ Note Given that these examples will not respond to any events, you can enter each of the following examples

directly into the custom XAML viewer you created in Chapter 28, rather than creating new Visual Studio 2008 WPF

Application project workspaces

Building Brushes with Solid Colors

The SolidColorBrush type provides the Color property to establish a solid colored brush type The

Color property takes a System.Windows.Media.Color type, which contains various properties (such

as A, R, G, and B) to establish the color itself While the capability to have solid colors is useful,

ironi-cally you typiironi-cally will not need to directly create a SolidColorBrush explicitly, given that XAML

supports a type converter that maps known color names (e.g., "Blue") to a SolidColorBrush object

behind the scenes Consider the following approaches to fill an Ellipse with a solid color:

<StackPanel>

<! Solid brush using type converter >

<Ellipse Fill ="DarkRed" Height ="50" Width ="50"/>

www.free-ebooks-download.org

Trang 37

<! Using the SolidColorBrush type >

<Ellipse Height ="50" Width ="50">

<Ellipse.Fill>

<SolidColorBrush Color ="DarkGoldenrod"/>

</Ellipse.Fill>

</Ellipse>

<! Using the SolidColorBrush and Color type >

<Ellipse Height ="50" Width ="50">

■ Note The WPF graphics API provides a helper class named Brushes, which defines properties for dozens ofpredefined colors This is very useful when you need a solid colored brush in procedural code

Working with Gradient Brushes

The two gradient brush types (LinearGradientBrush and RadialGradientBrush) allow you to fill

an area by transitioning between two (or more) colors The distinction is that while a

LinearGradientBrush always transitions between colors using a straight line (which could, ofcourse, be rotated into any position using a graphical transformation or by setting the starting andending point), a RadialGradientBrush transitions from a specified starting point outward within anelliptical boundary Consider the following:

<! A rectangle with a linear fill >

<Rectangle RadiusX ="15" RadiusY ="15" Height ="40" Width ="100">

<Rectangle.Fill>

<LinearGradientBrush StartPoint="0,0.5" EndPoint="1,0.5">

<GradientStop Color="LimeGreen" Offset="0.0" />

<GradientStop Color="Orange" Offset="0.25" />

<GradientStop Color="Yellow" Offset="0.75" />

<GradientStop Color="Blue" Offset="1.0" />

</LinearGradientBrush>

</Rectangle.Fill>

</Rectangle>

<! An ellipse with a radial fill >

<Ellipse Height ="75" Width ="75">

<Ellipse.Fill>

<RadialGradientBrush GradientOrigin="0.5,0.5"

Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5">

www.free-ebooks-download.org

Trang 38

<GradientStop Color="Yellow" Offset="0" />

<GradientStop Color="Red" Offset="0.25" />

<GradientStop Color="Blue" Offset="0.75" />

<GradientStop Color="LimeGreen" Offset="1" />

will peak to blend with the previous color

The ImageBrush Type

The final brush type we will examine here is ImageBrush, which as the name suggests allows you to

load an external image file (or better yet, to load an embedded image resource) as the basis of a

brush type To assign an external file to an ImageBrush, one approach requires you to set the

ImageSource property to a valid BitmapImage object Consider the following simple definition, which

assumes you have a *.jpg file, Gooseberry0007.JPG, located in the same location as this *.xaml file:

<! A large rectangle built using an image brush >

<Rectangle Height ="100" Width ="300">

Figure 30-6 shows each of our brush types in action

Figure 30-6.Numerous brushes at work

■ Source Code The FunWithBrushes.xamlfile can be found under the Chapter 30 subdirectory

www.free-ebooks-download.org

Trang 39

Working with WPF Pens

In comparison to brushes, the topic of pens is trivial, as the Pen type is really nothing more than aBrush in disguise Specifically, Pen represents a brush type that has a specified thickness, repre-sented by a double value Given this point, you could create a Pen that has a Thickness propertyvalue so large that it appears to be, in fact, a brush! However, in most cases you will build a Pen ofmore modest thickness to represent how to render the outline of a 2D image

In many cases, you will not directly need to create a Pen type, as this will be done indirectlywhen you assign a value to properties such as StrokeThickness However, building a custom Pentype is very handy when working with Drawing-derived types (described next) Before we see a cus-tomized pen doing something useful, consider the following example:

<Pen Thickness="10" LineJoin="Round" EndLineCap="Triangle" StartLineCap="Round" />This particular Pen type has set the LineJoin property, which controls how to render the con-nection point between two lines (e.g., the corners) EndLineCap, as the name suggests, controls how

to render the endpoint of a line stroke (a triangle in this case), while StartLineCap controls the samesetting at the line’s point of origin

A Pen can also be configured to make use of a dash style, which affects how the pen draws the

line itself The default setting is to use a solid color (as dictated by a given brush); however, theDashStyle property may be assigned to any custom DashStyle object While creating a customDashStyle object gives you complete control over how a Pen should render its data, the DashStyleshelper class defines a number of static members that provide common default styles Because these

are static members of a class rather than values from an enumeration, we must make use of the

XAML {x:Static} markup extension:

<Pen Thickness="10" LineJoin="Round" EndLineCap="Triangle"

StartLineCap="Round" DashStyle = "{x:Static DashStyles.DashDotDot}" />

Now that you have a better idea of the Pen type, let’s make use of some of them within variousDrawing-derived types

Exploring the Drawing-Derived Types

Recall that while the Shape types allow you to generate any sort of interactive two-dimensional face, they entail quite a bit of overhead due to their rich inheritance chain As an alternative, WPFprovides a sophisticated drawing and geometry programming interface, which renders more light-weight 2D images The entry point into this API is the abstract System.Windows.Media.Drawing class,which on its own does little more than define a bounding rectangle to hold the rendering WPF pro-vides five types that extend Drawing, each of which represents a particular flavor of drawing content,

sur-as described in Table 30-3

Table 30-3.WPF Drawing-Derived Types

DrawingGroup Used to combine a collection of separate Drawing-derived types into a single

composite rendering

GeometryDrawing Used to render 2D shapes

GlyphRunDrawing Used to render textual data using WPF graphical rendering services

ImageDrawing Used to render an image file into a bounding rectangle

VideoDrawing Used to play (not “draw”) an audio file or video file This type can only be fully

exploited using procedural code If you wish to play videos via XAML, theMediaPlayer type is a better choice

www.free-ebooks-download.org

Trang 40

While each of these types is useful in its own right, GeometryDrawing is the type of interest whenyou wish to render 2D images, and it is the one we will focus on during this section In a nutshell,

the GeometryDrawing type represents a geometric type detailing the structure of the 2D image, a

Brush-derived type to fill its interior, and a Pen to draw its border

The Role of Geometry Types

The geometric structure described by a GeometryDrawing type is actually one of the WPF

geometry-centric class types, or possibly a collection of geometry-geometry-centric types that work together as a single

unit Each of these geometries can be expressed in XAML or via C# code All of the geometries

derive from the System.Windows.Media.Geometry base class, which defines a number of useful

members common to all derived types, some of which appear in Table 30-4

Table 30-4.Select Members of the System.Windows.Media.Geometry Type

Bounds A property used to establish the current bounding rectangle

FillContains() Allows you to determine if a given Point (or other Geometry type) is within

the bounds of a particular Geometry-derived type Obviously, this is usefulfor hit-testing calculations

GetArea() Returns a double that represents the entire area a Geometry-derived type

occupies

GetRenderBounds() Returns a Rect that contains the smallest possible rectangle that could be

used to render the Geometry-derived type

Transform Allows you to assign a Transform object to the geometry to alter the

rendering

WPF provides a good number of Geometry-derived types out of the box, and these can begrouped into two simple categories: basic shapes and paths The first batch of geometric types,

RectangleGeometry, EllipseGeometry, LineGeometry, and PathGeometry, are used to render basic

shapes As luck would have it, these four types mimic the functionality of the System.Windows

Media.Shapes types you have already examined (and in many cases they have identical members)

For many of your rendering operations, the basic shape types will do just fine Do be aware,however, that if you require more exotic geometries, WPF supplies numerous auxiliary types that

work in conjunction with the PathGeometry type In a nutshell, PathGeometry maintains a collection

of “path segments,” which can be any of the following: ArchSegment, BezierSegment, LineSegment,

PolyBezierSegment, PolyLineSegment, PolyQuadraticBezierSegment, and QuadraticBezierSegment

Dissecting a Simple Drawing Geometry

Let’s take a closer look at the <GeometryDrawing> type created at the beginning of this chapter:

<GeometryDrawing Brush ="LightBlue">

Ngày đăng: 12/08/2014, 23:20

TỪ KHÓA LIÊN QUAN