Chapter 23. Data Binding


Data binding is the technique of connecting controls and elements to data, and if that definition seems a little vague, it only attests to the wide scope and versatility of these techniques. Data binding can be as simple as connecting a CheckBox control to a Boolean variable, or as massive as connecting a database to a data-entry panel.

Controls have always served the dual purpose of displaying data to the user and allowing the user to change that data. In modern application programming interfaces, however, many of the routine links between controls and data have become automated. In the past, a programmer would write code both to initialize a CheckBox from a Boolean variable, and to set the Boolean variable from the CheckBox after the user has finished with it. In today's modern programming environments, the programmer defines a binding between the CheckBox and the variable. This binding automatically performs both jobs.

Very often a data binding can replace an event handler, and that goes a long way to simplifying code, particularly if you're coding in XAML. Data bindings defined in XAML can eliminate the need for an event handler in the code-behind file and, in some cases, eliminate the code-behind file entirely. The result is code that I like to think of as having "no moving parts." Everything is initialization, and much less can go wrong. (Of course, the event handlers still exist, but they're behind the scenes, and presumably they come to us already debugged and robust enough for heavy lifting.)

Data bindings are considered to have a source and a target. Generally the source is some data and the target is a control. In actual practice you'll discover that the distinction between source and target sometimes gets a bit vague and sometimes the roles even seem to swap as a target supplies data to a source. Although the convenient terms do not describe a rigid mechanism, the distinction is important nonetheless.

Perhaps the simplest bindings are those that exist between two controls. For example, suppose you want to use a Label to view the Value property of a ScrollBar. You could install an event handler for the ValueChanged event of the ScrollBar, or you could define a data binding instead, as the following stand-alone XAML file demonstrates.

BindLabelToScrollBar.xaml

[View full width]

<!-- === ==================================================== BindLabelToScrollBar.xaml (c) 2006 by Charles Petzold ============================================= ========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <!-- Binding Source. --> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" /> <!-- Binding Target. --> <Label HorizontalAlignment="Center" Content="{Binding ElementName=scroll, Path=Value}" /> </StackPanel>



The binding itself is always set on the target of the binding. In this XAML file, the binding is set on the Content property of the Label control with this syntax:

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


Like StaticResource and DynamicResource, Binding is a markup extension. Curly braces surround the Binding definition. Both ElementName and Path are among several properties of the Binding class that can appear in this definition. In this particular Binding definition, the ElementName is set to scroll, which is the name given to the ScrollBar in its Name attribute. The Path property of Binding is set to Value, which in this context refers to the Value property of the ScrollBar. The Content property of the Label, then, is bound to the Value property of the ScrollBar. As you manipulate the ScrollBar, the Label shows the current value.

No matter how long you've been writing XAML, I can practically guarantee that you'll want to put quotation marks inside that Binding definition. ElementName and Path look so much like XML attributes that your fingers will want to type something like this:

Content="{Binding ElementName="scroll" Path="Value"}" 


That's completely wrong! Within those curly braces, different rules apply. ElementName and Path are not XML attributes. The only attribute involved here is Content. Not only must there be no quotation marks within the curly braces, but the ElementName and Path items must also be separated by a comma.

On the other hand, if you really can't stop yourself from typing quotation marks in the Binding definition, perhaps you'd prefer the alternative property element syntax.

PropertyElementSyntax.xaml

[View full width]

<!-- === ===================================================== PropertyElementSyntax.xaml (c) 2006 by Charles Petzold ============================================= =========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <!-- Binding Source. --> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" /> <!-- Binding Target. --> <Label HorizontalAlignment="Center"> <Label.Content> <Binding ElementName="scroll" Path="Value" /> </Label.Content> </Label> </StackPanel>



Now the Binding element appears within Label.Content start and end tags, and the ElementName and Path properties are treated as normal XML attributes. You may be tempted to remove the Label.Content tags under the assumption that they're not needed. They aren't needed under most circumstances, but they are needed here.

Regardless of how you do it, the control or element in which the Binding definition occurs is always the target of the binding. A binding target must derive from DependencyObject. The property on which the binding is set must be backed by a dependency property. In this particular case, therefore, it's necessary for Label to have a static public field of type DependencyProperty named ContentProperty (which, of course, it does).

The requirements for bindings are more evident when you examine a binding in C#. I'll assume you've created ScrollBar and Label controls and stored the objects as scroll and lbl. The code is:

Binding bind = new Binding(); bind.Source = scroll; bind.Path = new PropertyPath(ScrollBar.ValueProperty); lbl.SetBinding(Label.Content, bind); 


This code is not the exact equivalent of the Binding definition in XAML because it sets the Source property of Binding to the ScrollBar object rather than setting the ElementName property to the string "scroll." But notice that the SetBinding method is called on the target of the binding. This method is defined by FrameworkElement and the first argument is DependencyProperty, so those two facts indicate that the binding target can't be just anything. The binding must be established on properties backed by dependency properties because controls and elements are designed to react to changes in their dependency properties.

Although not quite evident from this code, the requirements for a binding source are much looser. The bound property of the source need not be a dependency property. In the ideal case, the property should be associated with an event that indicates when the property changes, but some bindings can work even without that notification event.

Let's experiment a bit with the BindLabelToScrollBar.xaml file. Although the terms source and target seem to imply that changes in the source element (in this case, the ScrollBar) precipitate changes in the target element (the Label), that's only one of four possible binding modes. You specify the mode you want by using the Mode property and members of the BindingMode enumeration. This is the default:

Content="{Binding ElementName=scroll, Path=Value, Mode=OneWay}" 


Notice that the setting of the Mode property is separated from the Path property setting by another comma, and that no quotation marks appear around the BindingMode enumeration member OneWay. If you prefer the property element syntax, you add it to the attributes in the Binding element:

<Label.Content>     <Binding ElementName="scroll" Path="Value" Mode="OneWay" /> </Label.Content> 


You can also set the mode to TwoWay:

Content="{Binding ElementName=scroll, Path=Value, Mode=TwoWay}" 


In this program, the functionality is the same as OneWay but in theory, changes to the Content property of Label should now also be reflected in the Value property of the ScrollBar. Here's another possibility:

Content="{Binding ElementName=scroll, Path=Value, Mode=OneTime}" 


The OneTime mode means that the target is initialized from the source but does not track changes in the source. In this program, the Label displays a value of 0 because that's the initial Value property of the ScrollBar, but changes in the ScrollBar cause no further changes in that displayed value. (You could explicitly set Value equal to 50 in the ScrollBar element; in that case the Label will display 50 because it is initialized with that value.)

The final option is:

Content="{Binding ElementName=scroll, Path=Value, Mode=OneWayToSource}" 


This is the mind-boggler of the group because it's instructing the source to be updated from the target, which is the opposite of how we normally think about source and target. It's as if the bull's-eye wants to impale itself on the arrow. In this case, the target (the Label) is supposed to update the source (the ScrollBar), but the Label has no numeric data to give to the ScrollBar. The Label is blank and remains blank as you move the ScrollBar.

Although this OneWayToSource mode seems to violate fundamental laws of the universe, it turns out to be quite useful when you want to establish a binding between two properties but the target property isn't backed by a dependency property while the source property is. In that case, put the binding on the source, and set the Mode to OneWayToSource.

To explore these concepts a bit more, let's look at a similar XAML file in which the ScrollBar and Label controls have switched roles.

BindScrollBarToLabel.xaml

[View full width]

<!-- === ==================================================== BindScrollBarToLabel.xaml (c) 2006 by Charles Petzold ============================================= ========== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <!-- Binding Target. --> <ScrollBar Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" Value="{Binding ElementName=lbl, Path=Content}" /> <!-- Binding Source. --> <Label Name="lbl" Content="50" HorizontalAlignment="Center" /> </StackPanel>



Now the Label is the source and the ScrollBar is the target. As usual, the Binding definition appears on the target and binds the Value property of the ScrollBar with the Content property of the Label:

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


I've also given the Label control a Content of 50. When this program starts up, Label displays 50, and the ScrollBar thumb is poised in the center. As you move the ScrollBar, the Label is updated. Obviously a two-way binding has been established by default. It works the same way if you explicitly indicate that option:

Value="{Binding ElementName=lbl, Path=Content, Mode=TwoWay}" 


However, it doesn't work when you change to a one-way mode:

Value="{Binding ElementName=lbl, Path=Content, Mode=OneWay}" 


The ScrollBar is still initialized to its midpoint position because it is the target of the binding and the Label source indicates 50. But there are no further changes because the ScrollBar isn't getting any more numeric data from the Label. You'll get the same result with:

Value="{Binding ElementName=lbl, Path=Content, Mode=OneTime}" 


But now try this one:

Value="{Binding ElementName=lbl, Path=Content, Mode=OneWayToSource}" 


Now the ScrollBar target is governing the Label source. The Label is initialized to 0 because that's the initial Value of the ScrollBar. Thereafter, the Label dutifully reports changes in the ScrollBar.

The default Mode of a binding is governed by the property on which the binding is defined. This experimentation with BindScrollBarToLabel.xaml reveals that the Value property of ScrollBar has a default binding Mode of TwoWay. In theory, the ValueProperty dependency property of ScrollBar should have a FrameworkPropertyMetadata object with a BindsTwoWayByDefault property of true. However, the ExploreDependencyProperties program in Chapter 16 reveals that the scrollbar's ValueProperty has its metadata stored in a PropertyMetadata object rather than FrameworkPropertyMetadata.

For this reason, it's probably not a good idea to try to guess at the default binding modes of controls. This Mode property is one of the most important components of the binding, and it wouldn't hurt if you consider each binding carefully to determine what the proper Mode setting should be and then explicitly set it.

Some C# programs earlier in this book had some data bindings. These often made use of the DataContext property defined by FrameworkElement, which is an alternative way to denote the source object involved in the binding. Here's a little stand-alone file that shows how to set the DataContext in XAML:

BindingWithDataContext.xaml

[View full width]

<!-- === ====================================================== BindingWithDataContext.xaml (c) 2006 by Charles Petzold ============================================= ============ --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml"> <!-- Binding Source. --> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" /> <!-- Binding Target. --> <Label HorizontalAlignment="Center" DataContext="{Binding ElementName=scroll}" Content="{Binding Path=Value}" /> </StackPanel>



Notice that both the DataContext and Content attributes of Label are set to a Binding definition, which has essentially been split up into two pieces. The first Binding definition indicates the ElementName and the second has the Path.

There's certainly no advantage to using the DataContext property in this example, but in some cases the DataContext property is extremely valuable. DataContext is inherited through the element tree, so if you set it for one element, it also applies to all the children of that element.

The TwoBindings.xaml file illustrates this technique. The DataContext is set once for the StackPanel element. Properties of both a Label and a Button are bound to the ScrollBar. For the Label, the bound property is just the Content, but for the Button, the bound property is FontSize. As you move the ScrollBar thumb, the text inside the Button gets larger and eventually so does the Button itself.

TwoBindings.xaml

[View full width]

<!-- ============================================== TwoBindings.xaml (c) 2006 by Charles Petzold ============================================= = --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" DataContext="{Binding ElementName=scroll}"> <!-- Binding Source. --> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Minimum="1" Maximum="100" LargeChange="10" SmallChange="1" /> <!-- Binding Targets. --> <Label HorizontalAlignment="Center" Content="{Binding Path=Value, Mode=OneWay}" /> <Button HorizontalAlignment="Center" Margin="24" FontSize="{Binding Path=Value, Mode=OneWay}"> Bound Button </Button> </StackPanel>



Of course, we wouldn't have suffered much grief if we included the ElementName property in both Binding definitions. However, consider a panel in which many controls are bound to various properties of a particular type of object. By just setting the DataContext to a different object of that type, all the controls reflect the new object. In Chapter 26 you'll see an example of that technique.

Here's a little stand-alone file that uses data binding to display the current width and height of the program's client area:

WhatSize.xaml

[View full width]

<!-- =========================================== WhatSize.xaml (c) 2006 by Charles Petzold =========================================== --> <Grid xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" Name="grid"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="{Binding ElementName=grid , Path=ActualWidth}" /> <TextBlock Text=" &#x00D7; " /> <TextBlock Text="{Binding ElementName=grid , Path=ActualHeight}" /> <TextBlock Text=" device independent units" /> </StackPanel> </Grid>



The StackPanel has a horizontal orientation and contains four TextBlock elements that combine to form what appears to be a single line of text. Two of the TextBlock elements define bindings to the ActualWidth and ActualHeight properties of the Grid. These properties are read-only, so the binding mode can't be anything except one-way.

My first attempts at writing the WhatSize.xaml program weren't very successful. Rather than defining multiple TextBlock elements, I originally wanted to use a single TextBlock with multiple children of type Run. Two of these Run elements would have their Text properties bound to the ActualWidth and ActualHeight properties of the Grid. The markup looked like this:

<!-- This markup doesn't work! --> <TextBlock>     <Run Text="{Binding ElementName=grid, Path=ActualWidth}" />     <Run Text=" &#x00D7; " />     <Run Text="{Binding ElementName=grid, Path=ActualHeight}" />     <Run Text=" device independent units" /> </TextBlock> 


This chunk of XAML still looks fine to me, but it raised an exception that said "Object of type 'System.Windows.Data.Binding' cannot be converted to type 'System.String'", obviously referring to the type of the Text property. The message seemed very strange to me because it implied that the parser was ignoring the very nature of data bindings.

The solution to this mystery is very simple: The Text property defined by TextBlock is backed by the dependency property named TextProperty. The Text property defined by Run is not backed by a dependency property. The target of a data binding must be a dependency property. This little fact is much more obvious when you define the binding in C#: The first argument to SetBinding is of type DependencyProperty.

The binding source need not be a dependency property. All the data-binding examples so far have involved both targets and sources backed by dependency properties, but you'll see examples later in this chapter of binding sources that are plain old .NET properties. In the general case, a OneWay binding involves a continual transfer of information from the binding source to the target. For one-way bindings to be successful, the source must implement a mechanism of some sort to keep the target informed when the source property has changed.

When I say "a mechanism of some sort," do I mean an event? That's certainly one possibility, but not the only one. One of the primary incentives behind the invention of dependency properties was data binding, and the dependency property system has built-in notification support. The binding source doesn't have to be a dependency property, but it really helps if it is.

Here is a simple element. In fact, the class is named SimpleElement and it derives directly from FrameworkElement.

SimpleElement.cs

[View full width]

//---------------------------------------------- // SimpleElement.cs (c) 2006 by Charles Petzold //---------------------------------------------- using System; using System.Globalization; using System.Windows; using System.Windows.Media; namespace Petzold.CustomElementBinding { class SimpleElement : FrameworkElement { // Define DependencyProperty. public static DependencyProperty NumberProperty; // Create DependencyProperty in static constructor. static SimpleElement() { NumberProperty = DependencyProperty.Register ("Number", typeof(double), typeof (SimpleElement), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsRender)); } // Expose DependencyProperty as CLR property. public double Number { set { SetValue(NumberProperty, value); } get { return (double)GetValue (NumberProperty); } } // Hard-coded size for MeasureOverride. protected override Size MeasureOverride (Size sizeAvailable) { return new Size(200, 50); } // OnRender just displays Number property. protected override void OnRender (DrawingContext dc) { dc.DrawText( new FormattedText(Number.ToString(), CultureInfo.CurrentCulture , FlowDirection.LeftToRight, new Typeface("Times New Roman"), 12, SystemColors.WindowTextBrush), new Point(0, 0)); } } }



This class defines a property named Number of type double that is backed by the DependencyProperty named (as is the convention) NumberProperty. The FrameworkPropertyMetadata indicates merely that the default value is 0, and that changes to the property should invalidate the visual and result in a call to OnRender. The MeasureOverride method takes a very simple approach to sizing and asks for a size of 200 by 50 device-independent units. The OnRender method simply displays the Number property.

I must confess that I was skeptical when I began assembling some markup that would let me explore bindings between SimpleElement and a ScrollBar. The application definition file is trivial as usual:

CustomElementBindingApp.xaml

[View full width]

<!-- === ======================================================= CustomElementBindingApp.xaml (c) 2006 by Charles Petzold ============================================= ============= --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="CustomElementBindingWindow.xaml"> </Application>



The Window element creates two ScrollBar controls and two SimpleElement elements and defines three bindings among them:

CustomElementBindingWindow.xaml

[View full width]

<!-- === =========== =============================================== CustomElementBindingWindow.xaml (c) 2006 by Charles Petzold ============================================= ================ --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold .CustomElementBinding" Title="Custom Element Binding Demo"> <StackPanel> <ScrollBar Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" Value="{Binding ElementName=simple, Path=Number, Mode=OneWayToSource}" /> <src:SimpleElement x:Name="simple" HorizontalAlignment="Center" /> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" Value="{Binding ElementName=simple, Path=Number, Mode=TwoWay}" /> <src:SimpleElement HorizontalAlignment="Center" Number="{Binding ElementName=scroll, Path=Value, Mode=OneWay}" /> </StackPanel> </Window>



Notice the x:Name attribute on the first SimpleElement element. The x:Name attribute is intended for XAML elements that do not derive from FrameworkElement and hence have no Name property. However, using Name in this case generated an error message that said "Because 'SimpleElement' is implemented in the same assembly, you must set the x:Name attribute rather than the Name attribute." So I did.

The first ScrollBar defines a OneWayToSource binding with the Number property of the first SimpleElement. As you click on this ScrollBar and move its thumb, the first SimpleElement changes. This is the no-brainer: We all expect the first ScrollBar to successfully update the first SimpleElement.

The second ScrollBar defines a TwoWay binding with the Number property of the first SimpleElement, and you'll see that this one works as well. Although SimpleElement has no explicit notification mechanism, changes to the Number property as a result of manipulating the first ScrollBar are detected in this binding, and the second ScrollBar tracks the first ScrollBar. Because this is a TwoWay binding, you can manipulate the second ScrollBar, and SimpleElement also changes (although the first ScrollBar remains still).

The second SimpleElement element defines a OneWay data binding with the second ScrollBar. As the second ScrollBar moveseither through binding or directly by youthe second SimpleElement is updated with the new value.

The lesson is clear: Define a DependencyProperty and get a data binding notification for free.

Two metadata flags affect data binding. If you include the FrameworkPropertyMetadataOptions.NotDataBindable flag, other elements can still bind to the dependency property, but you can't define a binding on the dependency property itself. (In other words, a dependency property with this flag set can't be the target of a data binding.) The FrameworkPropertyMetadataOptions.BindsTwoWayByDefault flag affects only bindings defined where that dependency property is the target.

As you've seen, you set the Path property of Binding to a property of the source object. So why is it called Path? Why isn't it called Property?

It's called Path because it can be more than just a property. It can be a series of properties (possibly with indices) combined with periods, much like C# code but without the hassle of strong typing. Here's a stand-alone XAML program that has two Path properties with lengths a little past the comfort level.

LongBindingPath.xaml

[View full width]

<!-- === =============================================== LongBindingPath.xaml (c) 2006 by Charles Petzold ============================================= ===== --> <Page xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" FontSize="12pt" Name="page"> <StackPanel> <TextBlock HorizontalAlignment="Center"> First element in StackPanel </TextBlock> <ListBox HorizontalAlignment="Center" Margin="24"> <ListBoxItem>First ListBox Item</ ListBoxItem> <ListBoxItem>Second ListBox Item</ ListBoxItem> <ListBoxItem>Third ListBoxItem</ ListBoxItem> <ListBoxItem>Fourth ListBox Item</ ListBoxItem> <ListBoxItem>Fifth ListBoxItem</ ListBoxItem> </ListBox> <TextBlock HorizontalAlignment="Center"> <Label Content="Number of characters in third ListBox item = " /> <Label Content="{Binding ElementName=page, Path=Content.Children[1] .Items[2].Content.Length}" /> <LineBreak /> <Label Content="Number of characters in selected item = " /> <Label Content="{Binding ElementName=page, Path=Content.Children[1] .SelectedItem.Content.Length}" /> </TextBlock> </StackPanel> </Page>



The only element that has a Name attribute is the root element. The Page contains a StackPanel that in turn contains two TextBlock elements surrounding a ListBox. The ListBox contains five ListBoxItem controls. The embedded Label controls inside that final TextBlock display the length of the text in the third item in the ListBox and the length of the text of the selected item. Here's the Path definition for the latter of those two:

Path=Content.Children[1].SelectedItem.Content.Length 


Although this may look like a chain of nested properties in some C# code, keep in mind that it's just a string, and it is parsed as a string. The parser uses reflection to determine whether these items make sense, and if they don't make sense, the parsing is just abandoned without raising any objections or exceptions. Starting from the left, the Path consists of the Content of the Page (which is a StackPanel), the second item in the Children collection of the StackPanel (which is the ListBox), the SelectedItem of the ListBox (which is a ListBoxItem), the Content of the ListBoxItem (which is a string), and the Length property of that string.

What was your reaction the first time you dragged the thumb of one of the scrollbars earlier in this chapter and realized that the Value property was truly a double-precision floating-point number with a fractional value and 16-odd significant digits? I alternated between finding it very cool and deeply disturbing. But it's not just an aesthetic issue. The floating-point output of the ScrollBar is potentially a problem if you need to bind the ScrollBar with something expecting integers.

As data is transferred from a binding source to a target (and sometimes back) the data might need to be converted from one type to another. The Binding class includes a property named Converter that lets you specify a class that includes two methods named Convert and ConvertBack to perform this conversion.

The class performing the conversion must implement the IValueConverter interface and look something like this:

public class MyConverter: IValueConverter {     public object Convert(object value, Type typeTarget,                           object param, CultureInfo culture)     {         ...     }     Public object ConvertBack(object value, Type typeTarget,                               object param, CultureInfo culture)     {          ...     } } 


The value parameter is the object to be converted, and typeTarget is the type that it's to be converted into. This is the type of the object that the method returns. If it can't return an object of that type, the method should return null. The third parameter is potentially an object specified by the ConvertParameter property of Binding, and the last parameter indicates the culture to assume when performing the conversion. (In many cases, it can be ignored.)

If you're creating a Binding in C#, you set the Convert property to an object of a class that implements the IValueConverter interface:

Binding bind = new Binding(); bind.Convert = new MyConverter(); 


You might also set the ConvertParameter property of Binding to an object that is passed as the param parameter to Convert and ConvertBack to control the conversion.

In XAML, getting that MyConverter class into the Binding definition is a little more roundabout. You need to create an object of type MyConverter and then reference that object in markup. You learned how to do tasks like this in Chapter 21: You make it a resource.

So, in a Resources section in your XAML, you specify the class that implements IValueConverter and you associate it with a key:

<src:MyConverter x:Key="conv" /> 


MyConverter is prefaced with src because I assume it's in a C# source code for the project and you've included an XML namespace declaration associating src with the namespace of your project.

In the Binding definition, you use a StaticResource markup extension to reference the MyConverter object:

"{Binding ... Convert={StaticResource conv}, ... }" 


You might also include a ConverterParameter property here. The conversion parameter lets you include additional information that affects the conversion. (You'll see an example shortly.) But that's not the only way to get additional information into the conversion class. The class must implement the IValueConverter interface but nothing prohibits it from defining public properties of its own. You can set these properties in XAML when the class is included in the Resources section of the XAML file:

<src:MyConverter Decimals="4" s:Key="conv" /> 


The following class provides a conversion from double to decimal. The conversion parameter is the number of decimal places to which the decimal result is rounded.

DoubleToDecimalConverter.cs

[View full width]

//--------- ------------------------------------------------ // DoubleToDecimalConverter.cs (c) 2006 by Charles Petzold //------------------------------------------------ --------- using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace Petzold.DecimalScrollBar { [ValueConversion(typeof(double), typeof(decimal))] public class DoubleToDecimalConverter : IValueConverter { public object Convert(object value, Type typeTarget, object param, CultureInfo culture) { decimal num = new Decimal((double)value); if (param != null) num = Decimal.Round(num, Int32 .Parse(param as string)); return num; } public object ConvertBack(object value, Type typeTarget, object param, CultureInfo culture) { return Decimal.ToDouble((decimal)value); } } }



The WPF documentation recommends that you include a ValueConversion attribute right before the class definition to alert development tools to the role this class plays. How bulletproof you make these conversion routines is really up to you. If you're using them in your own code, you can pretty much anticipate the types of the arguments. For example, the Convert method here assumes that the param argument is a string that can be safely parsed into an integer. It also assumes that the return value is a Decimal without checking the typeTarget parameter.

The DoubleToDecimalConverter class is part of the DecimalScrollBar project that also includes this Window definition.

DecimalScrollBarWindow.xaml

[View full width]

<!-- === ====================================================== DecimalScrollBarWindow.xaml (c) 2006 by Charles Petzold ============================================= ============ --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold .DecimalScrollBar" Title="Decimal ScrollBar"> <Window.Resources> <src:DoubleToDecimalConverter x:Key="conv" /> </Window.Resources> <StackPanel> <!-- Binding Source. --> <ScrollBar Name="scroll" Orientation="Horizontal" Margin="24" Maximum="100" LargeChange="10" SmallChange="1" /> <!-- Binding Target. --> <Label HorizontalAlignment="Center" Content="{Binding ElementName=scroll, Path=Value, Converter={StaticResource conv }, ConverterParameter=2}" /> </StackPanel> </Window>



The Resources section includes the DoubleToDecimalConverter class and associates it with the key "conv." The Binding definition in the Label element references the conversion class as a StaticResource and assigns the value "2" to the ConverterParameter property. (Obviously it's easy to assign a string to ConverterParameter. You can actually assign an object of any type to ConverterParameter, but in that case the object must be a resource and referenced it as a StaticResource.) The application definition file rounds out the project.

DecimalScrollBarApp.xaml

[View full width]

<!-- === =================================================== DecimalScrollBarApp.xaml (c) 2006 by Charles Petzold ============================================= ========= --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="DecimalScrollBarWindow.xaml"> </Application>



As you move the scrollbar in this program, the value is displayed with two decimal places.

One type of binding for which a conversion class is always required is the multi-binding. The multi-binding consolidates multiple objects from multiple sources into a single target object. The multi-binding converter must implement the IMultiValueConverter interface. The classic example of a multi-binding is combining red, green, and blue primaries into a single Color object.

The ColorScroll project is similar to ScrollCustomColors from Chapter 6, except that most of the functionality has been moved into XAML. As you might recall, ScrollCustomColors contains three scrollbars for the red, green, and blue primaries. The original program installed event handlers to construct a SolidColorBrush from the three primaries. The ColorScroll project uses binding. Here's the application definition file first:

ColorScrollApp.xaml

[View full width]

<!-- === ================================================= ColorScrollAppDef.xaml (c) 2006 by Charles Petzold ============================================= ======= --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="ColorScrollPage.xaml"> </Application>



Just for variety, I've made the main XAML file a Page rather than a Window. The Resources section defines two conversion classesthe first to convert double values from the scrollbars to byte values for the labels, and the second to construct a SolidColorBrush from the three scrollbar values. As in the ScrollCustomColors project, the layout requires two Grid panels. The first has three columns. On the left is another Grid that contains the scrollbars and labels, on the right is a Border element used just to display the resultant color, and in between is a GridSplitter.

ColorScrollPage.xaml

[View full width]

<!-- === =============================================== ColorScrollPage.xaml (c) 2006 by Charles Petzold ============================================= ===== --> <Page xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold.ColorScroll" WindowTitle="Color Scroll"> <Page.Resources> <src:DoubleToByteConverter x :Key="convDoubleToByte" /> <src:RgbToColorConverter x :Key="convRgbToColor" /> </Page.Resources> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="Auto" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <!-- Grid with ScrollBar and Label controls. --> <GridSplitter Grid.Row="0" Grid.Column="1" Width="6" <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="100*" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="33*" /> <ColumnDefinition Width="33*" /> <ColumnDefinition Width="33*" /> </Grid.ColumnDefinitions> <!-- Red. --> <Label Content="Red" Foreground="Red" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="0" /> <ScrollBar Name="scrRed" Background="Red" Value="128" Minimum="0" Maximum="255" SmallChange="1" LargeChange="16" Focusable="True" Grid .Row="1" Grid.Column="0" /> <Label Content="{Binding ElementName=scrRed, Path=Value, Mode=OneWay, Converter={StaticResource convDoubleToByte}}" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="0" /> <!-- Green. --> <Label Content="Green" Foreground="Green" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="1" /> <ScrollBar Name="scrGreen" Background="Green" Value="128" Minimum="0" Maximum="255" SmallChange="1" LargeChange="16" Focusable="True" Grid .Row="1" Grid.Column="1" /> <Label Content="{Binding ElementName=scrGreen, Path=Value, Mode=OneWay, Converter={StaticResource convDoubleToByte}}" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="1" /> <!-- Blue. --> <Label Content="Blue" Foreground="Blue" HorizontalAlignment="Center" Grid.Row="0" Grid.Column="2" /> <ScrollBar Name="scrBlue" Background="Blue" Value="128" Minimum="0" Maximum="255" SmallChange="1" LargeChange="16" Focusable="True" Grid .Row="1" Grid.Column="2" /> <Label Content="{Binding ElementName=scrBlue, Path=Value, Mode=OneWay, Converter={StaticResource convDoubleToByte}}" HorizontalAlignment="Center" Grid.Row="2" Grid.Column="2" /> </Grid> <GridSplitter Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Stretch" /> <Border Grid.Row="0" Grid.Column="2"> <Border.Background> <MultiBinding Converter="{StaticResource convRgbToColor}"> <Binding ElementName="scrRed" Path="Value" Mode="OneWay "/> <Binding ElementName="scrGreen" Path="Value" Mode="OneWay "/> <Binding ElementName="scrBlue" Path="Value" Mode="OneWay "/> </MultiBinding> </Border.Background> </Border> </Grid> </Page>



The three Label controls that sit underneath the three scrollbars display the scrollbar values, and each has a data binding. Here's the Binding for the red primary that sets the Content property of the Label from the Value property of the ScrollBar (the element named "scrRed").

Content="{Binding ElementName=scrRed, Path=Value,         Mode=OneWay, Converter={StaticResource convDoubleToByte}}" 


The converter is a static resource with the key name "convDoubleToByte" that references the following class:

DoubleToByteConverter.xaml

[View full width]

//--------- --------------------------------------------- // DoubleToByteConverter.cs (c) 2006 by Charles Petzold //------------------------------------------------ ------ using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace Petzold.ColorScroll { [ValueConversion(typeof(double), typeof(byte))] public class DoubleToByteConverter : IValueConverter { public object Convert(object value, Type typeTarget, object param, CultureInfo culture) { return (byte)(double)value; } public object ConvertBack(object value, Type typeTarget, object param, CultureInfo culture) { return (double)value; } } }



Because the binding is one-way from source to target, the ConvertBack method doesn't come into play and could simply return null, but I've implemented it anyway. In Convert, it's necessary to cast the object first to a double and then to a byte, or a run-time exception will result when an object is converted directly into a byte.

The multi-binding occurs near the bottom of ColorScrollPage.xaml. The binding sets the Background property of the Border element:

<Border.Background>     <MultiBinding Converter="{StaticResource convRgbToColor}">         <Binding ElementName="scrRed" Path="Value" Mode="OneWay "/>         <Binding ElementName="scrGreen" Path="Value" Mode="OneWay "/>         <Binding ElementName="scrBlue" Path="Value" Mode="OneWay "/>     </MultiBinding> </Border.Background> 


The MultiBinding element always contains one or more Binding elements as children. The converter defined in the Resources section of the file and referenced by the key name of "convRgbToColor" must take the three values from the three bindings and convert them into an object appropriate for the Background property of the Border control. Here's the class that implements the IMultiValueConverter interface.

RgbToColorConverter.xaml

[View full width]

//---------------------------------------------------- // RgbToColorConverter.cs (c) 2006 by Charles Petzold //---------------------------------------------------- using System; using System.Globalization; using System.Windows; using System.Windows.Data; using System.Windows.Media; namespace Petzold.ColorScroll { public class RgbToColorConverter : IMultiValueConverter { public object Convert(object[] value, Type typeTarget, object param, CultureInfo culture) { Color clr = Color.FromRgb((byte) (double)value[0], (byte) (double)value[1], (byte) (double)value[2]); if (typeTarget == typeof(Color)) return clr; if (typeTarget == typeof(Brush)) return new SolidColorBrush(clr); return null; } public object[] ConvertBack(object value, Type[] typeTarget, object param, CultureInfo culture) { Color clr; object[] primaries = new object[3]; if (value is Color) clr = (Color) value; else if (value is SolidColorBrush) clr = (value as SolidColorBrush) .Color; else return null; primaries[0] = clr.R; primaries[1] = clr.G; primaries[2] = clr.B; return primaries; } } }



The first parameter to the Convert method is an array of objects. There will be one object for every Binding child of the MultiBinding element, and it need not be a fixed number. This particular converter knows that the array will contain three objects of type double, so it immediately casts those values into byte and constructs a Color object. I decided to make the routine just a little generalized by checking if the typeTarget parameter indicates Color or Brush.

Because this is a one-way binding, the ConvertBack method is never called and can simply return null, but I just couldn't resist breaking out the three primaries from the Color or SolidColorBrush argument and returning them as an array.

It is possible for the child Binding elements of a MultiBinding element to reference their own converters. In the ColorScroll program, for example, the individual Binding elements could have referenced the DoubleToByteConveter so that the values entering the RgbToColorConverter would be an array of bytes rather than an array of double-precision floating-point numbers.

All the bindings we've seen so far have updated the targets immediately from the source elements. Sometimes that's not necessarily desirable. Consequently, it's sometimes not the default operation.

Here's a stand-alone XAML file that contains three pairs of TextBox controls. Each pair has their Text properties connected in two-way bindings. The TextBox controls are arranged with the source of each pair on the left and the target on the right.

BindToTextBox.xaml

[View full width]

<!-- ================================================ BindToTextBox.xaml (c) 2006 by Charles Petzold ============================================= === --> <Grid xmlns="http://schemas.microsoft.com/winfx /2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx /2006/xaml"> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Label Grid.Row="0" Grid.Column="0" Margin="24 24 24 0" Content="Source TextBox Controls" /> <Label Grid.Row="0" Grid.Column="1" Margin="24 24 24 0" Content="Target TextBox Controls" /> <TextBox Grid.Row="1" Grid.Column="0" Name="txtbox1" Margin="24" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="24" Text="{Binding ElementName=txtbox1, Path=Text, Mode=TwoWay}" /> <TextBox Grid.Row="2" Grid.Column="0" Name="txtbox2" Margin="24" /> <TextBox Grid.Row="2" Grid.Column="1" Margin="24" Text="{Binding ElementName=txtbox2, Path=Text, Mode=TwoWay}" /> <TextBox Grid.Row="3" Grid.Column="0" Name="txtbox3" Margin="24" /> <TextBox Grid.Row="3" Grid.Column="1" Margin="24" Text="{Binding ElementName=txtbox3, Path=Text, Mode=TwoWay}" /> </Grid>



Give the upper-left TextBox the input focus by clicking on it. This is the source of a two-way binding. As you type something in, you'll see that what you type also appears in the bound target TextBox on the right. Now press the Tab key. Now the target TextBox on the right has input focus. Type something in. Although the text you type appears in the TextBox that has the focus, the binding source TextBox on the left isn't updated with the new text. Yet, the binding is explicitly a two-way binding.

Press the Tab key again. As the target TextBox on the right loses input focus, the entire contents of the target are now transferred to the bound source TextBox on the left. It is truly a two-way binding, except that the source isn't updated from the target until the target loses input focus. You can continue experimenting with this program by pressing the Tab key and entering text into each TextBox.

In the default case, when the Text property of a TextBox is a target property of a two-way binding, the binding source is not updated from the target until the target loses input focus. For this to make sense, consider the source TextBox controls on the left of the BindToTextBox program to be not TextBox controls at all, but actually some database that the program is interacting with. The TextBox controls on the right allow you to view and edit the underlying database. When something in the database changes, you want to see that change right away. That's why the Text property of a target TextBox is updated with every change in the source.

However, as you enter text into the TextBox to change a field in the underlying database, do you want the database to be updated with every keystroke you press, including all the mistakes and backspaces and cat-jumps-on-the-keyboard incidents? Probably not. Only when you're finished entering the text do you want the source to be updated, and the only simple way that the binding can determine when you're finished is when the TextBox loses input focus.

You can change this behavior by setting the UpdateSourceTrigger property of the Binding. You can set it to a member of the UpdateSourceTrigger enumeration, either LostFocus (the default for the Text property of a TextBox), PropertyChanged (which is normal for most properties), or Explicit, which requires special action by the program for the changes to be reflected in the source.

Try changing one of the bindings in BindToTextBox.xaml to this:

Text="{Binding ElementName=txtbox1, Path=Text, Mode=TwoWay,                                 UpdateSourceTrigger=PropertyChanged}" 


Now the source TextBox changes with every keystroke typed in the target TextBox.

The UpdateSourceTrigger.Explicit option requires more work. A program using this option has to prepare by calling GetBindingExpression (a method defined by FrameworkElement) on the element on which you've defined the binding. The argument is the DependencyProperty involved in the binding:

BindingExpression bindexp =                 txtboxSource.GetBindingExpression(TextBox.TextProperty); 


When you want to update the source from the target (perhaps upon pressing a button labeled "Update"), call

bindexp.UpdateSource(); 


This call can't override the binding mode. The binding mode must be TwoWay or OneWayToSource or the call will be ignored.

If we want to start thinking about data bindings in terms of databases and other external classes and objects, we need to move away from using the ElementName property. ElementName is great when you need to bind two elements, and you can have lots of fun doing so, but it's necessary to move beyond that limitation.

Instead of indicating the source of a binding with ElementName, most of the bindings in the remainder of this chapter will use the Source property. The Source property refers to an object, and Path continues to refer to a property (or a series of chained properties) of that object.

One possibility for Source is an x:Static markup extension. As you'll recall from Chapter 21, x:Static lets a XAML file reference a static field or property in a class. In some cases (such as with the static properties of the Environment class) you can use x:Static by itself to get those properties. However, it's possible that what you really need is a property of the object referenced by the static property. In that case, you need a binding.

For example, consider the DayNames property of the DateTimeFormatInfo class in the System.Globalization namespace. DayNames is an instance property that returns an array of seven strings for the seven days of the week. To access DayNames you need an object of type DateTimeFormatInfo, and the DateTimeFormatInfo class itself provides two static properties to obtain an instance of the class. The static DateTimeFormatInfo.InvariantInfo property returns an instance of DateTimeFormatInfo applicable for an "invariant" culture (that is, the culture that Microsoft Corporation is part of), while DateTimeFormatInfo.CurrentInfo returns an instance appropriate for the user's own culture.

In C#, the following code stores an array of strings with the days of the week in the current user's own language:

string[] strDayNames = DateTimeFormatInfo.CurrentInfo.DayNames; 


In XAML, you must define a binding to get this information. The Source property is an x:Static markup extension for the static property DateTimeFormatInfo.CurrentInfo. The Path property of the binding is the string "DayNames." The XAML file also requires an XML namespace declaration to associate the System.Globalization namespace with a prefix (g, for example).

DaysOfWeek.xaml

[View full width]

<!-- ============================================= DaysOfWeek.xaml (c) 2006 by Charles Petzold ============================================= --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:g="clr-namespace:System .Globalization;assembly=mscorlib"> <!-- Bind ListBox ItemsSource to DayNames property of DateTimeFormatInfo. --> <ListBox Name="lstbox" HorizontalAlignment="Center" Margin="24" ItemsSource="{Binding Source={x:Static g :DateTimeFormatInfo.CurrentInfo}, Path=DayNames, Mode=OneTime}" /> <!-- Bind TextBlock Text to SelectedItem property of ListBox. --> <TextBlock HorizontalAlignment="Center" Text="{Binding ElementName=lstbox, Path=SelectedItem, Mode=OneWay}" /> </StackPanel>



The file actually has two bindings. The first obtains the DayNames array and assigns it to the ItemsSource property of a ListBox, thereby filling the ListBox with the days of the week. The second binding displays the currently selected item from the ListBox in a TextBlock.

The first binding in DaysOfWeek.xaml has its Mode property set to OneTime. It really can't be anything else because there's no way for the DateTimeFormatInfo class to issue a notification when the days of the week change. The file uses the Binding syntax to access a particular property, but it's only interested in getting that data just once.

Bindings generally involve a continual transfer of information from the binding source to the target, which requires that a mechanism exist to notify the target when the source property has changed. You get this mechanism for free when the source is a dependency property. Earlier in this chapter I showed you a class named SimpleElement that inherited from FrameworkElement and did little else beyond defining a dependency property named Number. This class was able to participate in data binding without any additional overhead.

It is not necessary to inherit from FrameworkElement to define dependency properties. If the source of your data is not a visual object, you can instead inherit from DependencyObject. That's the class that defines the SetValue and GetValue methods you need to implement dependency properties.

For example, suppose you wanted to write a little digital clock program. You want to do the visuals in XAML (of course) but a clock also requires a source of the current date and time, and a notification mechanism when the time changes. (We'll say every second to keep it simple.) Here's a class that inherits from DependencyObject and defines a property named DateTime backed by the dependency property DateTimeProperty.

ClockTicker1.cs

[View full width]

//--------------------------------------------- // ClockTicker1.cs (c) 2006 by Charles Petzold //--------------------------------------------- using System; using System.Windows; using System.Windows.Threading; namespace Petzold.DigitalClock { public class ClockTicker1 : DependencyObject { // Define DependencyProperty. public static DependencyProperty DateTimeProperty = DependencyProperty.Register ("DateTime", typeof(DateTime), typeof(ClockTicker1)); // Expose DependencyProperty as CLR property. public DateTime DateTime { set { SetValue(DateTimeProperty, value); } get { return (DateTime) GetValue (DateTimeProperty); } } // Constructor sets timer. public ClockTicker1() { DispatcherTimer timer = new DispatcherTimer(); timer.Tick += TimerOnTick; timer.Interval = TimeSpan.FromSeconds(1); timer.Start(); } // Timer event handler sets DateTime property. void TimerOnTick(object sender, EventArgs args) { DateTime = DateTime.Now; } } }



To me, the most interesting part of the ClockTicker1 class is the event handler for DispatcherTimer. It merely sets its own DateTime property to DateTime.Now, and it does so with a confident assurance that a call to SetValue (for which is what setting the DateTime property precipitates) will have some kind of profound effect outside the class.

To see what that effect is, let's look at the XAML portions of the DigitalClock project. The application definition file is first.

DigitalClockApp.xaml

[View full width]

<!-- === =============================================== DigitalClockApp.xaml (c) 2006 by Charles Petzold ============================================= ===== --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="DigitalClockWindow.xaml" />



To define the data binding, the Window file needs to reference the ClockTicker1 class. ClockTicker1 does not derive from FrameworkElement, so the Binding can't use the ElementName property. It must instead use Source. In this case, you want Source to reference an object of type ClockTicker1, and you already know how to reference an object in markup: You define the object in a Resources section and access it with StaticResource.

DigitalClockWindow.xaml

[View full width]

<!-- === ================================================== DigitalClockWindow.xaml (c) 2006 by Charles Petzold ============================================= ======== --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold.DigitalClock" Title="Digital Clock" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" FontFamily="Bookman Old Style" FontSize="36pt"> <Window.Resources> <src:ClockTicker1 x:Key="clock" /> </Window.Resources> <Window.Content> <Binding Source="{StaticResource clock}" Path="DateTime" /> </Window.Content> </Window>



The Resources section has a definition for ClockTicker1 and associates it with a key of "clock." The Content property of the window is set to a Binding for which the Source property is the StaticResource named "clock" and the Path references the DateTime property in ClockTicker1.

The program displays the date and time in its default format, which will vary by regional preferences but only show the short version of the date and time. No seconds are displayed, so you'll have to wait up to a minute to assure yourself that ClockTicker1 is actually doing sufficient work for the data binding to succeed.

Although defining a dependency property is probably the preferred way to enable a class to successfully serve as the source of a data binding, it's not the only approach. The more traditional approach is defining an event for the job. The trick is defining the event in such a way that the data-binding logic in WPF successfully locates it.

If you define a property named Whatever that you want to use as the source of a data binding, you can define an event named WhateverChanged, and WPF will successfully locate the event. You could modify the ClockTicker2 class for such an event by first removing the DateTimeProperty dependency property. Then define a public event named DateTimeChanged:

public event EventHandler DateTimeChanged; 


Change the definition of the DateTime property to this:

public DateTime DateTime {     get { return DateTime.Now; } } 


The constructor remains the same, but the TimerOnTick event handler is a little larger. It is responsible for firing the DateTimeChanged event:

void TimerOnTick(object sender, EventArgs args) {     if (DateTimeChanged != null)         DateTimeChanged(this, new EventArgs()); } 


And that's it.

The WPF data-binding logic will also successfully locate an event if it's defined in a class that implements the INotifyPropertyChanged interface. This interface requires the class to define an event named PropertyChanged based on the PropertyChangedEventHandler delegate:

public event PropertyChangedEventHandler PropertyChanged; 


When the class fires the PropertyChanged event, the first argument is this (as usual) and the second argument is an object of type PropertyChangedEventArgs, which inherits from EventArgs and defines an additional property named PropertyName of type string. This PropertyName property identifies the property being changed. PropertyChangedEventArgs also defines a constructor with a string argument to set PropertyName.

So, if a class defines a property named DateTime and wants to fire the PropertyChanged event signaling that DateTime has changed, it can do so like this:

PropertyChanged(this, new PropertyChangedEventArgs("DateTime")); 


This is a good approach for a class that has lots of changeable properties, because the single PropertyChanged event can handle them all. But it can also work for just one changeable property. This ClockTicker2 class demonstrates how to use the PropertyChanged event to signal a change in the DateTime property.

ClockTicker2.cs

[View full width]

//--------------------------------------------- // ClockTicker2.cs (c) 2006 by Charles Petzold //--------------------------------------------- using System; using System.ComponentModel; using System.Windows; using System.Windows.Threading; namespace Petzold.FormattedDigitalClock { public class ClockTicker2 : INotifyPropertyChanged { // Event required by INotifyPropertyChanged interface. public event PropertyChangedEventHandler PropertyChanged; // Public property. public DateTime DateTime { get { return DateTime.Now; } } // Constructor sets timer. public ClockTicker2() { DispatcherTimer timer = new DispatcherTimer(); timer.Tick += TimerOnTick; timer.Interval = TimeSpan.FromSeconds(1); timer.Start(); } // Timer event handler triggers PropertyChanged event. void TimerOnTick(object sender, EventArgs args) { if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs ("DateTime")); } } }



The ClockTicker2 class is part of a new project named FormattedDigitalClock that not only has this alternative approach to signaling a change in the DateTime property but also gives the writer of the XAML code more flexibility in formatting the DateTime object.

The format of the date and time that the previous DigitalClock program displays is the result of calling ToString on the DateTime object. However, the DateTime class defines an overload of the ToString method that accepts a formatting string argument and can render the date and time in a variety of formats. (These are documented in the opening page of the DateTimeFormatInfo class.) For example, the string "T" displays the time with seconds, while the string "t" displays the time without seconds. When displaying a DateTime object with Console.Write or converting it into a string with String.Format, you can use one of these formatting strings in an individual format itemfor example, "{2:T}" if the DateTime object is the third object to be formatted.

It would be nice to use these formatting strings directly in a XAML file, and that's the sole purpose of this FormattedTextConverter class. The class implements the IValueConverter interface so that you can use it for the Converter property in a data binding. The ConverterParameter property of the binding specifies the formatting string in the same way as if it were used in String.Format, because that's what the Convert method uses to convert the object to a string.

FormattedTextConverter.cs

[View full width]

//--------- ---------------------------------------------- // FormattedTextConverter.cs (c) 2006 by Charles Petzold //------------------------------------------------ ------- using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace Petzold.FormattedDigitalClock { public class FormattedTextConverter : IValueConverter { public object Convert(object value, Type typeTarget, object param, CultureInfo culture) { if (param is string) return String.Format(param as string, value); return value.ToString(); } public object ConvertBack(object value, Type typeTarget, object param, CultureInfo culture) { return null; } } }



You can use FormattedTextConverter with any type of object. It makes the most sense for those objects that have multiple formatting options (such as integers or floating-point numbers) but you can also use it to adorn the object with other text. For example, the param argument to Convert could be "-->{0}<--".

The FormattedDigitalClockWindow.xaml file sets the ConverterParameter property to "... {0:T} ..." so that the time with seconds is surrounded by ellipses.

FormattedDigitalClockWindow.xaml

[View full width]

<!-- === =========== ================================================ FormattedDigitalClockWindow.xaml (c) 2006 by Charles Petzold ============================================= ================= --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold .FormattedDigitalClock" Title="Formatted Digital Clock" SizeToContent="WidthAndHeight" ResizeMode="CanMinimize" FontFamily="Bookman Old Style" FontSize="36pt"> <Window.Resources> <src:ClockTicker2 x:Key="clock" /> <src:FormattedTextConverter x:Key="conv" /> </Window.Resources> <Window.Content> <Binding Source="{StaticResource clock}" Path="DateTime" Converter="{StaticResource conv}" ConverterParameter="... {0:T} ..." /> </Window.Content> </Window>



Watch out: The curly brackets used in the formatting specifications are the same curly brackets used to denote XAML markup extensions! You cannot set ConveterParameter property like this (without periods or spaces, unlike my previous example):

ConverterParameter="{0:T}" 


If that's what you want, you must preface it with an escape sequence that consists of the left and right curly brackets together:

ConverterParameter="{}{0:T}" 


Finally, here's the application definition file for the FormattedDigitalClock project.

FormattedDigitalClockApp.xaml

[View full width]

<!-- === =========== ============================================= FormattedDigitalClockApp.xaml (c) 2006 by Charles Petzold ============================================= ============== --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="FormattedDigitalClockWindow.xaml" />



Of course, if you're really enthusiastic about using .NET formatting strings in your XAML, you simply won't be satisfied with converting one object at a time because Console.Write and String.Format aren't limited in that way. It would be nice if you could format multiple objects into a single string.

This sounds like a job for MultiBinding. As you'll recall from the ColorScroll program, you can bind multiple sources into a single target by means of a converter class that implements the IMultiValueConverter interface. The class that follows assumes the parameter named param is a formatting string and the array of objects named value are the objects to be formatted. These are both just passed to String.Format.

FormattedMultiTextConverter.cs

[View full width]

//--------- --------------------------------------------------- // FormattedMultiTextConverter.cs (c) 2006 by Charles Petzold //------------------------------------------------ ------------ using System; using System.Globalization; using System.Windows; using System.Windows.Data; namespace Petzold.EnvironmentInfo2 { public class FormattedMultiTextConverter : IMultiValueConverter { public object Convert(object[] value, Type typeTarget, object param, CultureInfo culture) { return String.Format((string) param, value); } public object[] ConvertBack(object value, Type[] typeTarget, object param, CultureInfo culture) { return null; } } }



To demonstrate how this works, let's revisit a problem from the previous chapter: formatting static properties from the Environment class. The earlier solution used a separate TextBlock for every little piece of text. The EnvironmentInfo2 program shown here uses a single TextBlock with its Text property bound to a MultiBinding object. The Converter property references the FormattedMultiTextConverter class, of course.

EnvironmentInfo2Window.xaml

[View full width]

<!-- === ====================================================== EnvironmentInfo2Window.xaml (c) 2006 by Charles Petzold ============================================= ============ --> <Window xmlns="http://schemas.microsoft.com/winfx/ 2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" xmlns:s="clr-namespace :System;assembly=mscorlib" xmlns:src="/books/4/266/1/html/2/clr-namespace:Petzold .EnvironmentInfo2" Title="Environment Info"> <Window.Resources> <src:FormattedMultiTextConverter x :Key="conv" /> </Window.Resources> <TextBlock> <TextBlock.Text> <MultiBinding Converter="{StaticResource conv}" ConverterParameter= "Operating System Version: {0} &#x000A;.NET Version: {1} &#x000A;Machine Name: {2} &#x000A;User Name: {3} &#x000A;User Domain Name: {4} &#x000A;System Directory: {5} &#x000A;Current Directory: {6} &#x000A;Command Line: {7}" > <Binding Source="{x:Static s :Environment.OSVersion}" /> <Binding Source="{x:Static s :Environment.Version}" /> <Binding Source="{x:Static s :Environment.MachineName}" /> <Binding Source="{x:Static s :Environment.UserName}" /> <Binding Source="{x:Static s :Environment.UserDomainName}" /> <Binding Source="{x:Static s :Environment.SystemDirectory}" /> <Binding Source="{x:Static s :Environment.CurrentDirectory}" /> <Binding Source="{x:Static s :Environment.CommandLine}" /> </MultiBinding> </TextBlock.Text> </TextBlock> </Window>



The ConverterParameter is a long string that spills over onto multiple lines. To avoid excessive white space in this string, I aligned it against the left margin. Notice that line feeds are XML style ("&#x000A;") rather than C# style, and they appear at the left margin, again to avoid white space from creeping into where it's not welcome.

Also notice that the Binding elements don't require Path attributes because we're interested in the Source itself rather than the property of the Source. Alternatively, you could specify the Path with an empty string:

Path="" 


Here's the application definition file for the project.

EnvironmentInfo2App.xaml

[View full width]

<!-- === =================================================== EnvironmentInfo2App.xaml (c) 2006 by Charles Petzold ============================================= ========= --> <Application xmlns="http://schemas.microsoft.com/ winfx/2006/xaml/presentation" StartupUri="EnvironmentInfo2Window .xaml" />



So far you've seen Binding markup using ElementName and Source. The only other alternative is RelativeSource, which lets you reference an ancestor element in the element tree, or the element itself. This RelativeSourceDemo.xaml file shows three bindings using RelativeSource.

RelativeSourceDemo.xaml

[View full width]

<!-- === ================================================== RelativeSourceDemo.xaml (c) 2006 by Charles Petzold ============================================= ======== --> <StackPanel xmlns="http://schemas.microsoft.com /winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com /winfx/2006/xaml" TextBlock.FontSize="12" > <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <TextBlock Text="This TextBlock has a FontFamily of " /> <TextBlock Text="{Binding RelativeSource={RelativeSource self}, Path=FontFamily}" /> <TextBlock Text=" and a FontSize of " /> <TextBlock Text="{Binding RelativeSource={RelativeSource self}, Path=FontSize}" /> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <TextBlock Text="This TextBlock is inside a StackPanel with " /> <TextBlock Text= "{Binding RelativeSource={RelativeSource AncestorType={x:Type StackPanel}}, Path=Orientation}" /> <TextBlock Text=" orientation" /> </StackPanel> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center"> <TextBlock Text="The parent StackPanel has " /> <TextBlock Text= "{Binding RelativeSource={RelativeSource AncestorType={x:Type StackPanel}, AncestorLevel=2}, Path=Orientation}" /> <TextBlock Text=" orientation" /> </StackPanel> </StackPanel>



Nothing in this file has a Name attribute. The first binding includes the RelativeSource markup extension that lets a binding reference the same element the binding occurs in:

RelativeSource={RelativeSource self} 


In this example, the TextBlock displays its own font information. Later on in the program another TextBlock displays the Orientation property of the StackPanel it belongs to using a RelativeSource markup extension of

RelativeSource={RelativeSource AncestorType={x:Type StackPanel}} 


However, that StackPanel is nested within another StackPanel. To get at another StackPanel further up the chain requires the AncestorLevel property, as the third binding demonstrates:

[View full width]

RelativeSource={RelativeSource AncestorType={x :Type StackPanel}, AncestorLevel=2}



This chapter has demonstrated several different manifestations of the Binding element and several ways you can use it. Data bindings also play an important role in programs that must display data from a database of some sort and in programs that let a user modify the data or enter new data. Chapter 26 explores the techniques you can use in writing such applications.




Applications = Code + Markup. A Guide to the Microsoft Windows Presentation Foundation
Applications = Code + Markup: A Guide to the Microsoft Windows Presentation Foundation (Pro - Developer)
ISBN: 0735619573
EAN: 2147483647
Year: 2006
Pages: 72

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net