Tải bản đầy đủ - 0 (trang)
2 ProcessMonitor: A simple binding example

2 ProcessMonitor: A simple binding example

Tải bản đầy đủ - 0trang

ProcessMonitor: A simple binding example



213



Figure 11.2 ProcessMonitor uses

data binding to tie a list of processes to

a ListView.



has a static method, GetProcesses, on it that gives back an array of Processes.

Sounds perfect.

BINDING A LIST SOURCE TO A LIST TARGET



Because we have a list of data for the source, we’ll also want a list control to show it.

We drag a ListView over from the Toolbox, remove the automatically created Margin

attribute, and split the tag because we’re going to be doing some XAML surgery soon.







Now we need access to that Process class from the XAML. This class is available in the

System.Diagnostics namespace of the System assembly, so we add the assembly via

the XML namespace using statement xmlns:diag="clr-namespace:System.Diagnostics;assembly=System." The Window tag should look like this:


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

xmlns:diag="clr-namespace:System.Diagnostics;assembly=System"

Title="Monitor" Height="400" Width="400">



WPF provides two classes to help bind data: ObjectDataProvider and XmlDataProvider.

These classes are derivations of DataSourceProvider. This is the class that we’d extend

if we want to expose some custom form of data that isn’t supported in the way we want.

We’ll try the XmlDataProvider a little later, but for this task, we need the ObjectDataProvider. These classes are adapters that WPF uses to bind to particular types of data,

and are designed so that they can be described declaratively in XAML. They also allow

asynchronous binding so that the UI can operate while data is being loaded.



214



CHAPTER 11



Data binding with WPF



Setting up an ObjectDataProvider is easy. We give it enough information to get to

the data we need, and declare it as a resource somewhere in the XAML. In this case,

we’ll create the resource in the Grid element (listing 11.1).

Listing 11.1 Binding to the process list of the Process object



b






MethodName="GetProcesses"

ObjectType="{x:Type diag:Process}"/>





c



d



We use the key processes b so that we can reference the resource later. Because the

goal is to bind from the array returned from a method on that object, not the object

itself, we need another attribute to specify the MethodName to call—in this case, GetProcesses c. If we wanted to bind to a static property instead of a static method, we

wouldn’t have specified a MethodName, but the name of the property we wanted later

on when doing the binding.1

Finally, we need to use a markup extension d to specify the Process class type, as

opposed to an instance of a class. The x:Type indicates that we’re passing a type,

and diag:Process specifies the Process class. This is equivalent to the C# code

typeof(Process).

Now that we’ve created our ObjectDataProvider, the next step is to bind to it.

SETTING UP BINDINGS



There are slight differences in the way you bind for different controls—primarily

related to the way in which the data will be used. For a TextBlock, we’d most likely

bind to the Text property. For the ListView, the property to specify the source of the

items is called ItemsSource because it’s the source for the list of items. Because we

declared our resource in XAML, we can add an ItemsSource attribute like this:


ItemsSource="{Binding Source={StaticResource processes}}">



We set the ItemSource property using the XAML’s special binding notation. We’ll go

into the binding notation in a lot more detail later, but we’ll talk you through what

we’re doing right here. The curly-brace notation indicates that some form of reference

is going on. {Binding} means that we’re doing data binding. This will create, behind

the scenes, an object of type Binding. For the Binding to be useful, it needs to have

some properties set, and the particular property we’re setting is the Source property.

Fortunately, the curly-brace notation allows nesting because we want to set the

value of the Source property to reference the processes resource we created earlier.

This uses the StaticResource reference that we’ve been seeing for a while now.



1



In fact, as you’ve seen in previous chapters, we can bind to a Static property without using an ObjectDataProvider at all, using the {Static class.property} notation.



ProcessMonitor: A simple binding example



215



Debugging bindings

Given the declarative nature of WPF, debugging can be frustrating. When data binding

works, it’s lovely; but, when it doesn’t, it can be an extremely frustrating experience.

Fortunately, there’s at least some help. By default, if there’s a binding problem, some

information will be written to the output window. You can increase the amount of information written out via WPF’s support for debugging bindings. It isn’t a debugger in

the traditional sense because you can’t step through the XAML to see what’s happening, but it will write out a bunch of extra information about what’s going on when

you run.

To enable debug assistance, you need to add another reference to the XAML. Add

the following namespace on the Window element:

xmlns:debug="clr-namespace:System.Diagnostics;assembly=WindowsBase"



When you want to debug a particular binding, you can add a tracelevel to the statement, like this:

ItemsSource="{Binding Source={StaticResource processes},

debug:PresentationTraceSources.TraceLevel=High}"



Now when you run, a whole host of useful messages will be written to the output window, telling you step-by-step what’s happening during the bind. It isn’t guaranteed to

help, but it’s better than guessing!



The ListView now has a scroll bar as shown in figure 11.3. But the ListView itself is

empty—the data is there, but we still can’t see it.

The problem is that we have an array

of Process objects, but the ListView has

no idea how we want them displayed. We

need a template. ASP.NET developers

should feel right at home here because

WPF templates are similar to ASP.NET

templates. Visual Studio 2008 doesn’t

have a template editor, so it’s time to

expand the XAML editor.

To explain what we want to see and

how we want to see it to this ListView,

we need to create a DataTemplate

describing it. ListView has a property

called ItemTemplate that lets us tell the

control how we want our data displayed.

We add the following XAML within the Figure 11.3 The window shows evidence of data

ListView tag:

with a scrollbar, but nothing is showing up.



216



CHAPTER 11



Data binding with WPF















Here we use the expanded form of a XAML property setter to declare and instantiate a

DataTemplate object that contains a single TextBlock. The Text property of the

TextBlock is bound to a property called ProcessName. Path references the particular

property that we want to access. Because we aren’t specifying a Source this time, the

binding mechanism assumes that we want to bind to whatever object we have available—in this case, the Process object in the current row of the ListView. Once the

designer refreshes, the process names should be on the system as in figure 11.4.



Figure 11.4 With a DataTemplate

in place, the ListView now shows

our running processes.



Now we’re getting somewhere, but the name of the process by itself isn’t overly interesting. It would be nice to have the ID and, perhaps, the WorkingSet as well. The Process

class has a lot of properties, so we should hook up at least a couple more (listing 11.2).

Listing 11.2 DataTemplate that pulls multiple properties from data source



















b



c



ProcessMonitor: A simple binding example



217















As you can see, the DataTemplate can be as complex as we like. For example, here

we’re using a WrapPanel b. We could just as easily use a Grid or other layout, put in

drawings, set colors and backgrounds, and so on. In chapter 12, we’ll demonstrate

some more elaborate data templates.

You may also notice that, although we’re using the string notation (called the

MarkupExtension) for two of our TextBlocks, for the third one, we’re using a slightly different notation c. The two are functionally equivalent. Whereas the MarkupExtension is

more compact, the expanded Binding element is easier to read and allows you to do a

few things you can do only with the longhand notation.

In any case, now we’ve got some nice data in figure 11.5.

If we were to carry on with this application, we’d probably prefer a more flexible

set of views with columns, sorting, and grouping.

Now that we’ve got all the data binding figured out using XAML, how would we do

it with code?



11.2.2 Binding in code

So far, all the binding we’ve done has been through XAML. Sometimes binding in

code is necessary or simpler. If you want to bind against instances that are controlled

and exposed via a strong business logic layer, you’ll probably want to bind in code.

XAML bindings prefer XAML declarations and tend toward statics and widely shared



Figure 11.5 With a more

sophisticated template, we get

more appealing results.



218



CHAPTER 11



Data binding with WPF



instances, which may be contrary to threading or isolation goals. In any case, we can

do everything we just did in code.

First, we remove everything we just did (or save it off and start a new project). The

XAML should look like this:


xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

Title="Monitor" Height="400" Width="400">







































Notice that, although we’ve removed all the binding, we left the ItemTemplate in

place. Given that the ItemTemplate is all about how to display our data, it makes sense

to leave it in XAML. The DataTemplate does have specific bindings to specific properties; but, if they aren’t found, they’ll simply fail quietly. In fact, although it’s possible to

create a DataTemplate with code, it’s fairly tricky and somewhat discouraged.

Anyway, on to the code. We right-click Monitor.xaml and select View Code and

then create a new method called BindProcessesToListView (listing 11.3).

Listing 11.3 Binding in code

public Monitor()

{

InitializeComponent();

BindProcessesToListView();

}



Calls method to

create Binding



private void BindProcessesToListView()

{

ObjectDataProvider provider = new ObjectDataProvider();

provider.ObjectType = typeof(Process);

provider.MethodName = "GetProcesses";

Binding binding = new Binding();

binding.Source = provider;

binding.Mode = BindingMode.OneWay;



c



b



219



ProcessMonitor: A simple binding example

PresentationTraceSources.SetTraceLevel(binding,

PresentationTraceLevel.High);

listView1.SetBinding(ListView.ItemsSourceProperty, binding);



d

e



}



The code here mostly mirrors what we did in XAML. First, we create the ObjectDataProvider b, pointing it to the Process object and the GetProcesses method. Next,

we create the Binding object c. The data source for the Binding is the ObjectDataProvider we just created. We also specify that the binding is only going to go one

way—from the DataProvider to the ListView.

We also enable debug tracing d to write out information for us about what’s

going on the binding. Finally, we associate the binding to the ListView’s ItemsSource

property e.



Binding performance

Under the hood, binding to CLR uses a lot of reflection, and wherever there’s reflection, there are potential performance problems. Fortunately, Microsoft’s API philosophy of “make the simple things simple and make the complex things possible” is in

full force here. In the simple case, the framework gets Type and Property descriptors on the CLR objects and sets up the binding appropriately. In the case where performance is more critical, .NET and WPF provide the following interfaces (neither of

which is new to WPF) to increase binding speed:





ICustomTypeDescriptor —Provides a way for the binding code to find out about

the object and its properties without using reflection. If you haven’t used binding

in the past due to performance or functionality limitations, this is an interface

you’ll want to get cozy with.







INotifyPropertyChanged —Provides an interface to implement a custom

scheme for notifying the property system that the source data has been updated.

WPF native DependencyProperties already provide this notification logic (although they don’t use INotifyPropertyChanged).



In most cases, you’ll probably find that you don’t even need these optimizations, but

if you do, it’s nice to know that they are there.



If you run this code, it will look exactly like figure 11.5.



11.2.3 Binding notation and options

Binding is used all over the place in WPF. It’s used to get data, as we’ve seen, but it’s

also used in tons of other things such as sizing (to bind the width of one control to the

width of another control), animation (to get to the properties that are being animated), and control templates (to tie pieces of the template to specific properties).

The great thing is that the binding notation is really flexible. The downside is that

the binding notation is really flexible. WPF hasn’t been out for very long. We suspect



220



CHAPTER 11



Data binding with WPF



that, if all the time spent debugging and fixing bindings could be harnessed, we would

have, as a species, solved global warming and world hunger, and figured out a way of

getting your food delivered before the toast gets cold.

The source of a binding can be broken down into two things: where the data is

coming from and what bit of data you want from there. The where can be one of four

different things (table 11.2).

Table 11.2



Where the data comes from in a Binding



Property



Source



Description

You use Source when you want to bind to a particular object. Invariably, that

object is defined as a resource, so the notation looks like this (from our

ListView example):




ItemsSource="{Binding Source={StaticResource

processes}}">

ElementName



You use ElementName when you want to bind to a property on some other element in your UI. For example, if there were a control called someListBox and you

wanted a TextBox to be the same color as the list box, you’d write this:




"{Binding ElementName=someListBox, Path=Background}"

/>

RelativeSource



RelativeSource is used when you don’t know the specific element you want

to reference, but you know where that element is relative to where you are now—

that is, relative to the element that you’re currently binding. There are several different modes for RelativeSource that control its behavior.

Mode



FindAncestor



Behavior

In this case, the RelativeSource is the first ancestor element that meets a particular condition. You can

specify the ancestor you want based on how many levels up it is.



{Binding RelativeSource=

{RelativeSource FindAncestor,

AncestorLevel=2}}

This statement says to find the element two levels up

from where you are (parent’s parent). Or you can specify

that you want the first element of a particular type.



{Binding RelativeSource=

{RelativeSource FindAncestor,

AncestorType={x:Type GroupBox}}}

This says find the first parent element of type

GroupBox. You can also combine Level and

Type—you could bind to the third parent of type

GroupBox, for example.



ProcessMonitor: A simple binding example

Table 11.2



221



Where the data comes from in a Binding (continued)



Property



RelativeSource



Description



Self



(continued)



This is used to bind to a property on the current element. For example, suppose that you want to set your

border color to be the same as your text color, you’d do

something like this:




{Binding RelativeSource=

{RelativeSource Self},

Path=Foreground} />

TemplatedParent



This is a special reference used when you’re defining

ControlTemplates or DataTemplates.

When you’re writing a control template (for example),

there are two different places where you might want to

bind—to something in the control that’s using the control template or from something in the control template

itself.

For example, you might have a control template that

defines the look-and-feel of a button. The content of the

template might well be driven off of the data associated

with the button, but the control template can have several animations that need to depend on properties in

the template. The notation for TemplatedParent is

as follows:



{Binding RelativeSource=

{RelativeSource TemplatedParent},

Path=somethingInTemplate}

We aren’t showing a real-world example here because it

would be meaningless without an entire template and

an example, but we make use of this capability in the

chapter on transition effects. To give you an idea of how

well used this version of RelativeSource is,

there’s a custom notation for it.



{TemplateBinding somethingInTemplate}

This notation does almost exactly the same thing.

(We’ve run into few situations where only one or the

other works.)



PreviousData



PreviousData is only used when you’re binding to

something like a ListBox that has a currently

selected item and a previously selected item. As you

can guess, PreviousData returns the last selected

value before the current value. This might be used to

display history or if you want to build a transition from

the old value to the new value.

{Binding

RelativeSource={Relative Source

PreviousData},Path=Value}



222



CHAPTER 11

Table 11.2



Where the data comes from in a Binding (continued)



Property

Nothing



Data binding with WPF



Description

No, there isn’t a property called nothing. If you don’t specify a Source,

RelativeSource, or ElementName, their absence means something to

the binding code—that the data will be determined based on the data context of

the current item.

We’ll talk about data contexts in a lot more detail later, but they’re explicitly or

implicitly set locations to retrieve data. If the data context isn’t set for a particular element, then that element determines it based on its parent.



Once you’ve figured out where you’re getting your data, you can use the Path property to specify the path to the bit of data in that object. If you exclude Path, then

you’re binding to the entire object. (For example, if you want to bind a ListBox to a

collection, you want the entire collection.) Otherwise, Path can point to a property or

indicate the path to data in more complex ways. Here are some examples:













Path=SelectedItem

Path=SelectedItem.Tag

Path=SelectedItem.Tag[30]

Path=SelectedItem.Tag[30].Name

Path=SelectedItem.Tag[30].(Parent.Element).Name



If you think that this is scary, you should try debugging some of the code we’ve written

that uses stuff like this. The point that we want to make is that Path is very flexible and

has a rich syntax. In practice, if you’re doing things like some of the worst examples

here, you might want to reconsider your design. (Often, you can create a simple intermediate object in code that will make most of this nastiness go away.)

Binding also has several other features. For example, we can specify a converter

that can take the value returned by the binding and change it to be something else.

{Binding Path=ActualWidth,Converter={StaticResource AddPadding}}



The AddPadding resource points to an object that implements the IValueConverter

interface—a simple interface with only the two methods Convert and ConvertBack

(and no one ever bothers implementing the ConvertBack method). The AddPadding

resource looks like this:









Where local is the namespace for our local code. The AddPaddingValueConverter

looks something like this:

public class AddPaddingValueConverter: IValueConverter

{

public AddPaddingValueConverter () {}



Binding to XML



223



public object Convert(object value, Type targetType,

object parameter, System.Globalization.CultureInfo culture)

{

double d = System.Convert.ToDouble(value);

return d + 20;

}

public object ConvertBack(object value, Type targetType,

object parameter, System.Globalization.CultureInfo culture)

{

double d = System.Convert.ToDouble(value);

return d - 20;

}

}



See, nothing to it! But now, when the value is retrieved from the ActualWidth property, it’s passed to the converter, and 20 is added to the value that’s used. Even though

we’ve shown all the different properties that you can set on a binding, we’re only

scratching the surface on binding notations and options. You’ll be seeing many, many

examples both throughout this chapter and throughout the book. For example, the

next section talks about binding to XML.



11.3 Binding to XML

We’re not sure. Our track record with technology is somewhat hit-and-miss, littered

with Betamax tapes and Amiga computers, but we’ve had a look at this whole XML

thing, and we think it has a chance of catching on. No promises, though.

Seriously, XML is everywhere now, and WPF supports binding directly to XML

objects, as we’ll demonstrate in this section. For this exercise, we wanted to push the

binding system, so we found some nice, large XML examples.

MITRE is a federally funded research lab. One of the projects MITRE works on is

called the Common Vunerabilities and Exposures (CVE) list. This list provides a single

source to identify and describe vulnerabilities and exposures in computer systems, and

it so happens that the list is published as an XML file. The latest version of the XML, as of

this writing, weighed in at around 30MB. That sounds like a nice chunk of XML to give

the binding engine to chew on. In effect, the XML is going to be our model.

But even for something like a web browser, an intermediate object model2 is generally used to encapsulate behavior. For all but the simplest applications, using a data format as the abstraction for your model is almost certainly a lousy idea. If we were to

write an application around CVEs, like a CVE editor for instance, we’d build business

objects with interactive behavior, and the details of how we stored it would be invisible

from the UI.

That all being said, sometimes a light wrapping over XML or SQL is all you need.

Along those lines, we’re going to create a little application to view the data in these

XML files (figure 11.6).

2



If you were writing an XML editor, these would be ideal domain objects.



Tài liệu bạn tìm kiếm đã sẵn sàng tải về

2 ProcessMonitor: A simple binding example

Tải bản đầy đủ ngay(0 tr)

×