Wednesday, 3 August 2011

Custom Data Binding, Data Navigation and Data Validation in WPF


Getting Started:
Creating WPF Project.
  1. Open Visual Studio 2010.
  2. Go to File => New => Project
  3. Select Windows in installed templates
  4. Select WPF Application
  5. Enter the Name and choose the location.
  6. Click OK
First of all add a new class using add new item tab and rename that class name.
MyFriends.cs
using System;using System.Windows;using System.ComponentModel;using System.Collections.Generic;using System.Linq;using System.Text;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes; 
class MyFriends : INotifyPropertyChanged{        // INotifyPropertyChanged Members        public event PropertyChangedEventHandler PropertyChanged;        protected void Notify(string propName)        {            if (this.PropertyChanged != null)            {                PropertyChanged(this, new PropertyChangedEventArgs(propName));            }        }        string name;        public string Name        {            get            {                return this.name;            }            set            {                if (this.name == value)                {                    return;                }                this.name = value;                Notify("Name");            }        }       int age;       public int Age       {              get              {                  return this.age;              }              set              {                if(this.age == value )                  {                     return;                  }                  this.age = value;                  Notify("Age");              }       }       string country;       public string Country       {           get           {               return this.country;           }           set           {               if (this.country == value)               {                   return;               }               this.country = value;               Notify("Country");           }       }       string profession;       public string Profession       {           get           {               return this.profession;           }           set           {               if (this.profession == value)               {                   return;               }               this.profession = value;               Notify("Profession");           }       }       string mobileNo;       public string MobileNo       {           get           {               return this.mobileNo;           }           set           {               if (this.mobileNo == value)               {                   return;               }               this.mobileNo = value;               Notify("MobileNo");           }       }       string favQuote;       public string FavQuote       {           get           {               return this.favQuote;           }           set           {               if (this.favQuote == value)               {                   return;               }               this.favQuote = value;               Notify("FavQuote");           }       }       public MyFriends() { }       public MyFriends(string name, int age, string country, string profession, string mobileNo, string favQuote)       {            this.name = name;            this.age = age;            this.country = country;            this.profession = profession;            this.mobileNo = mobileNo;            this.favQuote = favQuote;        }}    class Friends : List<MyFriends>    {    }
I am adding three more classes: 
[ValueConversion(/*sourceType*/ typeof(int), /*targetType*/ typeof(Brush))]    public class AgeToForegroundConverter : IValueConverter    {        // Called when converting the Age to a Foreground brush        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)        {            // Only convert to brushes...            if (targetType != typeof(Brush))            {                return null;            }            // DANGER! After 25, it's all downhill...            int age = int.Parse(value.ToString());            return (age > 25 ? Brushes.Red : Brushes.Black);        }        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)        {            // Should not be called in our example            throw new NotImplementedException();        }    }  
public class Base16Converter : IValueConverter{        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)        {            // Convert to base 16            return ((int)value).ToString("x");        }        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)        {            // Convert from base 16            return int.Parse((string)value, System.Globalization.NumberStyles.HexNumber);        }}public class NumberRangeRule : ValidationRule{        int min;        public int Min        {            get { return min; }            set { min = value; }        }        int max;        public int Max        {            get { return max; }            set { max = value; }        }        public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)        {            int number;            if (!int.TryParse((string)value, out number))            {                return new ValidationResult(                false,                "Invalid number format");            }            if (number < min || number > max)            {                return new ValidationResult(                false,                string.Format("Number out of range ({0}-{1})", min, max));            }            //return new ValidationResult(true, null); // valid result            return ValidationResult.ValidResult; // static valid result            // to save on garbage        }}
Window.xaml.
<Window x:Class="TestWpf.BindListData"        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        xmlns:class="clr-namespace:TestWpf"        Title="BindListData" Height="400" Width="400"><Window.Resources>        <class:Friends x:Key="MyGoodFriends">            <class:MyFriends Name="Raj Beniwal" Age="28" Country="India" Profession="Software Development" MobileNo="9899999990" FavQuote="Nothing is impossible"></class:MyFriends>            <class:MyFriends Name="Mahesh Chand" Age="38" Country="USA" Profession="Technical Consultant" MobileNo="9899999990" FavQuote="Not posted"></class:MyFriends>                <class:MyFriends Name="Sameer Mathotra" Age="30" Country="Dubai" Profession="Software Engineer" MobileNo="9899999990" FavQuote="Not posted"></class:MyFriends>                    <class:MyFriends Name="Vikash Nanda" Age="25" Country="Australia" Profession="Mechanical Engineer" MobileNo="9899999990" FavQuote="Not posted"></class:MyFriends>                        <class:MyFriends Name="Sidharth Thakur" Age="27" Country="New Zealand" Profession="Project Manager" MobileNo="9899999990" FavQuote="Not posted"></class:MyFriends>                            <class:MyFriends Name="Shivam Tripathy" Age="32" Country="London" Profession="HR Consultant" MobileNo="9899999990" FavQuote="Not posted"></class:MyFriends>                                <class:MyFriends Name="Rohit Khanna" Age="35" Country="India" Profession="Businessman" MobileNo="9899999990" FavQuote="Not posted"></class:MyFriends>        </class:Friends>      
<!--<class:AgeToForegroundConverter x:Key="ageConverter" />-->       <class:Base16Converter x:Key="ageConverter"></class:Base16Converter>  </Window.Resources>    <Grid DataContext="{StaticResource MyGoodFriends}" Width="386">
        <TextBlock Margin="12,24,331,320">Name:</TextBlock>
        <TextBox Text="{Binding Path=Name}" Margin="86,24,12,307" IsReadOnly="True" />
        <TextBox  Foreground="{Binding Path=Age, Converter={StaticResource ageConverter}}" Margin="86,69,11,265" x:Name="txtAge">
            <TextBox.Text>
                <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
                    <Binding.ValidationRules>
                        <class:NumberRangeRule Min="0" Max="100"></class:NumberRangeRule>
                    </Binding.ValidationRules>
                </Binding>                           </TextBox.Text>
        </TextBox>
        <Button Margin="86,319,54,12" x:Name="btnBirthday" Click="btnBirthday_Click">When is my Birthday</Button>
        <TextBlock Margin="12,69,0,274" HorizontalAlignment="Left" Width="34">Age:</TextBlock>
        <Button Content="Back" Margin="272,292,0,48" Click="btnBack_Click" x:Name="btnBack" HorizontalAlignment="Left" Width="47" />
        <Button Content="Next" Margin="0,292,13,48" x:Name="btnForward" Click="btnForward_Click" HorizontalAlignment="Right" Width="48" />
        <TextBlock HorizontalAlignment="Left" Margin="12,113,0,226" Width="54">Country:</TextBlock>
        <TextBlock HorizontalAlignment="Left" Margin="13,0,0,189" Width="63" Height="22" VerticalAlignment="Bottom">Profession:</TextBlock>
        <TextBox IsReadOnly="True" Margin="87,105,11,226" Text="{Binding Path=Country}" />
        <TextBox IsReadOnly="True" Margin="87,147,11,189" Text="{Binding Path=Profession}" />
        <TextBlock Height="22" HorizontalAlignment="Left" Margin="13,0,0,159" VerticalAlignment="Bottom" Width="63">Mobile No.:</TextBlock>
        <TextBox IsReadOnly="True" Margin="87,182,12,154" Text="{Binding Path=MobileNo}" />
        <TextBlock Height="22" HorizontalAlignment="Left" Margin="12,0,0,124" VerticalAlignment="Bottom" Width="63">Fav Quote:</TextBlock>
        <TextBox IsReadOnly="True" Margin="86,215,13,82" Text="{Binding Path=FavQuote}" />
    </Grid></Window> 
.xaml.cs 
public partial class BindListData : Window{        public BindListData()        {            InitializeComponent();            Validation.AddErrorHandler(this.txtAge, txtAge_ValidationError);        }        ICollectionView GetFriendsView()        {            Friends people = (Friends)this.FindResource("MyGoodFriends");            return CollectionViewSource.GetDefaultView(people);        }  
img1.jpg
Figure 1.
        private void btnBirthday_Click(object sender, RoutedEventArgs e)        {            // Get the current person out of the collection view           
            ICollectionView view = GetFriendsView();            MyFriends person = (MyFriends)view.CurrentItem;            ++person.Age;            MessageBox.Show(string.Format("Happy Birthday {0}, on your {1}th Birthday ", person.Name, person.Age));        }
img2.jpg  
Figure 2. 
private void btnForward_Click(object sender, RoutedEventArgs e){    ICollectionView view = GetFriendsView();    view.MoveCurrentToNext();    if (view.IsCurrentAfterLast)                {        view.MoveCurrentToLast();    }}  
img3.jpg 
Figure 3.
private void btnBack_Click(object sender, RoutedEventArgs e)
{    ICollectionView view = GetFriendsView();    view.MoveCurrentToPrevious();    if (view.IsCurrentBeforeFirst)
    
{
        view.MoveCurrentToFirst();    }}
img4.jpg 
Figure 4.   
void txtAge_ValidationError(object sender, ValidationErrorEventArgs e){    // Show the string created in NumberRangeRule.Validate
    //txtAge.ToolTip = (string)e.Error.ErrorContent;
    // Show the string pulled out of the exception by the
    // ExceptionValidationRule
    MessageBox.Show((string)e.Error.ErrorContent, "Validation Error");}

 img5.jpg
Figure 5. 
namespace System.Windows.Data{    public enum UpdateSourceTrigger    {        Default = 0, // updates "naturally" based on the target control
        PropertyChanged = 1, // updates the source immediately
        LostFocus = 2, // updates the source when focus changes
        Explicit = 3, // must call BindingExpression.UpdateSource()
    }}  
If you 
<class:AgeToForegroundConverter x:Key="ageConverter" />
 img6.jpg 
Figure 6.  
<TextBox  Foreground="{Binding Path=Age, Converter={StaticResource ageConverter}}" Margin="86,69,11,265" x:Name="txtAge">            <TextBox.Text>
                <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
                    <Binding.ValidationRules>
                        <class:NumberRangeRule Min="0" Max="100"></class:NumberRangeRule>
                    </Binding.ValidationRules>
                </Binding>                           </TextBox.Text>
</TextBox>
 img7.jpg
Figure 7.