Feeds:
Posts
Comments

The BindingBase.StringFormat property was included with the .NET framework 4.0 and is quite a handy feature. I recently found myself going back through old projects to remove binding converters and replace their logic with the StringFormat property. There is a decent example on the MSDN here. However, I felt like expanding on it a bit so here goes.

First I have created a Person class that has some basic properties like FirstName, LastName, Age, etc. Here is the code for the Person class and enum it uses.

using System;
using System.ComponentModel;

namespace BindingStringFormat
{
     public enum Sex
     {
          male,
          female
     }

     public class Person : INotifyPropertyChanged
     {
          #region Properties

          public string FirstName
          {
               get { return _firstName; }
               set { _firstName = value; OnPropertyChanged("FirstName"); }
          }private string _firstName;

          public string LastName
          {
               get { return _lastName; }
               set { _lastName = value; OnPropertyChanged("LastName"); }
          }private string _lastName;

          public int Age
          {
               get { return _age; }
               set { _age = value; OnPropertyChanged("Age"); }
          }private int _age;

          public DateTime BirthDate
          {
               get { return _birthDate; }
               set { _birthDate = value; OnPropertyChanged("BirthDate"); }
          }private DateTime _birthDate;

          public Sex Sex
          {
               get { return _sex; }
               set { _sex = value; OnPropertyChanged("Sex"); }
          }private Sex _sex;

          #endregion

          #region Constructor(s)

          public Person() { }

          public Person(string lastName, string firstName, int age, DateTime birthDate, Sex sex)
          {
               this.FirstName = firstName;
               this.LastName = lastName;
               this.Age = age;
               this.BirthDate = birthDate;
               this.Sex = sex;
          }

          #endregion

          #region INotifyPropertyChanged Members

          public event PropertyChangedEventHandler PropertyChanged;

          void OnPropertyChanged(string propName)
          {
               if (this.PropertyChanged != null)
                    this.PropertyChanged(this, new PropertyChangedEventArgs(propName));
          }

          #endregion
     }
}

Next is the MainWindow. I have created a public Person property in the code behind and set the DataContext of the MainWindow.xaml to the Person property. Here is the code for that.

using System;
using System.Windows;

namespace BindingStringFormat
{
     /// <summary>
     /// Interaction logic for MainWindow.xaml
     /// </summary>
     public partial class MainWindow : Window
     {
          public Person Person
          {
               get
               {
                    if (_person == null)
                         _person = new Person("Bob", "Billy", 29, new DateTime(1978, 3, 9), Sex.male);

                    return _person;
               }
               set
               {
                    _person = value;
               }
          }private Person _person;

          public MainWindow()
          {
               InitializeComponent();
               this.DataContext = Person;
          }
     }
}

Last but not least is the MainWindow.xaml which is where the StringFormat syntax lies. In this example you will notice I have bound a string, integer, enum, and date to some TextBlocks and customized their display with the StringFormat property. I especially like the ability to use StringFormat with MultiBinding, and as I said before, this should help you get rid of some now obsolete BindingConverters. Enjoy!


<Window x:Class="BindingStringFormat.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

   <Grid Margin="25">
        <StackPanel>
             <TextBlock Margin="5">
                  <TextBlock.Text>
                       <MultiBinding StringFormat="Hello {0} {1}!">
                            <Binding Path="FirstName"/>
                            <Binding Path="LastName"/>
                       </MultiBinding>
                  </TextBlock.Text>
             </TextBlock>
             <TextBlock Text="{Binding Path=Age, StringFormat='You are {0} years old.'}" Margin="5"/>
             <TextBlock Text="{Binding Path=Sex, StringFormat='You are a {0}.'}" Margin="5"/>
             <TextBlock Text="{Binding Path=BirthDate, StringFormat='You were born on {0:MM/dd/yyyy}'}" Margin="5"/>
        </StackPanel>
   </Grid>

</Window>

Here is what you should see when you run the application:

BindingBase.StringFormat

Advertisements

Today I’m going to show how to bind a collection of objects to a TreeView while using HierarchicalDataTemplates. The root nodes in the TreeView will be Continents. Each continent will have a collection of countries, while each country will have a collection of cities. There are a few additional properties associated with each of these object. For example, cities have populations, continents have a size in square miles, etc. Here are the three classes I created for the project.

public class Continent
{
    public Continent() { }

    public string Name { get; set; }
    public int Size { get; set; }   //in sq miles
    public ObservableCollection<Country> Countries { get; set; }
}

public class Country
{
    public Country() { }

    public string Name { get; set; }
    public int Population { get; set; }
    public ObservableCollection<City> Cities { get; set; }
}

public class City
{
    public City() { }

    public string Name { get; set; }
    public int Population { get; set; }
}

Now, here is all of the code for my MainWindow.cs which contains the code for populating my ObservableCollection<Continent>.

public partial class MainWindow : Window
{
    public ObservableCollection<Continent> Continents { get; set; }

    public MainWindow()
    {
       InitializeComponent();
       Continents = CreateWorld();
       this.DataContext = Continents;
    }

    private ObservableCollection<Continent> CreateWorld()
    {
       ObservableCollection<Continent> world = new ObservableCollection<Continent>();

       Continent northAmerica = new Continent()
       {
          Name = "North America",
          Size = 9540000,
          Countries = new ObservableCollection<Country>()
          {
             new Country()
             {
                Name = "Canada",
                Population = 15000000,
                Cities = new ObservableCollection<City>()
                {
                   new City() { Name = "Ottowa", Population = 812179 },
                   new City() { Name = "Vancouver", Population = 347009 },
                   new City() { Name = "Quebec", Population = 457339 }
                }
             },
             new Country()
             {
                Name = "United States",
                Population = 3456700,
                Cities = new ObservableCollection<City>()
                {
                   new City() { Name = "Denver", Population = 124897 },
                   new City() { Name = "Boston", Population = 234659 },
                   new City() { Name = "New York", Population = 354730 },
                   new City() { Name = "Los Angeles", Population = 423100 }
                }
             },
             new Country()
             {
                Name = "Mexico",
                Population = 34000234,
                Cities = new ObservableCollection<City>()
                {
                   new City() { Name = "Mexico City", Population = 21000000 },
                   new City() { Name = "Tijuana", Population = 1594000 },
                   new City() { Name = "Puebla", Population = 1590256 }
                }
             }
          }
       };

       Continent southAmerica = new Continent()
       {
          Name = "South America",
          Size = 6879000,
          Countries = new ObservableCollection<Country>()
          {
             new Country()
             {
                Name = "Brazil",
                Population = 4210000,
                Cities = new ObservableCollection<City>()
                {
                   new City() { Name = "Sao Paulo", Population = 11500000 },
                   new City() { Name = "Rio de Janeiro", Population = 7186000 },
                   new City() { Name = "Fortaleza", Population = 2505054 }
                }
             },
             new Country()
             {
                Name = "Argentina",
                Population = 5673420,
                Cities = new ObservableCollection<City>()
                {
                   new City() { Name = "Buenos Aires", Population = 5698000 },
                   new City() { Name = "Chaco", Population = 7845000 }
                }
             }
          }
       };

       world.Add(northAmerica);
       world.Add(southAmerica);
       return world;
    }
}

Finally, here is the MainWindow.xaml that uses the HierarchicalDataTemplates to show content at each level of TreeViewItems in the tree. Please note how the multibinding utilizes Binding.StringFormat to show cities and their population in the deepest node.

<Window x:Class="TreeViewBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="331" Width="289"
        xmlns:local="clr-namespace:TreeViewBinding">

    <Window.Resources>
        <DataTemplate DataType="{x:Type local:City}">
            <TextBlock>
                <TextBlock.Text>
                    <MultiBinding StringFormat="{}{0} ({1})">
                        <Binding Path="Name"/>
                        <Binding Path="Population"/>
                    </MultiBinding>
                </TextBlock.Text>
           </TextBlock>
       </DataTemplate>
       <HierarchicalDataTemplate DataType="{x:Type local:Country}"
                                 ItemsSource="{Binding Cities}">
            <TextBlock Text="{Binding Name}"/>
       </HierarchicalDataTemplate>
       <HierarchicalDataTemplate DataType="{x:Type local:Continent}"
                                 ItemsSource="{Binding Countries}">
            <TextBlock Text="{Binding Name}"/>
       </HierarchicalDataTemplate>
    </Window.Resources>

    <Grid>
        <TreeView x:Name="myTreeView" ItemsSource="{Binding}" Margin="0,0,12,12" />
    </Grid>

</Window>

Here is a screenshot of what you should see when you run the application. Enjoy!

A little while back I was tasked with creating a custom window in WPF that had its own look and feel. In order to do this you must first set the WindowStyle to None. This is great because it opens the door for you to customize your new window to look however you want. Unfortunately, when you set your WindowStyle to None you lose all of the base functionality of the window. You now have to implement native window actions such as sizing, dragging, the minimize, maximize/restore, and close buttons, etc. After looking around the web for a while I found a really good starting point for creating my custom window. Here is a link to a sample project that has an Office Style custom window already created.

http://blog.opennetcf.com/ayakhnin/content/binary/OfficeStyleWindow.zip

In this project you will find a nicely styled window whose look should be easily customizable. Now we just have to add some code to make our new window accommodate for the taskbar. One thing we must be especially aware of is accommodating for the taskbar when it is set to auto-hide. When the taskbar is auto-hidden, you have to leave 2 pixels available for the bar on the docked edge so the user can mouse over that area to restore the hidden taskbar.

After much testing and digging I ended up writing a public static WindowSizing class that should handle the bar correctly. Using the sample linked above, we must first insert these two lines inside of the Window1 constructor after InitializeComponent(); in the Window1.xaml.cs file.

this.Loaded += new RoutedEventHandler(Window1_Loaded);
this.SourceInitialized += new EventHandler(Window1_SourceInitialized);

Here is the code for the event handlers.

void Window1_Loaded(object sender, RoutedEventArgs e)
{
     this.WindowState = WindowState.Maximized;
}

void Window1_SourceInitialized(object sender, EventArgs e)
{
     WindowSizing.WindowInitialized(this);
}

And here is the public static WindowSizing class code which should be self explanatory.

using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace OfficeStyleWindowProject
{
    public static class WindowSizing
    {
        const int MONITOR_DEFAULTTONEAREST = 0x00000002;

        #region DLLImports

        [DllImport("shell32", CallingConvention = CallingConvention.StdCall)]
        public static extern int SHAppBarMessage(int dwMessage, ref APPBARDATA pData);

        [DllImport("user32", SetLastError = true)]
        static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

        [DllImport("user32")]
        internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

        [DllImport("user32")]
        internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

        #endregion

        private static MINMAXINFO AdjustWorkingAreaForAutoHide(IntPtr monitorContainingApplication, MINMAXINFO mmi)
        {
            IntPtr hwnd = FindWindow("Shell_TrayWnd", null);
            if (hwnd == null) return mmi;
            IntPtr monitorWithTaskbarOnIt = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
            if (!monitorContainingApplication.Equals(monitorWithTaskbarOnIt)) return mmi;
            APPBARDATA abd = new APPBARDATA();
            abd.cbSize = Marshal.SizeOf(abd);
            abd.hWnd = hwnd;
            SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref abd);
            int uEdge = GetEdge(abd.rc);
            bool autoHide = System.Convert.ToBoolean(SHAppBarMessage((int)ABMsg.ABM_GETSTATE, ref abd));

            if (!autoHide) return mmi;

            switch (uEdge)
            {
                case (int)ABEdge.ABE_LEFT:
                    mmi.ptMaxPosition.x += 2;
                    mmi.ptMaxTrackSize.x -= 2;
                    mmi.ptMaxSize.x -= 2;
                    break;
                case (int)ABEdge.ABE_RIGHT:
                    mmi.ptMaxSize.x -= 2;
                    mmi.ptMaxTrackSize.x -= 2;
                    break;
                case (int)ABEdge.ABE_TOP:
                    mmi.ptMaxPosition.y += 2;
                    mmi.ptMaxTrackSize.y -= 2;
                    mmi.ptMaxSize.y -= 2;
                    break;
               case (int)ABEdge.ABE_BOTTOM:
                    mmi.ptMaxSize.y -= 2;
                    mmi.ptMaxTrackSize.y -= 2;
                    break;
                default:
                    return mmi;
            }
            return mmi;
        }

        private static int GetEdge(RECT rc)
        {
            int uEdge = -1;
            if (rc.top == rc.left && rc.bottom > rc.right)
                uEdge = (int)ABEdge.ABE_LEFT;
            else if (rc.top == rc.left && rc.bottom < rc.right)
                uEdge = (int)ABEdge.ABE_TOP;
            else if (rc.top > rc.left)
                uEdge = (int)ABEdge.ABE_BOTTOM;
            else
                uEdge = (int)ABEdge.ABE_RIGHT;
            return uEdge;
        }

        public static void WindowInitialized(Window window)
        {
            IntPtr handle = (new System.Windows.Interop.WindowInteropHelper(window)).Handle;
            System.Windows.Interop.HwndSource.FromHwnd(handle).AddHook(new System.Windows.Interop.HwndSourceHook(WindowProc));
        }

        private static IntPtr WindowProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
               case 0x0024:
                    WmGetMinMaxInfo(hwnd, lParam);
                    handled = true;
                    break;
            }

            return (IntPtr)0;
        }

        private static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam)
        {
            MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
            IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

            if (monitorContainingApplication != System.IntPtr.Zero)
            {
                MONITORINFO monitorInfo = new MONITORINFO();
                GetMonitorInfo(monitorContainingApplication, monitorInfo);
                RECT rcWorkArea = monitorInfo.rcWork;
                RECT rcMonitorArea = monitorInfo.rcMonitor;
                mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
                mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
                mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
                mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
                mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;                                                 //maximum drag X size for the window
                mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;                                                 //maximum drag Y size for the window
                mmi.ptMinTrackSize.x = 800;                                                             //minimum drag X size for the window
                mmi.ptMinTrackSize.y = 600;                                                             //minimum drag Y size for the window
                mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi);                  //need to adjust sizing if taskbar is set to autohide
            }
            Marshal.StructureToPtr(mmi, lParam, true);
        }

        public enum ABEdge
        {
            ABE_LEFT = 0,
            ABE_TOP = 1,
            ABE_RIGHT = 2,
            ABE_BOTTOM = 3
        }

        public enum ABMsg
        {
            ABM_NEW = 0,
            ABM_REMOVE = 1,
            ABM_QUERYPOS = 2,
            ABM_SETPOS = 3,
            ABM_GETSTATE = 4,
            ABM_GETTASKBARPOS = 5,
            ABM_ACTIVATE = 6,
            ABM_GETAUTOHIDEBAR = 7,
            ABM_SETAUTOHIDEBAR = 8,
            ABM_WINDOWPOSCHANGED = 9,
            ABM_SETSTATE = 10
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct APPBARDATA
        {
            public int cbSize;
            public IntPtr hWnd;
            public int uCallbackMessage;
            public int uEdge;
            public RECT rc;
            public bool lParam;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct MINMAXINFO
        {
            public POINT ptReserved;
            public POINT ptMaxSize;
            public POINT ptMaxPosition;
            public POINT ptMinTrackSize;
            public POINT ptMaxTrackSize;
        };

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        public class MONITORINFO
        {
            public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
            public RECT rcMonitor = new RECT();
            public RECT rcWork = new RECT();
            public int dwFlags = 0;
        }

        [StructLayout(LayoutKind.Sequential)]
        public struct POINT
        {
            public int x;
            public int y;

            public POINT(int x, int y)
            {
                this.x = x;
                this.y = y;
            }
        }

        [StructLayout(LayoutKind.Sequential, Pack = 0)]
        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
    }
}

If you have any questions or problems please feel free to leave a comment.

For a downloadable sample please go here.

After a few months of doing very simple data binding with my MVVM, I had a problem where I needed to enable or disable a control based on multiple conditions. I could easily perform this task with an event handler and some logic in my code behind but knew it would be cleaner if I could somehow handle it all with binding.  After doing a little digging I ran across a very powerful binding technique called, yep you guessed it, MultiBinding.

The easiest way for me to demonstrate MulitBinding and its capabilities is to explain a small working sample. I have decided to create a small form with only a TextBox, ComboBox, and Submit button. I only want the submit button to be enabled when some text has been entered AND something was chosen from the ComboBox. Since this example is so concise, I am just going to give you all of the xaml at once. Here are the contents of my MainWindow.xaml file. I have highlighted the MultiBinding syntax for your convenience.

<Window x:Class="MultiBindingTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:Local="clr-namespace:MultiBindingTest"
        Title="MainWindow" Height="200" Width="300">

    <Window.Resources>
        <Local:FormFilledMultiConverter x:Key="FormFilledMultiConverter"/>
    </Window.Resources>

    <Grid>
        <StackPanel Orientation="Vertical" HorizontalAlignment="Center" Margin="10">
            <StackPanel Orientation="Horizontal">
                <Label x:Name="labelName" Content="Name:"/>
                <TextBox x:Name="textBoxName" Width="110"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal">
            <Label x:Name="labelLetter" Content="Letter:" VerticalAlignment="Center"/>
            <ComboBox x:Name="comboBoxLetter" Width="110" Margin="10">
                <ComboBoxItem Content="Alpha"/>
                <ComboBoxItem Content="Beta"/>
                <ComboBoxItem Content="Delta"/>
                <ComboBoxItem Content="Gamma"/>
            </ComboBox>
            </StackPanel>
            <Button x:Name="buttonSubmit" Width="50" Height="30" Content="Submit">
                <Button.IsEnabled>
                    <MultiBinding Converter="{StaticResource FormFilledMultiConverter}">
                        <Binding Path="Text" ElementName="textBoxName"/>
                        <Binding Path="SelectedIndex" ElementName="comboBoxLetter"/>
                    </MultiBinding>
                </Button.IsEnabled>
            </Button>
        </StackPanel>
    </Grid>
</Window>

As you can see from the xaml, there is a MultiBinding converter, FormFilledMultiConverter.cs, that we are utilizing to perform the logic used to determine when the Submit button is enabled. Here is the code for the converter.

using System;
using System.Globalization;
using System.Windows.Data;

namespace MultiBindingTest
{
    public class FormFilledMultiConverter : IMultiValueConverter
    {
        #region IMultiValueConverter Members

        public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
        {
            for (int i = 0; i < values.Length; i++)
                if (values[i] == null)
                    return false;

            string name = values[0].ToString();
            int selectedIndex = System.Convert.ToInt32(values[1]);

            //if some text was entered for then name AND a letter was chosen from the combobox, return true, else return false.
            return (name.Length > 0 && selectedIndex != -1) ? true : false;
        }

        public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }
}

The contents of the converter are pretty straightforward. Now, lets say we had anther ComboBox on the form where the user needed to select a color. To make the Submit button dependent on this new ComboBox we would do the following in xaml:

<Button x:Name="buttonSubmit" Width="50" Height="30" Content="Submit">
    <Button.IsEnabled>
        <MultiBinding Converter="{StaticResource FormFilledMultiConverter}">
            <Binding Path="Text" ElementName="textBoxName"/>
            <Binding Path="SelectedIndex" ElementName="comboBoxLetter"/>
            <Binding Path="SelectedIndex" ElementName="myNewComboBox"/>
        </MultiBinding>
    </Button.IsEnabled>
</Button>

We could then grab the new ComboBox’s SelectedIndex in the converter by using this:

int newSelectedIndex = System.Convert.ToInt32(values[2]);

I hope this clearly demonstrates the fundamentals of MultiBinding, if anyone would like a link to the full sample code just post your request in the comments. Enjoy!

<Window x:Class=”MultiBindingTest.MainWindow”
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation&#8221;
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml&#8221;
xmlns:Local=”clr-namespace:MultiBindingTest”
Title=”MainWindow” Height=”200″ Width=”300″><Window.Resources>
<Local:FormFilledMultiConverter x:Key=”FormFilledMultiConverter”/>
</Window.Resources> 

<Grid>
<StackPanel Orientation=”Vertical” HorizontalAlignment=”Center” Margin=”10″>
<StackPanel Orientation=”Horizontal”>
<Label x:Name=”labelName” Content=”Name:”/>
<TextBox x:Name=”textBoxName” Width=”110″/>
</StackPanel>
<StackPanel Orientation=”Horizontal”>
<Label x:Name=”labelLetter” Content=”Letter:” VerticalAlignment=”Center”/>
<ComboBox x:Name=”comboBoxLetter” Width=”110″ Margin=”10″>
<ComboBoxItem Content=”Alpha”/>
<ComboBoxItem Content=”Beta”/>
<ComboBoxItem Content=”Delta”/>
<ComboBoxItem Content=”Gamma”/>
</ComboBox>
</StackPanel>
<Button x:Name=”buttonSubmit” Width=”50″ Height=”30″ Content=”Submit”>
<Button.IsEnabled>
<MultiBinding Converter=”{StaticResource FormFilledMultiConverter}”>
<Binding Path=”Text” ElementName=”textBoxName”/>
<Binding Path=”SelectedIndex” ElementName=”comboBoxLetter”/>
</MultiBinding>
</Button.IsEnabled>
</Button>
</StackPanel>
</Grid>
</Window>

Binding Validation

When developing web or desktop applications, data entry is a necessity. In order to ensure things run according to plan, we always want to validate our user data. For example, we may want to restrict the length of a user name field, or verify that a person’s first or last name only contains letters. A good way to ensure such restrictions is to implement data binding validation. For this simple example I am going to set up a form with two text boxes. The first text box may only contain letters, while the second may only contain numbers and have a maximum length of 5.

Lets get started by creating a new WPF application and declaring two dependency properties in our MainWindow.xaml.cs file like so. We will be binding these to their respective TextBlock’s Text property. Please note the default values being given to the properties (“TEXT”, and “123”).

public string UserText
{
    get { return (string)GetValue(UserTextProperty); }
    set { SetValue(UserTextProperty, value); }
}

public static readonly DependencyProperty UserTextProperty =
    DependencyProperty.Register("UserText", typeof(string), typeof(MainWindow), new UIPropertyMetadata("TEXT"));

public string UserNumbers
{
    get { return (string)GetValue(UserNumbersProperty); }
    set { SetValue(UserNumbersProperty, value); }
}

public static readonly DependencyProperty UserNumbersProperty =
    DependencyProperty.Register("UserNumbers", typeof(string), typeof(MainWindow), new UIPropertyMetadata("123"));

Now when binding to our TextBlocks without binding validation our syntax would look something like this:

<TextBox x:Name="textBoxUserText" Text="{Binding UserText, UpdateSourceTrigger=PropertyChanged}"/>

However, if we are going to use binding validation our syntax will be a little different. First, we need a way to visually convey to the user that their data entry failed our validation. This is the purpose of the reference to the textBoxInError style that will be described later. We also need to specify a validation rule that will be used to test that the data entered is in the correct format. In this case our validation rule is called TextOnlyValidationRule. Here is the updated syntax.

<TextBox x:Name="textBoxUserText" Style="{StaticResource textBoxInError}">
    <TextBox.Text>
        <Binding Path="UserText" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <Local:TextOnlyValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Not to worry, I haven’t forgotten about the validation rule itself. Actually, in many cases, a regular expression will be all you need to validate your data. Here is our TextOnlyValidationRule.cs file.

using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;

namespace BindingValidation
{
    public class TextOnlyValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            string parameter = value as string;

            Regex regexAlpha = new Regex(@"^[a-zA-Z]+$");

            if (parameter.Length != 0)
            {
                if (regexAlpha.IsMatch(parameter))
                    return new ValidationResult(true, null);
                else
                    return new ValidationResult(false, "Input must contain only letters!");
            }
            else
                return new ValidationResult(false, "You must enter some letters!");
        }
    }
}

From the rule you can see the regular expression is verifying that only letters are entered. You can also see that we are making sure at least some text has been entered (remember the default is “TEXT”). The strings in the ValidationResults are what is displayed when you mouse over the text box when it is in an error state. This is handled by the style and wil be shown later.

Now, what if we wanted to use this single validation rule for many text boxes and specify a different maximum length for different text boxes? This could easily be done by adding a MaxLength property to our validation rule and assigning to it in xaml. I have done this for our NumbersOnlyValidationRule.cs file which you can see below:

using System.Globalization;
using System.Text.RegularExpressions;
using System.Windows.Controls;

namespace BindingValidation
{
    public class NumbersOnlyValidationRule : ValidationRule
    {
        public int MaxLength { get; set; }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            string parameter = value as string;

            Regex regexNumeric = new Regex(@"^[0-9]+$");

            if(parameter.Length == 0)
                return new ValidationResult(false, "You must enter some numbers!");

            if (parameter.Length > MaxLength)
                return new ValidationResult(false, "Length must be less than " + MaxLength + "!");

            if (regexNumeric.IsMatch(parameter))
                return new ValidationResult(true, null);
            else
                return new ValidationResult(false, "Input must contain only numbers!");
        }
    }
}

Now in xaml when you want to assign to the MaxLength property you do the following:

<TextBox x:Name="textBoxUserNumbers" Style="{StaticResource textBoxInError}">
    <TextBox.Text>
        <Binding Path="UserNumbers" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <Local:NumbersOnlyValidationRule MaxLength="5" />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

Finally, here is the style for when the text box is in error. The relative source binding (self) points to the first error in the error array of the current element. If there is an error it will be displayed as a tooltip.

<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" Value="{Binding RelativeSource={x:Static RelativeSource.Self}, Path=(Validation.Errors)[0].ErrorContent}"/>
        </Trigger>
    </Style.Triggers>
</Style>

Here is a screenshot of the application showing a tooltip error.

Post a comment if you would like a link to the entire sample application.

Snippet

public string UserText
        {
            get { return (string)GetValue(UserTextProperty); }
            set { SetValue(UserTextProperty, value); }
        }

        public static readonly DependencyProperty UserTextProperty =
            DependencyProperty.Register("UserText"typeof(string), typeof(MainWindow), new UIPropertyMetadata("TEXT"));

        public string UserNumbers
        {
            get { return (string)GetValue(UserNumbersProperty); }
            set { SetValue(UserNumbersProperty, value); }
        }

        public static readonly DependencyProperty UserNumbersProperty =
            DependencyProperty.Register("UserNumbers"typeof(string), typeof(MainWindow), new UIPropertyMetadata("123"));

Attached Properties

Attached properties are a beautiful thing. For a comprehensive introduction check out the MSDN  here. Anyway, the other day I was tasked with creating a button that has two distinct visual states, a normal state and mouse over state. The layout of the button itself was fairly simple. It was to be a rectangle with a stroke and fill. The rectangle had to contain an image and some text denoting what the purpose of the button was. Normally this would be no problem if I only had one button with one image. However, I needed two images and would prefer to have only one style that I could use for multiple buttons. For this example we are creating a “Save As” button.

When dropping the normal Windows button control on a form, it only exposes a single image property. The main problem was how to go about exposing multiple image properties on that button. One solution to this problem is to utilize attached properties. First I created a new WPF application in Visual Studio and added a class called CustomButton.cs.

Here are the contents of the CustomButton.cs class.

using System.Windows;
using System.Windows.Media;

namespace AttachedPropertyTesting
{
     public class CustomButton
     {
          static CustomButton()
          {
               ImageProperty = DependencyProperty.RegisterAttached("Image",
                    typeof(ImageSource), typeof(CustomButton), new FrameworkPropertyMetadata((ImageSource)null));

               ImageMouseOverProperty = DependencyProperty.RegisterAttached("ImageMouseOver",
                    typeof(ImageSource), typeof(CustomButton), new FrameworkPropertyMetadata((ImageSource)null));
          }

          public static readonly DependencyProperty ImageProperty;

          public static ImageSource GetImage(DependencyObject obj)
          {
               return (ImageSource)obj.GetValue(ImageProperty);
          }

          public static void SetImage(DependencyObject obj, ImageSource value)
          {
               obj.SetValue(ImageProperty, value);
          }

          public static readonly DependencyProperty ImageMouseOverProperty;

          public static ImageSource GetImageMouseOver(DependencyObject obj)
          {
               return (ImageSource)obj.GetValue(ImageMouseOverProperty);
          }

          public static void SetImageMouseOver(DependencyObject obj, ImageSource value)
          {
               obj.SetValue(ImageMouseOverProperty, value);
          }
     }
}

As you can see, we have declared two Dependency Properties, Image, and ImageMouseOver and registered them as attached properties. We can now access these properties from the button control itself. Then, with a little styling and template binding we have everything we need. I have placed our two images in a folder called Images. Here are the contents of the MainWindow.xaml.

<Window x:Class="AttachedPropertyTesting.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:aps="clr-namespace:AttachedPropertyTesting"
        Title="MainWindow" Height="200" Width="350">

<Window.Resources>
     <Style x:Key="btnSaveOrb" TargetType="{x:Type Button}">
          <Setter Property="Template">
               <Setter.Value>
                    <ControlTemplate TargetType="{x:Type Button}">
                         <Grid Background="LightBlue">
                              <Rectangle x:Name="rectangle" RadiusX="4" RadiusY="4" Stroke="Brown" Fill="Bisque"/>
                              <Label x:Name="label" Content="{TemplateBinding Content}" Margin="60,0,0,0" HorizontalAlignment="Left" VerticalAlignment="Center" Foreground="Brown"/>
                              <Image x:Name="image" Source="{TemplateBinding aps:CustomButton.Image}" HorizontalAlignment="Left" VerticalAlignment="Top"/>
                              <Image x:Name="imageMouseOver" Source="{TemplateBinding aps:CustomButton.ImageMouseOver}" HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="Hidden"/>
                         </Grid>
                         <ControlTemplate.Triggers>
                              <Trigger Property="IsMouseOver" Value="True">
                                   <Setter Property="Stroke" TargetName="rectangle" Value="Green"/>
                                   <Setter Property="Fill" TargetName="rectangle" Value="LightGreen"/>
                                   <Setter Property="Visibility" TargetName="imageMouseOver" Value="Visible"/>
                              </Trigger>
                         </ControlTemplate.Triggers>
                    </ControlTemplate>
               </Setter.Value>
          </Setter>
     </Style>
</Window.Resources>

     <Grid>
          <Button x:Name="buttonTwoImages"
                  aps:CustomButton.Image="Images/largeSaveIcon.png"
                  aps:CustomButton.ImageMouseOver="Images/largeSaveIconMouseOver.png"
                  Style="{StaticResource btnSaveOrb}"
                  Width="151"
                  Height="47"
                  Content="SaveAs">
          </Button>
     </Grid>
</Window>

One thing to note here is the reference to the clr-namespace at the top of the xaml file. Below are two screenshots showing the button in action. The first shows the button in its normal state while the second shows the button in its  mouse over state with the mouse over image. I hope you all realize there are limitless possibilities with attached properties. Enjoy!

If you have worked with Windows Presentation Foundation (WPF) you should be fully aware of the term data binding. As stated on the MSDN “data binding provides a simple and consistent way for applications to present and interact with data .” It also allows for a clean separation of business logic from the UI and should be used where applicable. A question I commonly see posted on forums is “How do I data bind to an enumeration?”. I hope to clearly address that question here so lets just jump into the code.

First you must create a new WPF project and declare an enumeration. For this example I have created a fruit enum as follows:

public enum Fruit
{
     apples,
     oranges,
     peaches,
     pears,
     strawberries,
}

My MainWindow.cs file will contain only the default constructor and one dependency property of type fruit. One thing to note about the DP is the UIPropertyMetadata. This provides the default value of apples for the DP since that is the first element in the enum. Here is the dependency property:


public Fruit Fruit
{
     get { return (Fruit)GetValue(FruitProperty); }
     set { SetValue(FruitProperty, value); }
}

public static readonly DependencyProperty FruitProperty =
     DependencyProperty.Register("Fruit", typeof(Fruit), typeof(MainWindow), new UIPropertyMetadata(new Fruit()));

Next is the EnumToBooleanConverter which can be seen below. If you are not familiar with binding converters have a look at this.

public class EnumToBooleanConverter : IValueConverter
{
     public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
          return value.Equals(parameter);
     }

     public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
     {
          if (value.Equals(false))
               return Binding.DoNothing;
          else
               return parameter;
     }
}

Finally, here is all of the code contained in the MainWindow.xaml file. There are a few things to note here. First, we must include a reference to the local namespace so we can include the binding converter in the Windows.Resources tag. There are also two styles included in the Windows.Resources tag, one for the radio buttons and one for the stack panel. They are referenced using their x:Key.

You can see the binding syntax in the  IsChecked property of each radio button. It is important to note that we are NOT referring to the enums by simply using strings in xaml. We explicitly pass in the enum value using this syntax: x:Static local:Fruit.EnumValue.

<Window x:Class="DatabindingToAnEnumeration.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DatabindingToAnEnumeration"
        Title="MainWindow" Height="250" Width="300"
        x:Name="windowMain">

    <Window.Resources>
        <local:EnumToBooleanConverter x:Key="EnumToBooleanConverter"/>

        <Style x:Key="radioButtonStyle" TargetType="RadioButton">
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Left"/>
            <Setter Property="Margin" Value="15,0"/>
        </Style>

        <Style x:Key="stackPanelStyle" TargetType="StackPanel">
            <Setter Property="Orientation" Value="Horizontal"/>
            <Setter Property="Background" Value="LightGray"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="Margin" Value="15,0"/>
        </Style>

    </Window.Resources>

    <Grid Background="LightYellow" Height="200">
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="30"/>
            <RowDefinition Height="50"/>
        </Grid.RowDefinitions>

        <RadioButton Content="{x:Static local:Fruit.apples}" Grid.Row="0" Style="{StaticResource radioButtonStyle}"
                     IsChecked="{Binding Path=Fruit, ElementName=windowMain, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Fruit.apples}}"/>

        <RadioButton Content="{x:Static local:Fruit.oranges}" Grid.Row="1" Style="{StaticResource radioButtonStyle}"
                     IsChecked="{Binding Path=Fruit, ElementName=windowMain, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Fruit.oranges}}"/>

        <RadioButton Content="{x:Static local:Fruit.peaches}" Grid.Row="2" Style="{StaticResource radioButtonStyle}"
                     IsChecked="{Binding Path=Fruit, ElementName=windowMain, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Fruit.peaches}}"/>

        <RadioButton Content="{x:Static local:Fruit.pears}" Grid.Row="3" Style="{StaticResource radioButtonStyle}"
                     IsChecked="{Binding Path=Fruit, ElementName=windowMain, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Fruit.pears}}"/>

        <RadioButton Content="{x:Static local:Fruit.strawberries}" Grid.Row="4" Style="{StaticResource radioButtonStyle}"
                     IsChecked="{Binding Path=Fruit, ElementName=windowMain, Converter={StaticResource EnumToBooleanConverter}, ConverterParameter={x:Static local:Fruit.strawberries}}"/>

        <StackPanel Grid.Row="5" Style="{StaticResource stackPanelStyle}">
            <Label Content="Chosen Fruit: " VerticalAlignment="Center"/>
            <TextBlock Text="{Binding Fruit, ElementName=windowMain}" VerticalAlignment="Center" FontWeight="Bold"/>
        </StackPanel>
    </Grid>

</Window>

Just to show that everything is working as expected, I placed a stack panel at the bottom of the window. The panel has a TextBlock that is bound to our Fruit dependency property so you should see it update when you select different radio buttons. Here is an image of the running app.

Hopefully this clears things up. Enjoy!