Saturday, October 10, 2009

A couple basic WPF databinding examples.

The databinding options available to us in WPF are numerous and powerful.  We can bind a UI element to an object, a list, another UI element, a collection, an ADO.Net object and the list goes on and on.  You can bind declaratively or programmatically.  You can bind one way, two way, or one time. 

Let’s take a look at just a couple of these databinding options.  Say we are creating a user preference page and from this page we want to allow the user to specify the foreground color of a textbox by typing an ARGB number in a textbox.  Further, we want the color of the text in the textbox to represent the color that was typed into the textbox.  We are trying to bind the foreground property of the textbox to the value of the textbox’s Text property.

Here is what our app looks like:

image

Here is the code that will pull that off:



Code Snippet



  1. <Window x:Class="WpfSelfBinding.Window1"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    Title="Window1" Height="300" Width="300">
  5.     <Grid>
  6.         <Label Height="28" Margin="12,47,0,0" Name="label1" VerticalAlignment="Top"
  7.                HorizontalAlignment="Left" Width="100">Text Color</Label>
  8.         <TextBox x:Name="tbColor" Height="23" Margin="110,49,48,0" VerticalAlignment="Top"
  9.                  Foreground="{Binding Text,RelativeSource={RelativeSource Self}}" >
  10.         </TextBox>
  11.     </Grid>
  12. </Window>




The key bit of XAML we need to focus on is the textbox’s Foreground property:

Foreground="{Binding Text,RelativeSource={RelativeSource Self}}"

This binding is telling the app to bind the Textbox’s Foreground property to the value of the Text property of myself.  If you run the App you will notice that as you change the value of the textbox, the color of the text will also change.

Now I can’t get even close to the color I want by using ARGB so let’s see if we can make the user experience a little more pleasant.  let’s give the user the option of selecting the color from a color picker.  WPF does not have a color picker dialog so we will have to use the Windows Forms library.  We’ll modify the app a little bit and add a button that will  launch the color picker and populate the textbox with the result.

Here is the new UI:

image

The new XAML:



Code Snippet



  1. <Window x:Class="WpfSelfBinding.Window1"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    Title="Window1" Height="300" Width="300">
  5.     <Grid>
  6.         <Label Height="28" Margin="12,47,0,0" Name="label1" VerticalAlignment="Top"
  7.                HorizontalAlignment="Left" Width="100">Text Color</Label>
  8.         <TextBox x:Name="tbColor" Height="23" Margin="110,49,48,0" VerticalAlignment="Top"
  9.                  Foreground="{Binding Text,RelativeSource={RelativeSource Self}}" >
  10.         </TextBox>
  11.         <Button x:Name="btnColor" Height="23" HorizontalAlignment="Right" Margin="0,49,24,0"
  12.                VerticalAlignment="Top" Width="18" Click="btnColor_Click">...</Button>
  13.     </Grid>
  14. </Window>




And the code behind:



Code Snippet



  1. using System.Windows;
  2. using System.Windows.Media;
  3. namespace WpfSelfBinding
  4. {
  5.     /// <summary>
  6.     /// Interaction logic for Window1.xaml
  7.     /// </summary>
  8.     public partial class Window1 : Window
  9.     {
  10.         public Window1()
  11.         {
  12.             InitializeComponent();
  13.         }
  14.         private void btnColor_Click(object sender, RoutedEventArgs e)
  15.         {
  16.             System.Windows.Forms.ColorDialog colorDialog = new System.Windows.Forms.ColorDialog();
  17.             System.Windows.Forms.DialogResult result = colorDialog.ShowDialog();
  18.             if (result == System.Windows.Forms.DialogResult.OK)
  19.             {
  20.                 tbColor.Text = SysDrawColorWinMediaColor(colorDialog.Color).ToString();
  21.             }
  22.         }
  23.         private Color SysDrawColorWinMediaColor(System.Drawing.Color color)
  24.         {
  25.             System.Windows.Media.Color bColor;
  26.             int i = color.ToArgb();
  27.             byte a = (byte)((i >> 24) & 255);
  28.             byte r = (byte)((i >> 16) & 255);
  29.             byte g = (byte)((i >> 8) & 255);
  30.             byte b = (byte)(i & 255);
  31.             bColor = Color.FromArgb(a, r, g, b);
  32.             return bColor;
  33.         }
  34.     }
  35. }




Two things are worth noting in the code.  Rather than importing the System.Windows.Forms namespace, we just use the fully qualified class name.  This is because there are several conflicts between WPF classes and windows forms classes in the  System.Windows.Forms namespace.  The second thing we have to do is convert the color returned by the color picker (System.Drawing.Color) to a System.Windows.Media color.  We use bit shifting and a bit mask to pull this off in the SysDrawColorWinMediaColor function. 

Rather than using the color dialog result to directly set the text property of the textbox let’s say we want to populate an object ( so we can persist the selection to a database or isolated storage).  It would be cool if we could bind directly to this class.

The UI looks exactly the same but here is our new class



Code Snippet



  1. using System.ComponentModel;
  2. namespace WpfSelfBinding
  3. {
  4.     class BindableColorClass:INotifyPropertyChanged
  5.     {
  6.         private string textColor = "#FF0F0FF0";
  7.         public event PropertyChangedEventHandler PropertyChanged;
  8.         protected void OnPropertyChanged(string name)
  9.         {
  10.             PropertyChangedEventHandler handler = PropertyChanged;
  11.             if (handler != null)
  12.             {
  13.                 handler(this, new PropertyChangedEventArgs(name));
  14.             }
  15.         }
  16.         public string TextColor
  17.         {
  18.             get { return textColor; }
  19.             set
  20.             {
  21.                 if (textColor != value)
  22.                 {
  23.                     textColor = value;
  24.                     OnPropertyChanged("TextColor");
  25.                 }
  26.             }
  27.         }
  28.     }
  29. }




The key here is that our class implements INotifyPropertyChanged.  We raise the PropertyChanged event when the TextColor property changes.  This event is what wires our class into WPF’s Data Change Notification mechanism.

Here is the new Window1 code:



Code Snippet



  1. using System.Windows;
  2. using System.Windows.Media;
  3. namespace WpfSelfBinding
  4. {
  5.     /// <summary>
  6.     /// Interaction logic for Window1.xaml
  7.     /// </summary>
  8.     public partial class Window1 : Window
  9.     {
  10.         BindableColorClass colorClass = new BindableColorClass();
  11.         public Window1()
  12.         {
  13.             InitializeComponent();
  14.             tbColor.DataContext = this.colorClass;
  15.         }
  16.         private void btnColor_Click(object sender, RoutedEventArgs e)
  17.         {
  18.             System.Windows.Forms.ColorDialog colorDialog = new System.Windows.Forms.ColorDialog();
  19.             System.Windows.Forms.DialogResult result = colorDialog.ShowDialog();
  20.             if (result == System.Windows.Forms.DialogResult.OK)
  21.             {
  22.                 colorClass.TextColor = SysDrawColorWinMediaColor(colorDialog.Color).ToString();
  23.             }
  24.         }
  25.         private Color SysDrawColorWinMediaColor(System.Drawing.Color color)
  26.         {
  27.             System.Windows.Media.Color bColor;
  28.             int i = color.ToArgb();
  29.             byte a = (byte)((i >> 24) & 255);
  30.             byte r = (byte)((i >> 16) & 255);
  31.             byte g = (byte)((i >> 8) & 255);
  32.             byte b = (byte)(i & 255);
  33.             bColor = Color.FromArgb(a, r, g, b);
  34.             return bColor;
  35.         }
  36.     }
  37. }




There are a couple things to pay particular attention to here.  First we have a class variable referencing an instance of our BindableColorClass.  Next, notice in our btnColor_Click event we are updating the TextColor of the BindableColorClass, NOT the textbox’s text value.  Finally notice that on class construction we are setting the textbox’s DataContext to our instance of the BindableColorClass.

Here is the ne XAML:



Code Snippet



  1. <Window x:Class="WpfSelfBinding.Window1"
  2.    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  3.    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  4.    Title="Window1" Height="300" Width="300">
  5.     <Grid>
  6.         <Label Height="28" Margin="12,47,0,0" Name="label1" VerticalAlignment="Top"
  7.                HorizontalAlignment="Left" Width="100">Background Color</Label>
  8.         <TextBox x:Name="tbColor" Height="23" Margin="110,49,48,0" VerticalAlignment="Top"
  9.                  Foreground="{Binding Text,RelativeSource={RelativeSource Self}}" >
  10.             <TextBox.Text>
  11.                 <Binding Path="TextColor" Mode="TwoWay"/>
  12.             </TextBox.Text>
  13.         </TextBox>
  14.         <Button x:Name="btnColor" Height="23" HorizontalAlignment="Right" Margin="0,49,24,0"
  15.                VerticalAlignment="Top" Width="18" Click="btnColor_Click">...</Button>
  16.     </Grid>
  17. </Window>




The XAML we want to focus on:

<TextBox.Text>
    <Binding Path="TextColor" Mode="TwoWay"/>
</TextBox.Text>

This tells WPF to bind the TextBox.Text property to the “TextColor” property.  The “TextColor” property of what??  Our background property uses the source property to tell WPF which item’s text property to bind to but we didn’t specify one here.  Since we didn’t set the source property, WPF is going to look for a DataContext which we DO set in our code behind. 

tbColor.DataContext = this.colorClass;

WPF knows to bind the textbox’s text property to TextColor property of the windows colorClass.

The intention is to persist the BindableColorClass eventually so we need the TextColor property to be changed either from the color picker or by typing the ARGB color into the TextBox.  To pull this off all we have to do is set the binding Mode to TwoWay.  Set a break point on the TextColor getter and setter to see when they fire.  If you are typing the ARGB color into the textbox by hand you will need to tab out of the textbox to get the change to take place.  This is the default textbox binding behavior.

No comments:

Post a Comment