Much of the process of creating item and list UIs revolves around displaying and massaging data for usability. For example, formatting and parsing allow us to transform data as it's shuttled between a data source and a bound control. With regard to list data sources, users are accustomed to transforming the data as it appears in the UI, most often to facilitate the location of specific data items. The most common style of transformation is sorting, which allows users to find specific data in a list based on order. Instead of looking through all the items in a list, users might prefer filtering the items that don't match certain criteria in order to focus on the subset of items that do. Although not a transformation, searching is the most immediate way to find a specific data item. In the data binding world, these features are provided by a data view, a special class that provides custom views of list data sources. To provide these features, data view classes implement two data binding interfaces: IBindingList and IBindingListView. As you saw in Chapter 16, IBindingList not only provides list management and change notification using BindingList<T>, but it also allows classes to implement single-column sorting and searching in a well-known data binding fashion. Similarly, IBindingListView is used to provide multiple-column sorting and filtering support. BindingSource exposes both interfaces for complex bound controls to use, and it exposes simplified methods for sorting, filtering, and searching. SortingData can be sorted through one or more columns in either ascending or descending order. Setting the sort criteria is a simple matter of setting the Sort string property on the BindingSource that encapsulates the data view: // DataViewForm.cs partial class DataViewForm : Form { ... void sortButton_Click(object sender, EventArgs e) { string sortCriteria = this.sortCriteriaTextBox.Text; this.customersBindingSource.Sort = sortCriteria; } } The sort criteria string is parsed to the following format: DataSourcePropertyName [ASC|DESC][, DataSourcePropertyName [ASC|DESC]]* Figure 17.30 illustrates the effects of sorting in this way. Figure 17.30. Creating a Sorted View
If you need to return the data view to its presorted order, you invoke BindingSource.RemoveSort: // DataViewForm.cs partial class DataViewForm : Form { ... void removeSortButton_Click(object sender, EventArgs e) { this.customersBindingSource.RemoveSort(); } } Note that by sorting the BindingSource component, you are changing the underlying list data source in a way that affects all controls bound to the BindingSource. When such a change occurs, list data sources are required to issue a list change notification, ensuring that all simple-bound and complex-bound controls automatically update themselves visually to reflect the change. This is why the DataGridView appears re-sorted, even though it didn't initiate the sort itself. And, this is also why the DataGridView doesn't display sort chevrons in the column headers to indicate the current sort orders; full-list change notifications don't broadcast that it is specifically a sort operation that initiated them, so the DataGridView, and other bound controls, can refresh only the list items. Even so, no control provides enough UI to initiate and show multiple column sorting, as per our example. The native UI is single-column sorting provided by the DataGridView, which users access by left-clicking column headers. This sort is indicated visually with a sort chevron, as shown in Figure 17.31.[11]
Figure 17.31. Single-Column Sorting Natively Supported by DataGridView
Although DataGridView does provide UI support for single-column sorting; no native Windows Forms control provides a UI for either filtering or searching, discussed next. FilteringFiltering is the ability to reduce the visible data to the set of items that meet specific criteria. In lieu of native UI support, you need to provide a way for users to specify filter criteria and apply it to the data source. The BindingSource component's Filter property enables the latter, and you collect the filter string via a text box to do the former: // DataViewForm.cs partial class DataViewForm : Form { ... void filterButton_Click(object sender, EventArgs e) { string filterCriteria = this.filterCriteriaTextBox.Text; this.customersBindingSource.Filter = filterCriteria; } } As with sorting, a list change notification is issued from the data source when Filter is set, thereby ensuring that all bound clients update themselves visually, as with the DataGridView shown in Figure 17.32. Figure 17.32. Creating a Filtered View
To remove the filtered view to display all items in the list data source, you call BindingSource.RemoveFilter: // DataViewForm.cs partial class DataViewForm : Form { ... void removeFilterButton_Click(object sender, EventArgs e) { this.customersBindingSource.RemoveFilter(); } } Note that if you add a new item, via the UI, that doesn't match the filter criteria, it is added to the list data source and removed from the filtered view. You can also sort a filtered view and filter a sorted view if needed. SearchingSearching uses specific criteria to find a particular item but doesn't change the list itself. Consequently, you can search sorted and filtered views. Again, none of the Windows Forms controls provides a UI to enable searching. Instead, you need to build a UI that harvests two values from the user: the column to be searched, and the value searched for in that column, before these are passed as arguments to the BindingSource component's Find method: // DataViewForm.cs partial class DataViewForm : Form { ... void searchButton_Click(object sender, EventArgs e) { // Get search criteria string searchColumn = this.searchColumnTextBox.Text; string searchValue = this.searchValueTextBox.Text; // Execute search int index = this.customersBindingSource.Find(searchColumn, searchValue); // Select row this.customersBindingSource.Position = index; } } The Find method returns the index of the first item it finds whose property value (specified by the search column) matches the search value. A search doesn't cause a full-list change notification to be issued from the list data source, because the list hasn't changed. Thus, you need to select the found item manually in a way that ensures that all bound controls point to it. This is why we set the BindingSource component's Position property, which changes the current item in the list data source and consequently causes the DataGridView to change its selected row, as illustrated in Figure 17.33. Figure 17.33. Searching a ViewBindingSource.Find can return only a single index, and this is consistent with its behavior of returning the index to the first row that's found. For example, if the search were run again, the same item would be found, not the next item that matches the criteria. BindingSource.Find does not implicitly support FindNext. Checking for View SupportTyped data sets support simple and advanced sorting, searching, and filtering, but not all item types completely implement IBindingList and IBindingListView. Both interfaces offer methods for checking whether those features are supported: IBindingList.SupportsSearching, IBindingList.SupportSearching, IBindingListView.SupportsAdvancedSearching, and IBindingListView.SupportsFiltering. Instead of attempting to acquire references to those interfaces on a data source, you can use the BindingSource component's helper methods: // DataViewForm.cs public partial class DataViewForm : Form { ... void DataViewForm_Load(object sender, EventArgs e) { ... this.sortButton.Enabled = this.customersBindingSource.SupportsSorting | this.customersBindingSource.SupportsAdvancedSorting; this.searchButton.Enabled = this.customersBindingSource.SupportsSearching; this.filterButton.Enabled = this.customersBindingSource.SupportsFiltering; } Data views, item UIs, and list UIs form the basic UI models that allow you to build a variety of real-world data-bound UIs. |