DotNetCurry Logo

DataBinding Features in WPF 4.5

Posted by: Mahesh Sabnis , on 4/4/2014, in Category WPF
Views: 18583
Abstract: The DataBinding features provided in WPF 4.5 are very useful for creating Rich Line-Of-Business applications where time and again, the UI needs to be either updated with a collection of data received from external repositories or to perform complex validation checks based upon some complex time consuming logic.

Windows Presentation Foundation (WPF) is Microsoft’s premier technology for creating a broad range of Windows Desktop Apps. From its very first release 12 years ago (with the code name “Avalon”); WPF has provided several new features to develop robust and rich business applications. WPF does a very good job in managing the display and editing of complex data using powerful data-binding techniques. Along with Data Binding, the support for Model-View-View-Model (MVVM) in WPF has supported code-less development.

In almost all the types of Business applications, a common and an important requirement is where the UI makes a call to an external service for reading data (in most cases a large amount of data). The call in such scenarios must be handled asynchronously to keep the UI responsive during the process, and once the data is available, it must be automatically synchronized (updated) to the UI.

This article is published from the DotNetCurry .NET Magazine – A Free High Quality Digital Magazine for .NET professionals published once every two months. Subscribe to this eMagazine for Free and get access to hundreds of free tutorials from experts

Another requirement is performing data validations asynchronously, typically when there are more than one validation scenarios to be checked on the data-bound property or where validation has to be performed against complex business rules.

In this article, we will discuss how these requirements can be achieved using two different approaches in WPF 4.5.

Automatically Populating the UI with Collections on non-UI Threads

Populating the UI using Collections is a very common requirement in Business applications. The Task class provided in the Task Parallel Library (TPL) encapsulates all async operations. This class contains the ContinueWith() method, which creates a continuation that executes asynchronously when the Task is complete.

Using Task class (the old way)

To demonstrate the old way, a WCF service makes a call to the SQL Server Database using ADO.NET EF. The database table schema is as shown below:

table-schema

Step 1: Open Visual Studio 2012/2013 and create a blank solution. In this solution, add a WCF Service Application and name it as ‘WCF_DataService’. In this service, add an ADO.NET EF data model using the wizard and use the EmployeeInfo table shown above.

Step 2: Implement the ServiceContract interface and the Service class as shown here:

[ServiceContract]
public interface IService
{
    [OperationContract]
    int CreateEmployee(EmployeeInfo emp);
    [OperationContract]
    EmployeeInfo[] GetEmployees();
}

public class Service: IService
{
    CompanyEntities objContext;
    public Service()
    {
        objContext = new CompanyEntities();
    }

    public int CreateEmployee(EmployeeInfo emp)
    {
        objContext.AddToEmployeeInfoes(emp);
        objContext.SaveChanges();
        return emp.EmpNo;
    }

    public EmployeeInfo[] GetEmployees()
    {
        return objContext.EmployeeInfoes.ToArray();
    }
}

Step 3: Build the project and make sure that it is error-free.

Step 4: In the same solution, add a new WPF 4.5 project and call it ‘WPF45_DataBinding’. Add the WCF service reference in this project. Delete MainWindows1.xaml and add a new WPF Window and call it ‘MainWindows_Update_Collection_UI’.

Step 5: Design the MainWindows_Update_Collection_UI with DataGrid, Button and TextBlock. Set the AutoGeneratedColumns property of the DataGrid to false and define columns for each property from the EmployeeInfo class. The design should be similar to the following:

wpf-nonui-thread

Step 6: In the code-behind, define an instance of the WCF Proxy as shown here:

MyRef.ServiceClient Proxy;

public MainWindows_Update_Collection_UI()
{
    InitializeComponent();
    Proxy = new MyRef.ServiceClient();
}

Step 7: In the code-behind, add a method for making a call to the WCF service using the Task class:

///


/// The method uses the Task class
/// to update the DataGrid on UI after
/// making call to the WCF Service
///

void UpdatingOlderWay()
{
   
    Task taskGetEmployees = Task.Factory.StartNew

 

        foreach (var item in Emps)
        {
            Employees.Add(item);
        }
        return Employees;
    }, CancellationToken.None).ContinueWith(emp => {
        dgemp.ItemsSource = emp.Result;
    },TaskScheduler.FromCurrentSynchronizationContext());
}

If you observe the code, the Task class makes a call to the GetEmployees() method of the WCF service. The return data is then added to the Employees ObservableCollection declared locally in the Task call. The ContinueWith method then accepts the Employees collection and assigns it to the DataGrid dgemp. The TaskSchedular.FromCurrentSynchronizationContext() creates a TaskScheduler associated with the current synchronization context.

Step 8: Call the above method in the click event of the Get Data button:

private void btngetdata_Click(object sender, RoutedEventArgs e)
{
    try
    {
        UpdatingOlderWay();
        //UpdatingUsingWPF45();
        //dgemp.ItemsSource = NewEmployees;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Step 9: Run the project and click on the ‘Get Data’ button:

wpf-get-data

In WPF 4.5, the data is synchronized by accessing the collection using EnableCollectionSynchronization method of the BindingOperations class. The EnableCollectionSynchronization method enables a collection to be accessed across multiple threads. A specific lock object must be used for synchronizing the access to the collection.

Implementing Synchronization with New Techniques in WPF 4.5

Step 1: In the code-behind of the window we just created, declare the collection object and the synchronization lock object as shown here:

//Defining the lock object used for Synchronization
private static object lockObject = new object();

//Defining the Collection object to receive data from external service
ObservableCollection NewEmployees = new ObservableCollection();

Step 2: In the constructor of the code-behind, add this code:

BindingOperations.EnableCollectionSynchronization(NewEmployees, lockObject);

Step 3: Then add a new method for making a call to the WCF service:

///


/// This method will demonstrate the mechanism
/// of accessing the collection on non-UI thread
/// on the UI to Update the UI
///

void UpdatingUsingWPF45()
{
    Task taskGetEmployees = Task.Factory.StartNew

 

        foreach (var item in Emps)
        {
            NewEmployees.Add(item);
        }
        return NewEmployees;
    },null);
}

It is a simple snippet of code to make a call to the WCF service. Once the call is completed, the received data is added into the NewEmployees collection.

Step 4: Add the following code on the click event of the Get Data button,

private void btngetdata_Click(object sender, RoutedEventArgs e)
{
    try
    {
        //UpdatingOlderWay();
        UpdatingUsingWPF45();
        dgemp.ItemsSource = NewEmployees;
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Step 5: Run the application and the result will be exactly the same as we saw in our previous example. The distinctive feature in WPF 4.5 is that the collection source can be shared across threads where the code does not require an explicitly declared thread and it also reduces the coding complexity.

Performing Asynchronous DataValidation

Any user interface that accepts user input has to validate it, to ensure that valid data gets into the back-end. We will discuss how WPF uses the IDataErrorInfo interface and the INotifyErrorDataError interface that was introduced in the .NET Framework 4.5, to perform validation. The IDataErrorInfo interface provides functionality to implement custom error information on the model object bound to the user interface. The INotifyDataErrorInfo interface is used for performing data validation synchronously and asynchronously. This interface was originally introduced in Silverlight considering its asynchronous behavior for dealing with external objects.

The INotifyDataErrorInfo has the following properties:

  • HasErrors: This is a read-only property to notify about the validation errors on the object, if it has any
  • GetErrors: Returns validation errors for a given property
  • ErrorChanged: This event must be raised when a validation error is detected on each property of the source object

Here’s an implementation of the following:

Step 1: In the recently created WPF project, add a new window and name it as ‘MainWindows_Asynchronous_DataValidation’.

Step 2: In the WPF project add a new model class with the following code:

using System.Threading.Tasks;

namespace WPF45_DataBinding
{

///


/// The model class implementing the INotifyDataErrorInfo interface and INotifyPropertyChanged
///

public class Employee : INotifyDataErrorInfo, INotifyPropertyChanged
{
    private Dictionary

 

    public event PropertyChangedEventHandler PropertyChanged;

    ///


    /// The property changed
    ///

    ///
    void onPropertyChanged(string pName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(pName));
            PerformValidation();
        }
    }

 

    int _EmpNo;

    public int EmpNo
    {
        get { return _EmpNo; }
        set
        {
            _EmpNo = value;
            onPropertyChanged("EmpNo");
        }
    }
    string _EmpName;

    public string EmpName
    {
        get { return _EmpName; }
        set
        {
            _EmpName = value;
            onPropertyChanged("EmpName");

        }
    }
    decimal _Salary;

    public decimal Salary
    {
        get { return _Salary; }
        set
        {
            _Salary = value;
            onPropertyChanged("Salary");

        }
    }
    string _DeptName;

    public string DeptName
    {
        get { return _DeptName; }
        set { _DeptName = value; }
    }
    string _Designation;

    public string Designation
    {
        get { return _Designation; }
        set { _Designation = value; }
    }

    ///


    /// Method to get errors on a specific property.
    ///

    ///
    ///
    public System.Collections.IEnumerable GetErrors(string propertyName)
    {
        List errorsForProperties = new List();
        if (propertyName != null)
        {
            modelerrors.TryGetValue(propertyName, out errorsForProperties);
            return errorsForProperties;
        }
        else
        {
            return null;
        }

 

    }

    ///


    /// The Error Info on the object if any
    ///

    public bool HasErrors
    {
        get {
            try
            {
                var errInfo = modelerrors.Values.FirstOrDefault(rec => rec.Count > 0);
                if (errInfo != null)
                    return true;
                else
                    return false;
            }
            catch
            {
            }
            return true;            }
    }

 

   

    ///


    /// Perform Validation Asynchronously
    ///

    private void PerformValidation()
    {
        Task taskasyncvalidate = new Task(() => DataValidation());
        taskasyncvalidate.Start();
    }

 

    ///


    /// Logic for Validation
    ///

    private void DataValidation()
    {
        //Validation for EmpName
        List empNameErrors;
        if (modelerrors.TryGetValue("EmpName", out empNameErrors) == false)
        {
            empNameErrors = new List();
        }
        else
        {
            empNameErrors.Clear();
        }

 

        if (String.IsNullOrEmpty(EmpName))
        {
            empNameErrors.Add("The EmpName can't be null or empty.");
        }
        modelerrors["EmpName"] = empNameErrors;

        if (empNameErrors.Count > 0)
        {
            PropertyErrorsChanged("EmpName");
        }
        //Ends Here

        //Validations for Salary

        List salErrors;
        if (modelerrors.TryGetValue("Salary", out salErrors) == false)
        {
            salErrors = new List();
        }
        else
        {
            salErrors.Clear();
        }
        if (Salary <= 0 || Salary>70000)
        {
            salErrors.Add("The Salary must be greater than zero. and less than 70000");
        }
        else
        {
            salErrors.Clear();
        }
        modelerrors["Salary"] = salErrors;
        if (salErrors.Count > 0)
        {
            PropertyErrorsChanged("Salary");
        }
        //Ends Here
    }

 

    public event EventHandler ErrorsChanged;
    ///


    /// The ErrorChaged handler which will be on each property.
    ///

    ///
    public void PropertyErrorsChanged(string propertyName)
    {
        if (ErrorsChanged != null) {
            var evtargs = new DataErrorsChangedEventArgs(propertyName);
            ErrorsChanged.Invoke(this, evtargs);
        }
    }
}
}

 

  • The Employee class contains properties EmpNo, EmpName, Salary, DeptName and Designation
  • The modelerrors, the Dictionary
  • The class implements INotifyDataErrorInfo and INotifyPropertyChanged interfaces
  • The DataValidation() method defines logic for the data validation on EmpName and Salary properties. The local List empNameErrors is used to store error messages for the validations on the EmpName property. If there are validation errors on this property, then the error message will be added in the empNameErrors and this list is then passed to the modelerrors dictionary for the key as ‘EmpName’. Similarly the validations on the Salary property is declared for Salary less than or equal to zero and greater than 70000
  • The PerformValidation () method uses the Task object to run the DataValidation () method asynchronously
  • The GetErrors(), method checks for the validation errors on property in the modelerrors dictionary, using TryGetValue() method of the dictionary object. This method accepts the property name as key and the value returned will be the list of errors on the property.

Currently validation logic is written for EmpName and Salary but it can be implemented for rest of the properties as per the requirements.

Step 3: In the MainWindows_Asynchronous_DataValidation add the following xaml:

        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:src="clr-namespace:WPF45_DataBinding"
        Title="Asynchronous Data Validation in WPF 4.5" Height="436.917" Width="610.15">
   
       
           
   

   
       
           
           
           
           
           
           
       

       
           
           
       

       
       
       
       
       

       
       
       
       
       
                   VerticalAlignment="Top" Width="225" Height="137"
                    TextWrapping="WrapWithOverflow"/>

   

The xaml is bound with the Employee model class. Textboxes are bound with the properties from the Employee class. The validations are checked using ValidatesOnNotifyDataErrors property of the Binding class.

Step 4: Run the application to bring up the following window:

async-data-val

Enter some text in the EmpName TextBox and the validation gets executed on the Salary box as seen here:

async-2

Now remove text entered for the EmpName and the validation on the EmpName triggers:

async-3

The tootip for the error gets displayed as shown here:

validation-error

Typically Asynchronous validations can be performed when the input data is to be validated based upon some time consuming logic like checking for a strong password based on some complex business rules or checking if a user already exist by making a call to the service and so on.

Conclusion: The new DataBinding features provided in WPF 4.5 are very useful for creating Rich Line-Of-Business applications where time and again, the UI needs to be either updated with a collection of data received from external repositories or to perform complex validation checks based upon some complex time consuming logic.

Download the entire source code from our GitHub Repository

Was this article worth reading? Share it with fellow developers too. Thanks!
Share on Google+
Further Reading - Articles You May Like!
Author
Mahesh Sabnis is a DotNetCurry author and Microsoft MVP having over 17 years of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions). Follow him on twitter @maheshdotnet


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!