Click here to Skip to main content
Click here to Skip to main content

Enhanced ListView Operations Using MVVM

, 29 Jun 2010
Rate this:
Please Sign up or sign in to vote.
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, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Izhar Lotem
Software Developer
Israel Israel
Software Developer in a promising Clean-Tech company

Comments and Discussions

 
GeneralUnable to run your project on Visual Studio 2010 PinmemberShashank406-Aug-10 22:24 
GeneralRe: Unable to run your project on Visual Studio 2010 PinmemberIzhar Lotem8-Aug-10 3:51 
Generalif you like MVVM you may like these PinmvpSacha Barber30-Jun-10 22:13 
GeneralJust a code dump... PinmvpDave Kreskowiak29-Jun-10 2:00 
GeneralRe: Just a code dump... PinmemberIzhar Lotem29-Jun-10 2:45 
GeneralExplanation required PinmvpMd. Marufuzzaman29-Jun-10 1:50 
GeneralRe: Explanation required PinmemberIzhar Lotem29-Jun-10 2:43 
GeneralRe: Explanation required PinmvpMd. Marufuzzaman29-Jun-10 3:03 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140718.1 | Last Updated 29 Jun 2010
Article Copyright 2010 by Izhar Lotem
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid