Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Enhanced ListView Operations Using MVVM

0.00/5 (No votes)
29 Jun 2010 1  
Filter and export to Excel ListView data using MVVM standards.

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 ViewModels must implement:

/// <summary>
/// This is an iterface for every object which
/// can have its data filtered given a certain string
/// </summary>
public interface IEnhancedListObject
{
    /// <summary>
    /// Filter the data given a filter string
    /// </summary>
    /// <param name="strFilter"></param>
    /// <returns></returns>
    bool Filter(string strFilter);
    /// <summary>
    /// Write the Header into the excel sheet
    /// </summary>
    /// <param name="worksheet">The worksheet to use</param>
    /// <param name="nRow">The row to start with</param>
    /// <param name="nColumn">The column to start with</param>
    /// <returns></returns>
    void WriteHeaderIntoExcelSheet(Worksheet worksheet, ref int nRow, ref int nColumn);
    /// <summary>
    /// Write the data into an Excel worksheet
    /// </summary>
    /// <param name="worksheet">The worksheet to use</param>
    /// <param name="nRow">The row to start with</param>
    /// <param name="nColumn">The column to start with</param>
    /// <returns></returns>
    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
{
    /// <summary>
    /// FilterSource Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty FilterSourceProperty =
        DependencyProperty.RegisterAttached("FilterSource",
        typeof(TextBox), typeof(ListViewFilterExtension),
            new FrameworkPropertyMetadata(null,
                new PropertyChangedCallback(OnTextBoxSet)));


    /// <summary>
    /// Gets the FilterSource property.
    /// </summary>
    public static TextBox GetFilterSource(DependencyObject d)
    {
        return (TextBox)d.GetValue(FilterSourceProperty);
    }

    /// <summary>
    /// Sets the FilterSource property.
    /// </summary>
    public static void SetFilterSource(DependencyObject d, TextBox value)
    {
        d.SetValue(FilterSourceProperty, value);
    }

    /// <summary>
    /// Handles changes to the FilterSource property.
    /// </summary>
    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;
                    // the filter is empty - pass all items

                    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
{
    /// <summary>
    /// ExcelExporter Attached Dependency Property
    /// </summary>
    public static readonly DependencyProperty ExcelExporterProperty =
        DependencyProperty.RegisterAttached("ExcelExporter",
        typeof(ListView), typeof(ListViewExcelExporterExtension),
            new FrameworkPropertyMetadata(null,
                new PropertyChangedCallback(OnExcelExportRequest)));


    /// <summary>
    /// Gets the ExcelExporter property. 
    /// </summary>
    public static ListView GetExcelExporter(DependencyObject d)
    {
        return (ListView)d.GetValue(ExcelExporterProperty);
    }

    /// <summary>
    /// Sets the ExcelExporter property. 
    /// </summary>
    public static void SetExcelExporter(DependencyObject d, ListView value)
    {
        d.SetValue(ExcelExporterProperty, value);
    }

    /// <summary>
    /// Handles changes to the FilterSource property.
    /// </summary>
    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;

                // Workaround to fix MCST bug - The threads culture
                //          info must be en-US for the Excel sheet
                //          to open properly
                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)
                {
                    // Set the display to show from left to right
                    worksheet._DisplayRightToLeft = 0;

                    if (listView.Items.Count > 0)
                    {
                        // Write the header
                        IEnhancedListObject vm = listView.Items[0] as IEnhancedListObject;
                        int nRow = 1;
                        int nColumn = 1;
                        if (vm != null)
                        {
                            vm.WriteHeaderIntoExcelSheet(worksheet, ref nRow, ref nColumn);
                        }
                        // Write all the elements in the view model
                        foreach (var item in listView.Items)
                        {
                            vm = item as IEnhancedListObject;
                            vm.WriteDataIntoExcelSheet(worksheet, ref nRow, ref nColumn);
                        }
                    }
                }
                // Work around MSFT bug - return the culture info to the previous set
                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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here