WPF is still a great platform to develop rich User Interface and native User Experiences in Windows. Typically these user interfaces quite frequently need to show live data and live updates e.g. Health Graph in Healthcare domain, changing Share Price in Share Market domain etc. The developer community often uses custom threading implementations or Reactive Extensions to shows such updates on UI.
Consider the scenario where data that needs to be bound with UI, is stored in a Collection and end-user wants to perform grouping, sorting and filter operations on this collection. To enable these functions, the developer needs to explicitly code the grouping/sorting/filtering behavior and maybe provide additional UI elements as required. Finally based on User actions, the ‘View’ needs to be re-drawn to show the same data. There is a lot of explicit work that needs to handled manually. It would make lives a lot easier if the UI understood the change in behavior and applied the changes automatically.
WPF 4.5 comes with a host of new features (you can read about them here). One of them is the ICollectionViewLiveShaping interface. The main feature of this interface is that it provides properties which enables sorting, filtering and grouping on the CollectionView class in real time. The Live Shaping feature provided in WPF 4.5 actually monitors the changes made in the specific property of the collection, if the change occurs then these changes are refreshed on the UI. Now that sounds exactly like what we were wishing for in the previous paragraph.
Let’s build a small sample to see it in action.
Using ICollectionViewLiveShaping
Step 1: Open VS2012 and create a new WPF 4.5 application, name it as ‘WPF45_LiveShapping’. In this project add a new WPF window, name it as Shell.xaml. Add another WPF window, name it as MainWindow_Filter.xaml. Now in your project, you have Shell.Xaml, MainWindow.xaml and MainWindow_Filter.xaml. Open App.Xaml and change the value of StartUpUri property to Shell.xaml
Step 2: In Shell.xaml, add two buttons and name them ‘btnlivesorting’ and ‘btnlivefiltering’. On the click event of these buttons, write the following code:
private void btnlivesorting_Click(object sender, RoutedEventArgs e)
{
var winSort = new MainWindow();
winSort.Show();
}
private void btnliveFiltering_Click(object sender, RoutedEventArgs e)
{
var winFilter = new MainWindow_Filter();
winFilter.Show();
}
The code creates instances of MainWindow and MainWindow_Filter and calls the Show method on them.
Step 3: In the project, add a new class file - DataClasses. In this file, add the following classes:
/// <summary>
/// The class implement INotifyPropertyChanged interface.
/// This is used for the Properties value changes.
/// </summary>
public class Product : INotifyPropertyChanged
{
int _ProductId;
public int ProductId
{
get { return _ProductId; }
set
{
_ProductId = value;
OnPropertychanged("ProductId");
}
}
string _ProductName;
public string ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
OnPropertychanged("ProductName");
}
}
int _Price;
public int Price
{
get { return _Price; }
set
{
_Price = value;
OnPropertychanged("Price");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertychanged(string pName)
{
if (PropertyChanged != null)
{
PropertyChanged(this,new PropertyChangedEventArgs(pName));
}
}
}
public class ProductCollection : ObservableCollection<Product>
{
public ProductCollection()
{
Add(new Product() { ProductId = 1, ProductName = "Mobile-Single-SIM",
Price = 2300 });
Add(new Product() { ProductId = 2, ProductName = "Mobile-Duel-SIM",
Price = 2300 });
Add(new Product() { ProductId = 3, ProductName = "Tablet-Single-SIM",
Price = 2300 });
Add(new Product() { ProductId = 4, ProductName = "Tablet-Calling-
Facility", Price = 2300 });
Add(new Product() { ProductId = 5, ProductName = "WiFi Router", Price =
2300 });
Add(new Product() { ProductId = 6, ProductName = "Blooth", Price = 2300
});
Add(new Product() { ProductId = 7, ProductName = "Mobile-Pouch", Price =
2300 });
Add(new Product() { ProductId = 8, ProductName = "Mobile-Scratch-Guard",
Price = 2300 });
Add(new Product() { ProductId = 9, ProductName = "Mobile-Data-Card",
Price = 2300 });
}
}
/// <summary>
/// The class implement INotifyPropertyChanged interface.
/// This is used for the Properties value changes.
/// </summary>
public class EmployeeInfo : INotifyPropertyChanged
{
int _EmpNo;
public int EmpNo
{
get { return _EmpNo; }
set
{
_EmpNo = value;
OnPropertychanged("Salary");
}
}
string _EmpName;
public string EmpName
{
get { return _EmpName; }
set
{
_EmpName = value;
OnPropertychanged("Salary");
}
}
string _DeptName;
public string DeptName
{
get { return _DeptName; }
set
{
_DeptName = value;
OnPropertychanged("Salary");
}
}
int _Salary;
public int Salary
{
get { return _Salary; }
set
{
_Salary = value;
OnPropertychanged("Salary");
}
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertychanged(string pName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(pName));
}
}
}
public class EmployeeInfoCollection : ObservableCollection<EmployeeInfo>
{
public EmployeeInfoCollection()
{
Add(new EmployeeInfo()
{EmpNo=1,EmpName="Mahesh",DeptName="Purchase",Salary=34000 });
Add(new EmployeeInfo() { EmpNo = 2, EmpName = "Amit", DeptName =
"Purchase", Salary = 44000 });
Add(new EmployeeInfo() { EmpNo = 3, EmpName = "Ajay", DeptName =
"HRD", Salary = 14000 });
Add(new EmployeeInfo() { EmpNo = 4, EmpName = "Kishor", DeptName =
"HRD", Salary = 24000 });
Add(new EmployeeInfo() { EmpNo = 5, EmpName = "Sudhir", DeptName =
"Accounts", Salary = 54000 });
Add(new EmployeeInfo() { EmpNo = 6, EmpName = "Deepak", DeptName =
"Accounts", Salary = 44000 });
Add(new EmployeeInfo() { EmpNo = 7, EmpName = "Vikas", DeptName =
"Sales", Salary = 74000 });
Add(new EmployeeInfo() { EmpNo = 8, EmpName = "Prashant", DeptName =
"Sales", Salary = 84000 });
}
}
Step 4: Design the MainWindow.xaml as below:
The above window has a DataGrid with 3 columns for Product information. The Button click will start sorting on the Product record based upon the Price property
Step 5: Add the following code in the code behind of the MainWindow loaded event:
Declare objects at class level:
//The dispatcher times which will
//Update Price property after specific duration
DispatcherTimer dispTimer = new DispatcherTimer();
ObservableCollection<Product> lstProducts = new
ObservableCollection<Product>();
The code on the loaded event:
/// <summary>
/// Code for showing product information in DataGrid
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
var productCollection = new ProductCollection();
dgproducts.ItemsSource =productCollection;
Random random = new Random();
var Products = new ProductCollection();
//Code for updating Price
foreach (var item in Products)
{
lstProducts.Add(new Product()
{
ProductId = item.ProductId,
ProductName = item.ProductName,
Price = item.Price
});
}
dgproducts.ItemsSource = lstProducts;
}
The above code gets ProductCollection and binds it with the DataGrid. Add the following code in the click event of the Live Sort button:
void dispTimer_Tick(object sender, EventArgs e)
{
Random random = new Random();
foreach (var pc in lstProducts)
{
pc.Price += random.Next(100) - 5;
}
}
/// <summary>
/// On the click on the button the
/// Sorting will started. It uses below programming featutes
/// 1.ICollectionView: Enable collections to apply Custom Sorting,
/// Filtering and Grouping
/// 2.ICollectionViewLiveShaping: Defining Sorting, Grouping, Filtering
/// on ColectionView in Real Time
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void btnlivesort_Click(object sender, RoutedEventArgs e)
{
ICollectionView pView =
CollectionViewSource.GetDefaultView(lstProducts);
pView.SortDescriptions.Add(new SortDescription("Price",
ListSortDirection.Ascending));
var productview =
(ICollectionViewLiveShaping)CollectionViewSource.GetDefaultView(
lstProducts);
productview.IsLiveSorting = true;
dispTimer.Interval = TimeSpan.FromMilliseconds(600);
dispTimer.Tick += dispTimer_Tick;
dispTimer.Start();
}
}
Here we instantiate an object of the CollectionViewSource, call the GetDefaultView() method passing the lstproducts ObservableCollection to it. This method returns the default view. Note that we have defined a ‘SortDescription’ that tells the view to use the ‘Price’ property to sort the data. As you can probably tell, SortDescription object encapsulates sorting behavior of the collection. In the above case we’ve defined the behaviour where the collection is sorted in ascending order by the Price property.
To show live shaping in action we have put a timer that changes the Prices randomly on the timer’s tick every 600 milliseconds. Since we set IsLiveSorting property to true, the view is resorted as soon as the Prices are updated.
Step 6: Run the application, the Shell window will be displayed after clicking on ‘Live Sorting’ button, the MainWindow will be displayed as below:
Initially all Products are shown ordered by ProductId. Now click on ‘Live Sort’ button, the Prices will be updated. Products will get continuously re-sorted as the price changes. You can see a snapshot of the updated prices below:
As we can see products are now sorted by Price. So just because we had LiveSorting enabled no additional code was required to re-draw the UI once the prices changed.
Live Filtering
Now let’s look at Filtering.
Step 7: Open MainWindow_Filter. Xaml as below:
<Window.Resources>
<DataTemplate x:Key="dname">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding DeptName}"></TextBlock>
</StackPanel>
</DataTemplate>
</Window.Resources>
In the above window, we have DataGrid and ListBox. The DataGrid displays Employee Information and ListBox shoes the List of Departments based upon which the filtering will occur.
Step 8: Add the following code to the code behind for MainWindow_Filter:
EmployeeInfoCollection Emplyees;
List<string> Departments;
public string DeptName { get; set; }
Add the following to the Window_Loaded event:
/// <summary>
/// Displays Employee information in the DataGrid
/// and DeptName in ListBox
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Window_Loaded(object sender, RoutedEventArgs e)
{
Emplyees = new EmployeeInfoCollection();
dgemp.ItemsSource = Emplyees;
Departments = Emplyees.Select(d => d.DeptName).Distinct().ToList();
lstdept.ItemsSource = Departments;
}
The above code will display the Employee Information in DataGrid and DeptName in ListBox. The ItemsSource property of the DataGrid (dgEmp) is set to the Departments collection.
Add the following Helper method to check condition for the DeptName, this will be used for filtering Employee Information:
/// <summary>
/// Helper method to check if
/// the DeptName matches
/// </summary>
/// <param name="d"></param>
/// <returns></returns>
bool IsMatchFound(object d)
{
bool res = false;
EmployeeInfo emp = d as EmployeeInfo;
if (emp.DeptName == DeptName)
{
res = true;
}
return res;
}
Add the followingcode in the SelectionChanged event of the ListBox:
/// <summary>
/// The selection changed event on the ListBox to enable
/// Filtering on the DeptName. It uses the below API
/// 1. ListCollectionView: Represents collection View for collections
/// implementing IList
/// 2. Define Filter for DeptName to find the Match.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void lstdept_SelectionChanged(object sender,
SelectionChangedEventArgs e)
{
ListCollectionView empView =
CollectionViewSource.GetDefaultView(dgemp.ItemsSource) as
ListCollectionView;
// Enable Live Filtering of the ListViewCollection
empView.IsLiveFiltering = true;
// Enable the filtering on DeptName
empView.LiveFilteringProperties.Add("DeptName");
DeptName = lstdept.SelectedItem.ToString();
// Filter based upon DeptName
empView.Filter = new Predicate< object >(IsMatchFound);
// Refresh Collection
empView.Refresh();
}
The above code gets the default view using the CollectionViewSource.GetDefaultView() method. To this method the DataGrid.ItemsSource is passed which contains Employee Information. This returns an object of ListCollectionView. The LiveFiltering property is enabled on this default view. The DeptName property is set for the filtering. The filter property is set which to a Predicate that executes IsMatchFound method to filter the Employee Information based upon the DeptName. The view is refreshed using Refresh() method and the DataGrid view shows the filtered data.
Step 9: Run the application, the Shell windows will be displayed, click on the ‘Live Filtering’ button, the below output will be displayed:
In the DataGrid all Employees are displayed. Now select DeptName from the ListBox, the DataGrid will be updated automatically as below:
The DataGrid automatically shows only Employee matching with the DeptName.
Conclusion
Live Shaping makes DataBound behaviour easier to implement thus make WPF an even more MVVM friendly framework. Using Live Shaping in WPF 4.5, the Views can be automatically updated when the collection is changed or in fact, when values in the collection are changed. This will help develop real-time systems using WPF without complex coding.
Download the entire source code of this article (Github)
This article has been editorially reviewed by Suprotim Agarwal.
C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.
We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).
Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.
Click here to Explore the Table of Contents or Download Sample Chapters!
Was this article worth reading? Share it with fellow developers too. Thanks!
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades 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), and Front-end technologies like Angular and React. Follow him on twitter @
maheshdotnet or connect with him on
LinkedIn