Jan Schröder
IT-Dienstleistungen

Class LvSort - ListView with a sorter in C#

The C# class LvSort is adding sort functionality to the inherited class "ListView" by implementing a non case sensitive IComparer and visualizes the sort order by drawing an appropriate triangle in the sorting column header. It also adds the missing events "Scroll" and "Paint". 

 

Download the source (15 KB)

 

using System;
using System.Collections;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Drawing;
namespace SI.Controls
  {
    public class LvSort : System.Windows.Forms.ListView
  {
#region Description
    //
    //#######################################################################################
    //
    // class     LvSort
    // Author    Jan Schröder
    //           Schröder Informatik
    //           www.SchroederInformatik.de
    // Version   1.0.4
    // Date      05/05/24
    //
    // This class is adding sort functionality to the inherited class "ListView" by
    // implementing a non case sensitive IComparer and visualizes the sort order by drawing
    // an appropriate triangle in the sorting column header.
    //
    // It also adds the missing events "Scroll" and "Paint".
    //
    // Use this class as follows:
    //
    // 1. Copy "LvSort.cs" to your project.
    // 2. Add a "System.Windows.Forms.ListView" to your form.
    // 3. Replace "System.Windows.Forms.ListView" with "SI.Controls.LvSort" in
    //    the generated Code.
    //
    // The advantage of this kind of usage is, you will not have to deploy an additional DLL,
    // all code will be part of your executable.
    //
    //
    // Versions:
    // 1.0.0 05/03/02    First Version
    // 1.0.1 05/03/05    LblMeasureHeader.Font = MyListViewValue.Font in
    //                   ListViewColumnSorter.VisualizeOrder
    // 1.0.2 05/03/08    Invoking sort method when setting SortColum or Order, so setting one
    //                   of these properties (i.e. in a forms load event) will cause sorting.
    //                   Tip: To avoid an unnecessary sort, set Order to "none" before
    //                   setting the SortColumn and then Order to "ascending" or
    //                   "descending".
    //                   If you want to sort the list view by the fist two columns, notice
    //                   the following sequence:
    //                   1. ListView1.Order = SortOrder.None;
    //                   2. ListView1.SortColumn = 1;
    //                   3. ListView1.Order = SortOrder.Ascending;
    //                   4. ListView1.SortColumn = 0;
    //                   After step 2 there will be no sorting, because Order = None. After
    //                   step 3 the list view will be sorted by column 1 in ascending order.
    //                   After step 4 the list view will be sorted by column 0 in ascending
    //                   order and if there are items with identical text in column 0, they
    //                   will be sorted by column 1.
    // 1.0.3 05/03/08    base.ListViewItemSorter = null in properties Order and
    //                   SortColumn to avoid unnecessary sorting when loading the view.
    //                   To do so, Order has to be set to none or SortColumn to -1.
    // 1.0.4 05/05/24    Translation from VB into C#
    //
    //#######################################################################################
    //
#endregion
#region Constructors
    public LvSort()
    {
      base.ColumnClick += new ColumnClickEventHandler(MyBase_ColumnClick);
      LvwColumnSorter = new ListViewColumnSorter(this);
    }
#endregion
#region Data definition
    public string  FixedStringForLastItem
    {
      //
      // Fixed string for the last item, so it will stay the last item
      //
      get {
        return LvwColumnSorter.FixedStringForLastItem;
      }
      set {
        //
        // The IComparer has to know that
        //
        LvwColumnSorter.FixedStringForLastItem = value;
      }
    }
    public int SortColumn
    {
      //
      // Index of sort column
      //
      get {
        return LvwColumnSorter.SortColumn;
      }
      set {
        //
        // Put it through to IComparer
        //
        LvwColumnSorter.SortColumn = value;
        //
        // Let's do it
        //
        if ( LvwColumnSorter.Order == SortOrder.None ) {
          base.ListViewItemSorter = null;
        } else {
          if ( base.ListViewItemSorter == null ) {
            base.ListViewItemSorter = LvwColumnSorter;
          } else {
            base.Sort();
          }
        }
      }
    }
    public SortOrder Order
    {
      //
      // Sort order (none, ascending or descending)
      //
      get
      {
        return LvwColumnSorter.Order;
      }
      set
      {
        //
        // Last information needed for IComparer
        //
        LvwColumnSorter.Order = value;
        if ( value == SortOrder.None )
        {
          if ( LvwColumnSorter.SortColumn > -1 )
          {
            //
            // Setting the text causes a repaint of the header
            //
            base.Columns[LvwColumnSorter.SortColumn].Text =
              base.Columns[LvwColumnSorter.SortColumn].Text;
            //
            // if there has been a triangle from a previous sorting,
            // it is now deleted
            //
          }
          LvwColumnSorter.SortColumn = -1;
        }
        //
        // Let's do it
        //
        if ( LvwColumnSorter.SortColumn == -1 )
        {
          base.ListViewItemSorter = null;
        }
        else
        {
          if ( base.ListViewItemSorter == null )
          {
            base.ListViewItemSorter = LvwColumnSorter;
          }
          else
          {
            base.Sort();
          }
        }
      }
    }
    private LvlListenerClass LvlListener;
    private HdrListenerClass HdrListener;
    private ListViewColumnSorter LvwColumnSorter;
#endregion
#region event handling
    public event EventHandler Scroll;
    public event EventHandler MyPaint;
        private class LvlListenerClass : NativeWindow
    {
            //
            // Listen for operating system messages to raise the events
            // "Scroll" or "Paint" for the ListView
            //
            public event EventHandler Scroll;
            public event EventHandler MyPaint;
            private const int WM_HSCROLL = 0x114;
            private const int WM_VSCROLL = 0x115;
            private const int WM_PAINT = 0xF;
      private Control CtrlValue;
      public LvlListenerClass(Control Ctrl)
      {
        AssignHandle(Ctrl.Handle);
        CtrlValue = Ctrl;
      }
      protected override void WndProc(ref Message m)
      {
        base.WndProc(ref m);
        if ( m.Msg == WM_HSCROLL || m.Msg == WM_VSCROLL ) {
          Scroll(CtrlValue, new EventArgs());
        }
        if ( m.Msg == WM_PAINT ) {
          MyPaint(CtrlValue, new EventArgs());
        }
      }
      ~LvlListenerClass()
      {
        ReleaseHandle();
      }
    }
    private class HdrListenerClass : NativeWindow {
            //
            // Listen for operating system messages to raise the event
            // "HaederPaint", when the column headers are to be painted.
            // On this event, a triangle, symbolizing sort order and
            // column is to be drawn.
            //
            public event EventHandler HaederPaint;
            private const int WM_PAINT = 0xF;
            private Control CtrlValue;
      public HdrListenerClass( Control Ctrl,  System.IntPtr HeaderHandle)
      {
                AssignHandle(HeaderHandle);
                CtrlValue = Ctrl;
            }
      protected override void WndProc(ref Message m)
      {
        base.WndProc(ref m);
        if ( m.Msg == WM_PAINT ) {
          HaederPaint(CtrlValue, new EventArgs());
        }
      }
      ~HdrListenerClass()
      {
        ReleaseHandle();
      }
    }
    protected override void OnHandleCreated( EventArgs e)
    {
      //
      // Now it's the right time to do some initializations
      //
      base.OnHandleCreated(e);
      if (! this.DesignMode ) {
        LvlListener = new LvlListenerClass(this);
        LvlListener.MyPaint += new EventHandler(LvlListener_Paint);
        LvlListener.Scroll += new  EventHandler(LvlListener_Scroll);
        LvwColumnSorter.GetHeaderHandle();
        HdrListener = new HdrListenerClass(this, LvwColumnSorter.HeaderHandle);
        HdrListener.HaederPaint += new EventHandler(HdrListener_HaederPaint);
      }
    }
    private void LvlListener_Paint(object sender, EventArgs e)
    {
      //
      // Make this event public
      //
      if (MyPaint != null){MyPaint(sender, e);}
    }
    private void LvlListener_Scroll(object sender, EventArgs e)
    {
            //
            // Make this event public
            //
      if (Scroll != null){Scroll(sender, e);}
        }
    private void HdrListener_HaederPaint(object sender, System.EventArgs e)
    {
            //
            // The column headers has been painted, so draw the sort triangle
            //
            LvwColumnSorter.VisualizeOrder();
        }
    private void MyBase_ColumnClick(object sender,
      System.Windows.Forms.ColumnClickEventArgs e)
    {
            //
            // The user wants to sort the list view items
            //
            if ((e.Column == SortColumn) ) {
                //
                // The column has been clicked twice, so switch the sort order
                //
                if ((Order == SortOrder.Ascending) ) {
                    Order = SortOrder.Descending;
                } else {
                    Order = SortOrder.Ascending;
                }
            } else {
                //
                // It has to be sort ascending by the column
                //
                SortColumn = e.Column;
                Order = SortOrder.Ascending;
            }
        }
#endregion
#region API stuff
        //
        // The API function ChildWindowFromPoint is used to find out the window handle of
        // the columns header.
        //
    [DllImport("user32")] public static extern System.IntPtr
      ChildWindowFromPoint(System.IntPtr hwnd, int xPoint, int yPoint);
#endregion
#region Subroutines and functions
        private class ListViewColumnSorter : System.Collections.IComparer
    {
            //
            // This class implements an non case sensitve IComparer for
            // sorting items in a ListView
            //
            private int ColumnToSort;
            private SortOrder OrderOfSort;
            private CaseInsensitiveComparer ObjectCompare;
            private string FixedStringForLastItemValue;
            private LvSort MyListViewValue;
      public ListViewColumnSorter(LvSort MyListView)
      {
                ColumnToSort = 0;
                OrderOfSort = SortOrder.None;
                ObjectCompare = new CaseInsensitiveComparer();
                MyListViewValue = MyListView;
            }
      public string FixedStringForLastItem
      {
                //
                // Fixed string for the last item, so it will stay the last item
                //
                get {
                    return FixedStringForLastItemValue;
                }
                set {
                    FixedStringForLastItemValue = value;
                }
            }
      public int SortColumn
      {
                //
                // Index of sort column
                //
                set {
                    ColumnToSort = value;
                }
                get {
                    return ColumnToSort;
                }
            }
      public SortOrder Order
      {
                //
                // Sort order (none, ascending or descending)
                //
                set {
                    OrderOfSort = value;
                    VisualizeOrder();
                }
                get {
                    return OrderOfSort;
                }
            }
      public System.IntPtr HeaderHandle
      {
                //
                // The handle of the columns header
                //
                get {
                    return HeaderHandleValue;
                }
            }
      public void GetHeaderHandle()
      {
                //
                // Find out the handle of the columns header
                //
                HeaderHandleValue =
          ChildWindowFromPoint(MyListViewValue.Handle, 5, 5);
            }
      public int Compare(object a, object b)
      {
                //
                // Using a case insensitive comparer to compare the Text of
                // two list view items
                //
                if ( OrderOfSort == SortOrder.None ) {
                    //
                    // return 0 means that both items are equal, so no
                    // change of the sequence will occure
                    //
                    return 0;
                }
                int Result;
                ListViewItem LvIa = (ListViewItem) a;
                ListViewItem LvIb = (ListViewItem) b;
                string  Ca = LvIa.SubItems[ColumnToSort].Text;
                string  Cb = LvIb.SubItems[ColumnToSort].Text;
                //
                // Compare the two strings
                //
                Result = ObjectCompare.Compare(Ca, Cb);
                if ( FixedStringForLastItemValue == null ) {
                    // nothing
                } else {
                    if ( Cb == FixedStringForLastItemValue ) {
                        return -1;
                    }
                }
                //
                // if ( the strings contains numbers, correct the sequence. for example:
                // 3, 20, 1 has to be sorted as 1, 3, 20 and not 1, 20, 3
                //
        Double Da, Db;
        try
        {
          Da = Double.Parse(Ca);
          Db = Double.Parse(Cb);
          if ( Result != 0 )
          {
            if ( Da > Db )
            {
              Result = 1;
            }
            else
            {
              Result = -1;
            }
          }
        }
        catch
        {
          // Ca or Cb are not numeric ==> nothing to do
        }
                //
                // The return value depends on the sort order
                //
        if ( OrderOfSort == SortOrder.Descending)
        {
          // Descending sort is desired, compare result is to be
          // turned in the negative
          Result = -Result;
        }
        return Result;
            }
      public void VisualizeOrder()
      {
                //
                // The sort column and order are visualized by a little triangle,
                // which is to be drawn right to the columns name
                //
                if ( ColumnToSort == -1 ) {
                    return;
                }
                //
                // Draw the triangle in the header. Before that,
                // make theold triangle invisible. After that, store the
                // position of the triangle for usage at the next time.
                //
                if ( SortColumnOld > -1 && SortColumnOld != ColumnToSort ) {
                    //
                    // Setting the text causes a repaint of the header
                    //
                    MyListViewValue.Columns[SortColumnOld].Text =
            MyListViewValue.Columns[SortColumnOld].Text.TrimEnd();
                    //
                }
                if ( OrderOfSort == SortOrder.None ) {
                    SortColumnOld = -1;
                } else {
                    Graphics Grx;
                    Grx = Graphics.FromHwnd(HeaderHandle);
                    Label  LblMeasureHeader = new Label();
                    LblMeasureHeader.Font = MyListViewValue.Font;
                    LblMeasureHeader.AutoSize = true;
                    if ( MyListViewValue.Columns[ColumnToSort].TextAlign !=
            HorizontalAlignment.Left ) {
                        if ( MyListViewValue.Columns[ColumnToSort].Text.TrimEnd() ==
              MyListViewValue.Columns[ColumnToSort].Text ) {
                            MyListViewValue.Columns[ColumnToSort].Text += "     ";
                        }
                    }
                    LblMeasureHeader.Text = MyListViewValue.Columns[ColumnToSort].Text;
                    int x, y, i;
                    Point[] Triangle = new Point[3];
                    //
                    // Evaluate the length of the Name
                    //
                    x = LblMeasureHeader.Width + 13;
                    //
                    // Keep a minimum distance of the right bounce
                    //
                    if ( x + 15 > MyListViewValue.Columns[ColumnToSort].Width ) {
                        x = MyListViewValue.Columns[ColumnToSort].Width - 13;
                    }
                    //
                    // Sum the width of all columns left to the sort column
                    //
                    for (i = 0; (i < ColumnToSort); i++)
          {
                        x += MyListViewValue.Columns[i].Width;
                    }
                    //
                    // Top is determined by the text height
                    //
                    y = LblMeasureHeader.Height / 2 + 2;
                    //
                    // The three points of the triangle are depending on
                    // the sort order
                    //
                    if ( OrderOfSort == SortOrder.Ascending ) {
                        Triangle[0].X = x - 1;
                        Triangle[0].Y = y + 4;
                        Triangle[1].X = x + 9;
                        Triangle[1].Y = y + 4;
                        Triangle[2].X = x + 4;
                        Triangle[2].Y = y - 2;
                    } else {
                        Triangle[0].X = x;
                        Triangle[0].Y = y - 1;
                        Triangle[1].X = x + 9;
                        Triangle[1].Y = y - 1;
                        Triangle[2].X = x + 4;
                        Triangle[2].Y = y + 4;
                    }
                    Grx.FillPolygon(SystemBrushes.ControlDark, Triangle);
                    SortColumnOld = ColumnToSort;
                    Grx.Dispose();
                }
            }
            private int SortColumnOld = -1;
            private System.IntPtr HeaderHandleValue;
        }
#endregion
    }
}

Home   |  Fähigkeiten  |  Projekte  |  Zeitachse   |  Downloads   |  Kontakt