Introduction
This article is about creating a ListView
which has extended capabilities such as having its data filtered and exported to Excel, while supporting the MVVM pattern and having the ListView.ItemsSource
type not standing in the way, while also having the ability to determine how each type can be filtered.
Background
I was given an assignment at work of creating an application which presents some data. The data had to be filtered, in a way that resembles Windows file filtering. Being a hug MVVM supporter, I wanted my code to be clean, decoupled, as well as generic, so it could be used in other cases as well.
Using the code
Since the ListView
is required to handle all data types, I wanted the decision on how the filtering and exporting is done to be made by the ViewModel
. Thus, I can have a list of student details being filtered by the students' private name, and a list of cars being filtered only by the manufacturer. To handle this task, I created an interface which the ViewModel
s must implement:
public interface IEnhancedListObject
{
bool Filter(string strFilter);
void WriteHeaderIntoExcelSheet(Worksheet worksheet, ref int nRow, ref int nColumn);
void WriteDataIntoExcelSheet(Worksheet worksheet, ref int nRow, ref int nColumn);
}
The interface holds three methods:
Filter
: Decides if the element passes the given string.
WriteHeaderIntoExcelSheet
: Writes the columns of the exported data.
WriteDataIntoExcelSheet
: Writes the actual data into the Excel sheet.
Since I wanted my code to be as robust as possible, I tried to avoid having any code written in the Xaml.cs files. I used the attached properties for that matter.
Filtering:
public class ListViewFilterExtension
{
public static readonly DependencyProperty FilterSourceProperty =
DependencyProperty.RegisterAttached("FilterSource",
typeof(TextBox), typeof(ListViewFilterExtension),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnTextBoxSet)));
public static TextBox GetFilterSource(DependencyObject d)
{
return (TextBox)d.GetValue(FilterSourceProperty);
}
public static void SetFilterSource(DependencyObject d, TextBox value)
{
d.SetValue(FilterSourceProperty, value);
}
private static void OnTextBoxSet(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
ListView listView = d as ListView;
TextBox textBox = e.NewValue as TextBox;
if ((listView != null) && (textBox != null))
{
textBox.TextChanged += delegate(object sender,
TextChangedEventArgs tcea)
{
ICollectionView view =
CollectionViewSource.GetDefaultView(listView.ItemsSource);
view.Filter = null;
view.Filter = delegate(object obj)
{
IEnhancedListObject filterableObject = obj as IEnhancedListObject;
if (filterableObject == null) return false;
string textFilter = ((TextBox)sender).Text;
if (textFilter.Trim().Length == 0) return true;
return filterableObject.Filter(textFilter.Trim());
};
};
}
}
}
The filtering assumes the filter string would be taken from a TextBox
. The attached property is given a TextBox
as a parameter, and registers itself as a listener to the TextChanged
event. The delegate it registers goes through the elements in the ListView.ItemSource
, assuming they are implementing the IEnhancedListObject
interface, and runs the filter operation on them.
Export to Excel:
public class ListViewExcelExporterExtension
{
public static readonly DependencyProperty ExcelExporterProperty =
DependencyProperty.RegisterAttached("ExcelExporter",
typeof(ListView), typeof(ListViewExcelExporterExtension),
new FrameworkPropertyMetadata(null,
new PropertyChangedCallback(OnExcelExportRequest)));
public static ListView GetExcelExporter(DependencyObject d)
{
return (ListView)d.GetValue(ExcelExporterProperty);
}
public static void SetExcelExporter(DependencyObject d, ListView value)
{
d.SetValue(ExcelExporterProperty, value);
}
private static void OnExcelExportRequest(DependencyObject d,
DependencyPropertyChangedEventArgs e)
{
System.Windows.Controls.Button button = d as System.Windows.Controls.Button;
ListView listView = e.NewValue as ListView;
if ((button != null) && (listView != null))
{
button.Click += delegate(object sender, RoutedEventArgs er)
{
Microsoft.Office.Interop.Excel.Application application =
new Microsoft.Office.Interop.Excel.Application();
application.Visible = true;
CultureInfo currentCulture = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Workbook workbook = application.Workbooks.Add(Missing.Value);
Worksheet worksheet = workbook.ActiveSheet as Worksheet;
if (worksheet != null)
{
worksheet._DisplayRightToLeft = 0;
if (listView.Items.Count > 0)
{
IEnhancedListObject vm = listView.Items[0] as IEnhancedListObject;
int nRow = 1;
int nColumn = 1;
if (vm != null)
{
vm.WriteHeaderIntoExcelSheet(worksheet, ref nRow, ref nColumn);
}
foreach (var item in listView.Items)
{
vm = item as IEnhancedListObject;
vm.WriteDataIntoExcelSheet(worksheet, ref nRow, ref nColumn);
}
}
}
Thread.CurrentThread.CurrentCulture = currentCulture;
};
}
}
}
The exporting attached property assumes it would be used by a Button
. It gets a ListView
as its parameter. It registeres itself to the Button.Click
operation. Upon click, it goes through the ListView.ItemsSource
, assuming they are implementing the IEnhancedListObject
interface, and uses the interface to add the data into the Excel sheet.
In the example, I'm presenting a list of the 500 companies in the S & P index, which I've taken from Wikipedia.
History
- June 29, 2010: Initial version.