Question Capture Listview Horizontal scroll event

Bill7002

Member
Joined
Dec 22, 2008
Messages
5
Programming Experience
3-5
I need to know how to capture the horizontal scroll event of a listview. The project that I am working on has a listview and 6 textboxes that overlay row 0 of the listview. I'm using them to filter the contents of the columns. I have the Listview1_Resize and ListView1_ColumnWidthChanging events working fine for positioning the textboxes under the columnheaders. If the column widths are expanded to a point where the horizontal scroll bar is fired, my columns will not line up with the textboxes when the scroll bar is moved.

My forehead is getting bruised trying to figure this out. Any help is greatly appreciated.

Regards
Bill
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,464
Location
Norway
Programming Experience
10+
I found no better yet (*) than using the DrawColumnHeader event to capture X location of first item, column headers are redrawn when listview is scrolled horizontally also. This requires OwnerDraw set to True for the ListView, but you don't actually need to draw anything thanks to e.DrawDefault.

I didn't fancy covering up any item row, so when I tested this I just added a Panel above the Listview to hold the filter boxes. Using a Panel has the benefit of allowing some boxes to be positioned outside the visible area, so they will scroll horizontally in and out of view like the listview items. I set the height to 20 and same width as the Listview.

These are the events handled:
VB.NET:
Private Sub ListView1_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.ColumnWidthChangedEventArgs) Handles ListView1.ColumnWidthChanged
    AlignBoxes(True)
End Sub

Private Sub ListView1_DrawColumnHeader(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
    e.DrawDefault = True    
    AlignBoxes()
End Sub

Private Sub ListView1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewItemEventArgs) Handles ListView1.DrawItem
    e.DrawDefault = True
End Sub
AlignBoxes method is used here to both add some textboxes if not already, and align them when columns width changes and when column headers are drawn. Using the currentOffset variable helps prevent a lot of unnecessary processing when header redraws for other reasons.
VB.NET:
Private initialized As Boolean = False
Private currentOffset As Integer

Private Sub AlignBoxes(Optional ByVal force As Boolean = False)     
    If Not initialized Then
        For i As Integer = 0 To Me.ListView1.Columns.Count - 1
            Dim box As New TextBox
            box.Name = "box" & i.ToString
            Me.SearchPanel.Controls.Add(box)
        Next
        initialized = True
    End If

    Dim start As Integer = Me.ListView1.Items(0).Position.X
    If force OrElse start <> Me.currentOffset Then
        Me.currentOffset = start
        For i As Integer = 0 To Me.ListView1.Columns.Count - 1
            Dim header As ColumnHeader = Me.ListView1.Columns(i)
            Dim box As Control = Me.SearchPanel.Controls("box" & i.ToString)
            box.SetBounds(start, 0, header.Width, 20)
            start += header.Width
        Next
    End If
End Sub
(*) There is also the windows message WM_HSCROLL that can be used, if you subclass the Listview or use a NativeWindow to listen, both both these methods means more work for no gain here.
 

Bill7002

Member
Joined
Dec 22, 2008
Messages
5
Programming Experience
3-5
Horizontal scroll not captured through Column Draw

Thank you for the reply John. I don't know if the problem is VB2008 or 3.5. I tried your sample and couldn't get a response from either sub below. I placed msgboxes in both subs while moving the scroll bars and didn't get a response. The draw event must happen at some other time.

Private Sub ListView1_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.ColumnWidthChangedEventArgs)

Private Sub ListView1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewItemEventArgs

I'm not sure of how to use the WM_HSCROLL. I kind of thought this was for adding a horizontal scroll to a control that doesn't have one. Possibly some windows messaging type is the answer. I'm not sure. If you have the working sample, possibly I could get it from you. I might have missed something.

Thank You :(
Bill
 

Bill7002

Member
Joined
Dec 22, 2008
Messages
5
Programming Experience
3-5
Second Look

Hope you had a Merry Christmas John. I'm sorry that my last post was incomplete. Yes, I am getting the ListView1_ColumnWidthChanged Event. Just not the other 2. See Code below. I put in control breaks to test the drawitem and DrawColumnHeader. The logic never goes to these subs.

VB.NET:
     Private Sub ListView1_ColumnWidthChanged(ByVal sender As Object, ByVal e As System.Windows.Forms.ColumnWidthChangedEventArgs) Handles ListView1.ColumnWidthChanged
  
      AlignBoxes(True)

    End Sub

    Private Sub ListView1_DrawColumnHeader(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewColumnHeaderEventArgs) Handles ListView1.DrawColumnHeader
        e.DrawDefault = True
        AlignBoxes()
    End Sub

    Private Sub ListView1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawListViewItemEventArgs) Handles ListView1.DrawItem
        e.DrawDefault = True
    End Sub

    Private Sub AlignBoxes(Optional ByVal force As Boolean = False)
        If force Then
            SP1.Width = iSPW - 1
            SP2.Width = iSPW - 1
            SP3.Width = iSPW - 1
            SP4.Width = iSPW - 1
            SP5.Width = iSPW - 1
            SP6.Width = iSPW - 1

            LVH1.Width = ListView1.Columns.Item(0).Width - (iSPW + 1)
            LVH2.Width = ListView1.Columns.Item(1).Width - (iSPW + 0)
            LVH3.Width = ListView1.Columns.Item(2).Width - (iSPW + 0)
            LVH4.Width = ListView1.Columns.Item(3).Width - (iSPW + 0)
            LVH5.Width = ListView1.Columns.Item(4).Width - (iSPW + 0)

            LVH1.Left = SearchPanel.Left - 4
            LVH2.Left = LVH1.Left + LVH1.Width + iSPW
            LVH3.Left = LVH2.Left + LVH2.Width + iSPW
            LVH4.Left = LVH3.Left + LVH3.Width + iSPW
            LVH5.Left = LVH4.Left + LVH4.Width + iSPW
            LVH6.Left = LVH5.Left + LVH5.Width + iSPW
            LVH6.Width = ListView1.Width - (LVH5.Left + LVH5.Width + iSPW + 2)

            SP1.Left = LVH1.Left + LVH1.Width + 1
            SP2.Left = LVH2.Left + LVH2.Width + 1
            SP3.Left = LVH3.Left + LVH3.Width + 1
            SP4.Left = LVH4.Left + LVH4.Width + 1
            SP5.Left = LVH5.Left + LVH5.Width + 1
            SP6.Left = LVH6.Left + LVH6.Width + 1
        End If
    End Sub
 

Bill7002

Member
Joined
Dec 22, 2008
Messages
5
Programming Experience
3-5
John, I had Ownerdraw on. The problem was with the 2 subs. I rem'ed them out and recreated them. If works fine now.

Thank You
 

Bill7002

Member
Joined
Dec 22, 2008
Messages
5
Programming Experience
3-5
John, when I moved this from the test project to the actual project I'm working on, I get an error on the statement below.
Dim start As Integer = Me.ListView1.Items(0).Position.X

This is probably due to changing my listview to virtual. I needed to decrease the amount of time to display the large result set. Is this something that will make this unuseable.

Bill
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,464
Location
Norway
Programming Experience
10+
Bill7002 said:
I had Ownerdraw on. The problem was with the 2 subs. I rem'ed them out and recreated them. If works fine now.
So you didn't have the Handles right as I asked you before.
Bill7002 said:
changing my listview to virtual
help said:
In virtual mode, the Items collection is disabled, and if you attempt to access it will result in an InvalidOperationException.
Bill7002 said:
Is this something that will make this unuseable.
Yes and no. You can't use the Items collection, but you can cache any item requested by RetrieveVirtualItem and use this as item reference instead of Items(0). Since drawing happens also before any item is requested you just have to make sure your item reference isn't Nothing before trying to use it. Something along these lines:
VB.NET:
Private itemref As ListViewItem

Private Sub ListView987654321_RetrieveVirtualItem(ByVal sender As Object, ByVal e As System.Windows.Forms.RetrieveVirtualItemEventArgs) Handles ListView123456789.RetrieveVirtualItem
    e.Item = New ListViewItem(...)
    Me.itemref = e.Item
End Sub

Private Sub AlignBoxes(...)
    If Me.itemref IsNot Nothing Then
         'got Me.itemref.Position.X
    End If
End Sub
 
Top Bottom