Created
February 24, 2019 23:21
-
-
Save t3knoid/b0e275bb61675704e1a72e35a7ea910d to your computer and use it in GitHub Desktop.
Use this class to define a datagridview datasource that is a list. Use this in conjunction with the Microsoft DataGridAutoFilter library to create an automatically filtered datagridview table.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.ComponentModel; | |
using System.Reflection; | |
using System.Collections; | |
using System.Text.RegularExpressions; | |
namespace FilteredBindingList | |
{ | |
public class FilteredBindingList<T> : BindingList<T>, IBindingListView | |
{ | |
public FilteredBindingList() | |
{ } | |
public FilteredBindingList(IList<T> list) | |
: base(list) | |
{ | |
foreach (var v in list) | |
{ | |
originalListValue.Add(v); | |
} | |
} | |
private List<T> originalListValue = new List<T>(); | |
public List<T> OriginalList | |
{ | |
get | |
{ return originalListValue; } | |
} | |
#region Searching | |
protected override bool SupportsSearchingCore | |
{ | |
get | |
{ | |
return true; | |
} | |
} | |
protected override int FindCore(PropertyDescriptor prop, object key) | |
{ | |
// Get the property info for the specified property. | |
PropertyInfo propInfo = typeof(T).GetProperty(prop.Name); | |
T item; | |
if (key != null) | |
{ | |
// Loop through the items to see if the key | |
// value matches the property value. | |
for (int i = 0; i < Count; ++i) | |
{ | |
item = (T)Items[i]; | |
if (propInfo.GetValue(item, null).Equals(key)) | |
return i; | |
} | |
} | |
return -1; | |
} | |
public int Find(string property, object key) | |
{ | |
// Check the properties for a property with the specified name. | |
PropertyDescriptorCollection properties = | |
TypeDescriptor.GetProperties(typeof(T)); | |
PropertyDescriptor prop = properties.Find(property, true); | |
// If there is not a match, return -1 otherwise pass search to | |
// FindCore method. | |
if (prop == null) | |
return -1; | |
else | |
return FindCore(prop, key); | |
} | |
#endregion Searching | |
#region Sorting | |
ArrayList sortedList; | |
FilteredBindingList<T> unsortedItems; | |
bool isSortedValue; | |
ListSortDirection sortDirectionValue; | |
PropertyDescriptor sortPropertyValue; | |
protected override bool SupportsSortingCore | |
{ | |
get { return true; } | |
} | |
protected override bool IsSortedCore | |
{ | |
get { return isSortedValue; } | |
} | |
protected override PropertyDescriptor SortPropertyCore | |
{ | |
get { return sortPropertyValue; } | |
} | |
protected override ListSortDirection SortDirectionCore | |
{ | |
get { return sortDirectionValue; } | |
} | |
public void ApplySort(string propertyName, ListSortDirection direction) | |
{ | |
// Check the properties for a property with the specified name. | |
PropertyDescriptor prop = TypeDescriptor.GetProperties(typeof(T))[propertyName]; | |
// If there is not a match, return -1 otherwise pass search to | |
// FindCore method. | |
if (prop == null) | |
throw new ArgumentException(propertyName + | |
" is not a valid property for type:" + typeof(T).Name); | |
else | |
ApplySortCore(prop, direction); | |
} | |
protected override void ApplySortCore(PropertyDescriptor prop, | |
ListSortDirection direction) | |
{ | |
sortedList = new ArrayList(); | |
// Check to see if the property type we are sorting by implements | |
// the IComparable interface. | |
Type interfaceType = prop.PropertyType.GetInterface("IComparable"); | |
if (interfaceType != null) | |
{ | |
// If so, set the SortPropertyValue and SortDirectionValue. | |
sortPropertyValue = prop; | |
sortDirectionValue = direction; | |
unsortedItems = new FilteredBindingList<T>(); | |
if (sortPropertyValue != null) | |
{ | |
// Loop through each item, adding it the the sortedItems ArrayList. | |
foreach (Object item in this.Items) | |
{ | |
unsortedItems.Add((T)item); | |
sortedList.Add(prop.GetValue(item)); | |
} | |
} | |
// Call Sort on the ArrayList. | |
sortedList.Sort(); | |
T temp; | |
// Check the sort direction and then copy the sorted items | |
// back into the list. | |
if (direction == ListSortDirection.Descending) | |
sortedList.Reverse(); | |
for (int i = 0; i < this.Count; i++) | |
{ | |
int position = Find(prop.Name, sortedList[i]); | |
if (position != i && position > 0) | |
{ | |
temp = this[i]; | |
this[i] = this[position]; | |
this[position] = temp; | |
} | |
} | |
isSortedValue = true; | |
// If the list does not have a filter applied, | |
// raise the ListChanged event so bound controls refresh their | |
// values. Pass -1 for the index since this is a Reset. | |
if (String.IsNullOrEmpty(Filter)) | |
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); | |
} | |
else | |
// If the property type does not implement IComparable, let the user | |
// know. | |
throw new InvalidOperationException("Cannot sort by " | |
+ prop.Name + ". This" + prop.PropertyType.ToString() + | |
" does not implement IComparable"); | |
} | |
protected override void RemoveSortCore() | |
{ | |
this.RaiseListChangedEvents = false; | |
// Ensure the list has been sorted. | |
if (unsortedItems != null && originalListValue.Count > 0) | |
{ | |
this.Clear(); | |
if (Filter != null) | |
{ | |
unsortedItems.Filter = this.Filter; | |
foreach (T item in unsortedItems) | |
this.Add(item); | |
} | |
else | |
{ | |
foreach (T item in originalListValue) | |
this.Add(item); | |
} | |
isSortedValue = false; | |
this.RaiseListChangedEvents = true; | |
// Raise the list changed event, indicating a reset, and index | |
// of -1. | |
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, | |
-1)); | |
} | |
} | |
public void RemoveSort() | |
{ | |
RemoveSortCore(); | |
} | |
public override void EndNew(int itemIndex) | |
{ | |
// Check to see if the item is added to the end of the list, | |
// and if so, re-sort the list. | |
if (IsSortedCore && itemIndex > 0 | |
&& itemIndex == this.Count - 1) | |
{ | |
ApplySortCore(this.sortPropertyValue, | |
this.sortDirectionValue); | |
base.EndNew(itemIndex); | |
} | |
} | |
#endregion Sorting | |
#region AdvancedSorting | |
public bool SupportsAdvancedSorting | |
{ | |
get { return false; } | |
} | |
public ListSortDescriptionCollection SortDescriptions | |
{ | |
get { return null; } | |
} | |
public void ApplySort(ListSortDescriptionCollection sorts) | |
{ | |
throw new NotSupportedException(); | |
} | |
#endregion AdvancedSorting | |
#region Filtering | |
public bool SupportsFiltering | |
{ | |
get { return true; } | |
} | |
public void RemoveFilter() | |
{ | |
if (Filter != null) Filter = null; | |
} | |
private string filterValue = null; | |
public string Filter | |
{ | |
get | |
{ | |
return filterValue; | |
} | |
set | |
{ | |
if (filterValue == value) return; | |
// If the value is not null or empty, but doesn't | |
// match expected format, throw an exception. | |
if (!string.IsNullOrEmpty(value) && | |
!Regex.IsMatch(value, | |
BuildRegExForFilterFormat(), RegexOptions.Singleline)) | |
throw new ArgumentException("Filter is not in " + | |
"the format: propName[<>=]'value'."); | |
//Turn off list-changed events. | |
RaiseListChangedEvents = false; | |
// If the value is null or empty, reset list. | |
if (string.IsNullOrEmpty(value)) | |
ResetList(); | |
else | |
{ | |
int count = 0; | |
string[] matches = value.Split(new string[] { " AND " }, | |
StringSplitOptions.RemoveEmptyEntries); | |
while (count < matches.Length) | |
{ | |
string filterPart = matches[count].ToString(); | |
// Check to see if the filter was set previously. | |
// Also, check if current filter is a subset of | |
// the previous filter. | |
if (!String.IsNullOrEmpty(filterValue) | |
&& !value.Contains(filterValue)) | |
ResetList(); | |
// Parse and apply the filter. | |
SingleFilterInfo filterInfo = ParseFilter(filterPart); | |
ApplyFilter(filterInfo); | |
count++; | |
} | |
} | |
// Set the filter value and turn on list changed events. | |
filterValue = value; | |
RaiseListChangedEvents = true; | |
OnListChanged(new ListChangedEventArgs(ListChangedType.Reset, -1)); | |
} | |
} | |
// Build a regular expression to determine if | |
// filter is in correct format. | |
public static string BuildRegExForFilterFormat() | |
{ | |
StringBuilder regex = new StringBuilder(); | |
// Look for optional literal brackets, | |
// followed by word characters or space. | |
regex.Append(@"\[?[\w\s]+\]?\s?"); | |
// Add the operators: > < or =. | |
regex.Append(@"[><=]"); | |
//Add optional space followed by optional quote and | |
// any character followed by the optional quote. | |
regex.Append(@"\s?'?.+'?"); | |
return regex.ToString(); | |
} | |
private void ResetList() | |
{ | |
this.ClearItems(); | |
foreach (T t in originalListValue) | |
this.Items.Add(t); | |
if (IsSortedCore) | |
ApplySortCore(SortPropertyCore, SortDirectionCore); | |
} | |
protected override void OnListChanged(ListChangedEventArgs e) | |
{ | |
// If the list is reset, check for a filter. If a filter | |
// is applied don't allow items to be added to the list. | |
if (e.ListChangedType == ListChangedType.Reset) | |
{ | |
if (Filter == null || Filter == "") | |
AllowNew = true; | |
else | |
AllowNew = false; | |
} | |
// Add the new item to the original list. | |
if (e.ListChangedType == ListChangedType.ItemAdded) | |
{ | |
OriginalList.Add(this[e.NewIndex]); | |
if (!String.IsNullOrEmpty(Filter)) | |
//if (Filter == null || Filter == "") | |
{ | |
string cachedFilter = this.Filter; | |
this.Filter = ""; | |
this.Filter = cachedFilter; | |
} | |
} | |
// Remove the new item from the original list. | |
if (e.ListChangedType == ListChangedType.ItemDeleted) | |
OriginalList.RemoveAt(e.NewIndex); | |
base.OnListChanged(e); | |
} | |
internal void ApplyFilter(SingleFilterInfo filterParts) | |
{ | |
List<T> results; | |
// Check to see if the property type we are filtering by implements | |
// the IComparable interface. | |
Type interfaceType = | |
TypeDescriptor.GetProperties(typeof(T))[filterParts.PropName] | |
.PropertyType.GetInterface("IComparable"); | |
if (interfaceType == null) | |
throw new InvalidOperationException("Filtered property" + | |
" must implement IComparable."); | |
results = new List<T>(); | |
// Check each value and add to the results list. | |
foreach (T item in this) | |
{ | |
if (filterParts.PropDesc.GetValue(item) != null) | |
{ | |
IComparable compareValue = | |
filterParts.PropDesc.GetValue(item) as IComparable; | |
int result = | |
compareValue.CompareTo(filterParts.CompareValue); | |
if (filterParts.OperatorValue == | |
FilterOperator.EqualTo && result == 0) | |
results.Add(item); | |
if (filterParts.OperatorValue == | |
FilterOperator.GreaterThan && result > 0) | |
results.Add(item); | |
if (filterParts.OperatorValue == | |
FilterOperator.LessThan && result < 0) | |
results.Add(item); | |
} | |
} | |
this.ClearItems(); | |
foreach (T itemFound in results) | |
this.Add(itemFound); | |
} | |
internal SingleFilterInfo ParseFilter(string filterPart) | |
{ | |
SingleFilterInfo filterInfo = new SingleFilterInfo(); | |
filterInfo.OperatorValue = DetermineFilterOperator(filterPart); | |
string[] filterStringParts = | |
filterPart.Split(new char[] { (char)filterInfo.OperatorValue }); | |
filterInfo.PropName = | |
filterStringParts[0].Replace("[", ""). | |
Replace("]", "").Replace(" AND ", "").Trim(); | |
// Get the property descriptor for the filter property name. | |
PropertyDescriptor filterPropDesc = | |
TypeDescriptor.GetProperties(typeof(T))[filterInfo.PropName]; | |
// Convert the filter compare value to the property type. | |
if (filterPropDesc == null) | |
throw new InvalidOperationException("Specified property to " + | |
"filter " + filterInfo.PropName + | |
" on does not exist on type: " + typeof(T).Name); | |
filterInfo.PropDesc = filterPropDesc; | |
string comparePartNoQuotes = StripOffQuotes(filterStringParts[1]); | |
try | |
{ | |
TypeConverter converter = | |
TypeDescriptor.GetConverter(filterPropDesc.PropertyType); | |
filterInfo.CompareValue = | |
converter.ConvertFromString(comparePartNoQuotes); | |
} | |
catch (NotSupportedException) | |
{ | |
throw new InvalidOperationException("Specified filter" + | |
"value " + comparePartNoQuotes + " can not be converted" + | |
"from string. Implement a type converter for " + | |
filterPropDesc.PropertyType.ToString()); | |
} | |
return filterInfo; | |
} | |
internal FilterOperator DetermineFilterOperator(string filterPart) | |
{ | |
// Determine the filter's operator. | |
if (Regex.IsMatch(filterPart, "[^>^<]=")) | |
return FilterOperator.EqualTo; | |
else if (Regex.IsMatch(filterPart, "<[^>^=]")) | |
return FilterOperator.LessThan; | |
else if (Regex.IsMatch(filterPart, "[^<]>[^=]")) | |
return FilterOperator.GreaterThan; | |
else | |
return FilterOperator.None; | |
} | |
internal static string StripOffQuotes(string filterPart) | |
{ | |
// Strip off quotes in compare value if they are present. | |
if (Regex.IsMatch(filterPart, "'.+'")) | |
{ | |
int quote = filterPart.IndexOf('\''); | |
filterPart = filterPart.Remove(quote, 1); | |
quote = filterPart.LastIndexOf('\''); | |
filterPart = filterPart.Remove(quote, 1); | |
filterPart = filterPart.Trim(); | |
} | |
return filterPart; | |
} | |
#endregion Filtering | |
} | |
public struct SingleFilterInfo | |
{ | |
internal string PropName; | |
internal PropertyDescriptor PropDesc; | |
internal Object CompareValue; | |
internal FilterOperator OperatorValue; | |
} | |
// Enum to hold filter operators. The chars | |
// are converted to their integer values. | |
public enum FilterOperator | |
{ | |
EqualTo = '=', | |
LessThan = '<', | |
GreaterThan = '>', | |
None = ' ' | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment