Data sorting is an important grid feature that not just sorts data but also keeps it sorted when it changes in real time.
This topic contains the following sections.
- Setting sorting
- Sorting dynamically changing data
- Sorting unformatted values
- Custom sorting
- Docked rows
- Sorting and thread safety
- Sorting and performance
Required introduction
Setting sorting
Data sorting is defined and set in Header class. Dapfor's framework provides a simple and intuitive interface for this purpose. This interface enables provides an easy way to manage sorting, including multiple sorting. To sort data by one or several columns, a programmer just has to set Column.SortDirection to one of SortDirection values. Sorting sequence for different columns defines their levels during multiple sorting. The most frequent methods of sorting management in code are shown below.
C# | Copy |
---|---|
Header header = grid.Headers[0]; Column column1 = header["Column1"]; Column column2 = header["Column2"]; //Sort by the first column column1.SortDirection = SortDirection.Ascending; //Turn on the multiple sort by the second column column2.SortDirection = SortDirection.Descending; //Enumerate all sorted columns foreach (Column column in header.SortedColumns) { //Some code here... } //Clear the sort header.SortedColumns.Clear(); |
Sorting dynamically changing data
Data sorting in the grid is not limited to the initial sorting. When Column.SortDirection property is called, all data in the grid is sorted. However, data may change while the application is running. A simple example of this is price changes of financial instruments on the securities market. To move a row with the changed instrument in accordance with its new value, Row.Update()()()() method can be called. It provides much higher performance efficiency than re-sorting all grid rows. If a data object implements INotifyPropertyChanged interface, Row.Update()()()() method is called automatically.
C# | Copy |
---|---|
class Product : INotifyPropertyChanged { private readonly string _name; private double _price; public Product(string name) { _name = name; } public string Name { get { return _name; } } public double Price { get { return _price; } set { if(_price != value) { _price = value; if(PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs("Price")); } } } } public event PropertyChangedEventHandler PropertyChanged; } public void Populate(GridControl grid) { //Create some products Product product1 = new Product("Product 1"); Product product2 = new Product("Product 2"); product1.Price = 7; product2.Price = 9; //Add them to the grid grid.Rows.Add(product1); grid.Rows.Add(product2); //Sort the content grid.Headers[0]["Price"].SortDirection = SortDirection.Ascending; //The grid will move the product1 to the appropriate position product1.Price = 10; } |
Sorting unformatted values
Wpf GridControl sorts data only by unformatted values. For better understanding of reasons behind this, let's review different date presentations: American locale and French locale.
By default, in time of sorting row data is compared using IComparable interface. In other words, in most cases data objects return simple values (int, double, string, DateTime, TimeSpan, etc.) in their properties. The IComparable interface is implemented in all these object types. Thanks to this approach, the grid can compare not only rows with data of similar type but also data of completely different types. It is also important that sorting with IComparable compares values instead of formatted strings that are displayed in grid cells. To show the importance of this, we shall review an example of sorting by date represented with DateTime type.
XAML | Copy |
---|---|
<Window.Resources> <Converters:StringConverter x:Key="converter" FormatString="d"/> </Window.Resources> <df:GridControl Name="grid" > <df:GridControl.Headers> <df:Header> <df:Header.Columns> <df:Column Id="Date" Title="Date" SortDirection="Ascending" ValueConverter="{StaticResource converter}"/> </df:Header.Columns> </df:Header> </df:GridControl.Headers> </df:GridControl> |
C# | Copy |
---|---|
class DateExample { public DateExample(DateTime date) { Date = date; } public DateTime Date { get; set; } } public Window1() { InitializeComponent(); //Populate grid with random data Random random = new Random(); BindingList<DateExample> source = new BindingList<DateExample>(); for (int i = 0; i < 6; ++i) { source.Add(new DateExample(DateTime.Now + TimeSpan.FromDays(random.Next(1000)))); } grid.ItemsSource = source; } |
As it has been shown above, row sorting doesn't depend on used locale and on data presentation in grid cells. It's also worth mentioning that comparing two unformatted values (here - objects of DateTime type) has much higher performance than comparing two row objects of String type. Considering that moving one row involves about ln(n) comparisons, performance improvement of sorting all rows in a grid may be very significant.
Custom sorting
In some cases sorting rules may be more complex than comparison of two objects that implement IComparable interface. Let's review an example of comparing credit ratings assigned by rating agencies. For example, Standard & Poor's sets long-term credit ratings from AAA to D with intermediate values (e.g. BBB+, BBB and BBB-). Simple row comparison yields incorrect sorting results. To solve this problem, ICustomSort interface has been implemented.
C# | Copy |
---|---|
class RatingComparer : ICustomSort { public int Compare(Row row1, Row row2, string fieldId, object value1, object value2, int defaultResult) { int result = defaultResult; if (fieldId == "Rating") { string rating1 = (string) value1; string rating2 = (string) value2; if(!string.IsNullOrEmpty(rating1) && !string.IsNullOrEmpty(rating2)) { //compare two ratings: rating1 > rating2 => result is positive // rating1 == rating2 => result = 0 // rating1 < rating2 => result is negative //result = ... } } return result; } } grid.CustomSort = new RatingComparer(); |
Docked rows
Docked rows are rows that always stay on top or bottom of their hierarchy level disregarding sorting direction. For row docking Row.Dock property must have one of RowDockStyle value. Sequential docking of multiple rows fully excludes them from sorting. Extreme case of docking is assigning a top or bottom docking value to all grid rows. Row sorting will have no effect.
C# | Copy |
---|---|
//Dock row to the bottom grid.Rows[8].Dock = RowDockStyle.Bottom; //Dock row to the top grid.Rows[9].Dock = RowDockStyle.Top; //Undock row grid.Rows[0].Dock = RowDockStyle.None; |
Sorting and thread safety
When the grid works with data objects, it performs necessary synchronization on receiving notifications from INotifyPropertyChanged, IBindingList interfaces. On receiving a notification the grid first looks for a thread that sent this notification. If the notification is sent from a different thread, it is synchronized with the main thread. Depending on multi-threading synchronization mode, the calling thread may be locked at the moment of synchronization (synchronous mode) or keep running in parallel (asynchronous mode).
As the grid is working in the main thread, it processes only one data source change at a time. When data is changed simultaneously by multiple threads, the calling threads will either be locked at the moment of processing or changes will be processed basing on internal queue depending on the work mode. If ICustomSort interface is implemented, all its methods will be called only in GUI thread irrespective of the thread that sent a notification via INotifyPropertyChanged interface.
The above implementation enables correct thread protection of data presentation in MVVM model. All this enables direct binding of the grid to sources working in threads other than its main thread.
Sorting and performance
The grid is optimized for working with dynamic data that changes in real time. Resource consumption remains lowest irrespective of methods used for filling grid with data, data type and hierarchy level. For example, highly efficient integrated algorithms enable a grid with 5 000 rows to perform over 5 000 sorting operations per second. Here sorting means calling Row.Update()()()() method followed by row relocation. If after calling Row.Update()()()() method the row doesn’t move or moves outside the visible area of the grid, CPU resource consumption is much lower.
Returning to the event-driven model, we can say that objects of this model may freely send notifications via INotifyPropertyChanged / IBindingList interfaces without any threat of application hanging or performance dropping if sorting of dynamic real-time data is turned on.