Jan Schröder
IT-Dienstleistungen

Class LvSort - ListView with a sorter in VB

The Visual Basic 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 (14 KB)

 

Imports System.Collections
Imports System.Windows.Forms
Namespace SI.Controls
    Public Class LvSort
        Inherits System.Windows.Forms.ListView
#Region "Description"
        '
        '########################################################################################
        '
        ' Class     LvSort
        ' Author    Jan Schröder
        '           Schröder Informatik
        '           www.SchroederInformatik.de
        ' Version   1.0.3
        ' Date      05/03/08
        '
        ' 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.vb" 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    MyBase.ListViewItemSorter = Nothing 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#
        '
        '########################################################################################
        '
#End Region
#Region "Constructors"
        Public Sub New()
            MyBase.New()
            LvwColumnSorter = New ListViewColumnSorter(Me)
        End Sub
#End Region
#Region "Data definition"
        Public Property FixedStringForLastItem() As String
            '
            ' Fixed string for the last item, so it will stay the last item
            '
            Get
                Return LvwColumnSorter.FixedStringForLastItem
            End Get
            Set(ByVal Value As String)
                '
                ' The IComparer has to know that
                '
                LvwColumnSorter.FixedStringForLastItem = Value
            End Set
        End Property
        Public Property SortColumn() As Integer
            '
            ' Index of sort column
            '
            Get
                Return LvwColumnSorter.SortColumn
            End Get
            Set(ByVal Value As Integer)
                '
                ' Put it through to IComparer
                '
                LvwColumnSorter.SortColumn = Value
                '
                ' Let's do it
                '
                If LvwColumnSorter.Order = SortOrder.None Then
                    MyBase.ListViewItemSorter = Nothing
                Else
                    If MyBase.ListViewItemSorter Is Nothing Then
                        MyBase.ListViewItemSorter = LvwColumnSorter
                    Else
                        MyBase.Sort()
                    End If
                End If
                '
            End Set
        End Property
        Public Property Order() As SortOrder
            '
            ' Sort order (none, ascending or descending)
            '
            Get
                Return LvwColumnSorter.Order
            End Get
            Set(ByVal Value As SortOrder)
                '
                ' Last information needed for IComparer
                '
                LvwColumnSorter.Order = Value
                If Value = SortOrder.None Then
                    If LvwColumnSorter.SortColumn > -1 Then
                        '
                        ' Setting the text causes a repaint of the header
                        '
                        MyBase.Columns(LvwColumnSorter.SortColumn).Text = _
                            MyBase.Columns(LvwColumnSorter.SortColumn).Text
                        '
                        ' If there has been a triangle from a previous sorting,
                        ' it is now deleted
                        '
                    End If
                    LvwColumnSorter.SortColumn = -1
                End If
                '
                ' Let's do it
                '
                If LvwColumnSorter.SortColumn = -1 Then
                    MyBase.ListViewItemSorter = Nothing
                Else
                    If MyBase.ListViewItemSorter Is Nothing Then
                        MyBase.ListViewItemSorter = LvwColumnSorter
                    Else
                        MyBase.Sort()
                    End If
                End If
                '
            End Set
        End Property
        Private WithEvents LvlListener As LvlListenerClass
        Private WithEvents HdrListener As HdrListenerClass
        Private LvwColumnSorter As ListViewColumnSorter
#End Region
#Region "Event handling"
        Public Event Scroll(ByVal sender As Object, ByVal e As EventArgs)
        Public Shadows Event Paint(ByVal sender As Object, ByVal e As EventArgs)
        Private Class LvlListenerClass
            '
            ' Listen for operating system messages to raise the events
            ' "Scroll" or "Paint" for the ListView
            '
            Inherits NativeWindow
            Public Event Scroll(ByVal sender As Object, ByVal e As EventArgs)
            Public Event Paint(ByVal sender As Object, ByVal e As EventArgs)
            Const WM_HSCROLL = &H114
            Const WM_VSCROLL = &H115
            Const WM_PAINT = &HF
            Private Ctrl As Control
            Public Sub New(ByVal Ctrl As Control)
                AssignHandle(Ctrl.Handle)
            End Sub
            Protected Overrides Sub WndProc(ByRef m As Message)
                MyBase.WndProc(m)
                If m.Msg = WM_HSCROLL Or m.Msg = WM_VSCROLL Then
                    RaiseEvent Scroll(Ctrl, New EventArgs())
                End If
                If m.Msg = WM_PAINT Then
                    RaiseEvent Paint(Ctrl, New EventArgs())
                End If
            End Sub
            Protected Overrides Sub Finalize()
                ReleaseHandle()
                MyBase.Finalize()
            End Sub
        End Class
        Private Class HdrListenerClass
            '
            ' 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.
            '
            Inherits NativeWindow
            Public Event HaederPaint(ByVal sender As Object, ByVal e As EventArgs)
            Const WM_PAINT = &HF
            Private CtrlValue As Control
            Public Sub New(ByVal Ctrl As Control, ByVal HeaderHandle As System.IntPtr)
                AssignHandle(HeaderHandle)
                CtrlValue = Ctrl
            End Sub
            Protected Overrides Sub WndProc(ByRef m As Message)
                MyBase.WndProc(m)
                If m.Msg = WM_PAINT Then
                    RaiseEvent HaederPaint(CtrlValue, New EventArgs())
                End If
            End Sub
            Protected Overrides Sub Finalize()
                ReleaseHandle()
                MyBase.Finalize()
            End Sub
        End Class
        Protected Overrides Sub OnHandleCreated(ByVal e As EventArgs)
            '
            ' Now it's the right time to do some initializations
            '
            MyBase.OnHandleCreated(e)
            If Not Me.DesignMode Then
                LvlListener = New LvlListenerClass(Me)
                LvwColumnSorter.GetHeaderHandle()
                HdrListener = New HdrListenerClass(Me, LvwColumnSorter.HeaderHandle)
            End If
        End Sub
        Private Sub LvlListener_Paint(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles LvlListener.Paint
            '
            ' Make this event public
            '
            RaiseEvent Paint(sender, e)
            '
        End Sub
        Private Sub LvlListener_Scroll(ByVal sender As Object, ByVal e As System.EventArgs) _
                Handles LvlListener.Scroll
            '
            ' Make this event public
            '
            RaiseEvent Scroll(sender, e)
            '
        End Sub
        Private Sub HdrListener_HaederPaint(ByVal sender As Object, _
                ByVal e As System.EventArgs) Handles HdrListener.HaederPaint
            '
            ' The column headers has been painted, so draw the sort triangle
            '
            LvwColumnSorter.VisualizeOrder()
            '
        End Sub
        Private Sub MyBase_ColumnClick(ByVal sender As Object, ByVal e As _
                System.Windows.Forms.ColumnClickEventArgs) Handles MyBase.ColumnClick
            '
            ' The user wants to sort the list view items
            '
            If (e.Column = SortColumn) Then
                '
                ' The column has been clicked twice, so switch the sort order
                '
                If (Order = SortOrder.Ascending) Then
                    Order = SortOrder.Descending
                Else
                    Order = SortOrder.Ascending
                End If
            Else
                '
                ' It has to be sort ascending by the column
                '
                SortColumn = e.Column
                Order = SortOrder.Ascending
            End If
            '
        End Sub
#End Region
#Region "API stuff"
        '
        ' The API function ChildWindowFromPoint is used to find out the window handle of
        ' the columns header.
        '
        Private Structure GdiPoint
            Dim x As Integer
            Dim y As Integer
        End Structure
        Private Declare Function ChildWindowFromPoint Lib "user32" _
            (ByVal hWndParent As System.IntPtr, ByVal Point As GdiPoint) As System.IntPtr
        '
#End Region
#Region "Subroutines and functions"
        Private Class ListViewColumnSorter
            Implements System.Collections.IComparer
            '
            ' This class implements an non case sensitve IComparer for
            ' sorting items in a ListView
            '
            Private ColumnToSort As Integer
            Private OrderOfSort As SortOrder
            Private ObjectCompare As CaseInsensitiveComparer
            Private FixedStringForLastItemValue As String
            Private MyListViewValue As LvSort
            Public Sub New(ByVal MyListView As LvSort)
                ColumnToSort = 0
                OrderOfSort = SortOrder.None
                ObjectCompare = New CaseInsensitiveComparer()
                MyListViewValue = MyListView
            End Sub
            Public Property FixedStringForLastItem() As String
                '
                ' Fixed string for the last item, so it will stay the last item
                '
                Get
                    Return FixedStringForLastItemValue
                End Get
                Set(ByVal Value As String)
                    FixedStringForLastItemValue = Value
                End Set
            End Property
            Public Property SortColumn() As Integer
                '
                ' Index of sort column
                '
                Set(ByVal Value As Integer)
                    ColumnToSort = Value
                End Set
                Get
                    Return ColumnToSort
                End Get
            End Property
            Public Property Order() As SortOrder
                '
                ' Sort order (none, ascending or descending)
                '
                Set(ByVal Value As SortOrder)
                    OrderOfSort = Value
                    VisualizeOrder()
                End Set
                Get
                    Return OrderOfSort
                End Get
            End Property
            Public ReadOnly Property HeaderHandle() As System.IntPtr
                '
                ' The handle of the columns header
                '
                Get
                    Return HeaderHandleValue
                End Get
            End Property
            Public Sub GetHeaderHandle()
                '
                ' Find out the handle of the columns header
                '
                Dim TestPoint As GdiPoint
                TestPoint.x = 5
                TestPoint.y = 5
                HeaderHandleValue = _
                    ChildWindowFromPoint(MyListViewValue.Handle, TestPoint)
            End Sub
            Public Function Compare(ByVal a As Object, ByVal b As Object) As _
                    Integer Implements IComparer.Compare
                '
                ' Using a case insensitive comparer to compare the Text of
                ' two list view items
                '
                If OrderOfSort = SortOrder.None Then
                    '
                    ' Return 0 means that both items are equal, so no
                    ' change of the sequence will occure
                    '
                    Return 0
                    '
                End If
                '
                Dim Result As Integer
                Dim LvIa As ListViewItem = a
                Dim LvIb As ListViewItem = b
                Dim Ca As String = LvIa.SubItems(ColumnToSort).Text
                Dim Cb As String = LvIb.SubItems(ColumnToSort).Text
                '
                ' Compare the two strings
                '
                Result = ObjectCompare.Compare(Ca, Cb)
                If FixedStringForLastItemValue Is Nothing Then
                    ' nothing
                Else
                    If Cb = FixedStringForLastItemValue Then
                        Return -1
                    End If
                End If
                '
                ' 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
                '
                If Result <> 0 And IsNumeric(Ca) And IsNumeric(Cb) Then
                    If (CDbl(Ca) > CDbl(Cb)) Then
                        Result = 1
                    Else
                        Result = -1
                    End If
                End If
                '
                ' The return value depends on the sort order
                '
                Select Case OrderOfSort
                    Case SortOrder.Ascending
                        '
                        ' As ascending sort is desired, return the compare result
                        '
                        Return Result
                        '
                    Case SortOrder.Descending
                        '
                        ' As descending sort is desired, compare result is to be
                        ' turned in the negative
                        '
                        Return -Result
                        '
                End Select
            End Function
            Public Sub 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 Then
                    Exit Sub
                End If
                '
                ' 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 And SortColumnOld <> ColumnToSort Then
                    '
                    ' Setting the text causes a repaint of the header
                    '
                    MyListViewValue.Columns(SortColumnOld).Text = _
                        MyListViewValue.Columns(SortColumnOld).Text.TrimEnd
                    '
                End If
                If OrderOfSort = SortOrder.None Then
                    SortColumnOld = -1
                Else
                    Dim Grx As Graphics
                    Grx = Graphics.FromHwnd(HeaderHandle)
                    Dim LblMeasureHeader As New Label()
                    LblMeasureHeader.Font = MyListViewValue.Font
                    LblMeasureHeader.AutoSize = True
                    If MyListViewValue.Columns(ColumnToSort).TextAlign <> _
                        HorizontalAlignment.Left Then
                        If MyListViewValue.Columns(ColumnToSort).Text.TrimEnd = _
                                MyListViewValue.Columns(ColumnToSort).Text Then
                            MyListViewValue.Columns(ColumnToSort).Text += Space(5)
                        End If
                    End If
                    LblMeasureHeader.Text = MyListViewValue.Columns(ColumnToSort).Text
                    Dim x, y, i As Integer
                    Dim Triangle(2) As Point
                    '
                    ' 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 Then
                        x = MyListViewValue.Columns(ColumnToSort).Width - 13
                    End If
                    '
                    ' Sum the width of all columns left to the sort column
                    '
                    For i = 0 To ColumnToSort - 1
                        x += MyListViewValue.Columns(i).Width
                    Next
                    '
                    ' 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 Then
                        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
                    End If
                    Grx.FillPolygon(SystemBrushes.ControlDark, Triangle)
                    SortColumnOld = ColumnToSort
                    Grx.Dispose()
                End If
            End Sub
            Private SortColumnOld As Integer = -1
            Private HeaderHandleValue As System.IntPtr
        End Class
#End Region
    End Class
End Namespace


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