Link to home
Start Free TrialLog in
Avatar of SETP
SETP

asked on

Scrolling a ListBox

I have two listboxes side by side in a VB.NET 2003 WinForm application. Both have the same number of items. I want both listboxes to behave as though they were only one listbox with two columns. In other words, if I click on one of the items in the one listbox, the corresponding item must get highlighted in the other listbox. And if I scroll one of the listboxes (using the vertical scroll bar), the other listbox must scroll as well. The first part is simple, but I don't know how to do the second part. Any help would be greatly appreciated.

Thanks
ASKER CERTIFIED SOLUTION
Avatar of Mike Tomlinson
Mike Tomlinson
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Here is a complete example of synchronizing the scroll events of two listboxes:

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
    Friend WithEvents ListBox2 As System.Windows.Forms.ListBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.ListBox1 = New System.Windows.Forms.ListBox
        Me.ListBox2 = New System.Windows.Forms.ListBox
        Me.SuspendLayout()
        '
        'ListBox1
        '
        Me.ListBox1.Location = New System.Drawing.Point(16, 8)
        Me.ListBox1.Name = "ListBox1"
        Me.ListBox1.Size = New System.Drawing.Size(120, 121)
        Me.ListBox1.TabIndex = 0
        '
        'ListBox2
        '
        Me.ListBox2.Location = New System.Drawing.Point(152, 8)
        Me.ListBox2.Name = "ListBox2"
        Me.ListBox2.Size = New System.Drawing.Size(120, 121)
        Me.ListBox2.TabIndex = 1
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(288, 142)
        Me.Controls.Add(Me.ListBox2)
        Me.Controls.Add(Me.ListBox1)
        Me.Name = "Form1"
        Me.Text = "Form1"
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private lbss As ListBoxSyncScroll

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim i As Integer
        Dim item As String
        For i = 1 To 25
            item = "Item" & i
            ListBox1.Items.Add(item)
            ListBox2.Items.Add(item)
        Next

        lbss = New ListBoxSyncScroll(Me.ListBox1)
        lbss.SyncWithLBSS = New ListBoxSyncScroll(Me.ListBox2)
    End Sub

    Private Class ListBoxSyncScroll
        Inherits NativeWindow

        Private Declare Auto Function SendMessage Lib "user32" ( _
            ByVal hWnd As IntPtr, _
            ByVal Msg As Integer, _
            ByVal wParam As IntPtr, _
            ByVal lParam As IntPtr) As Integer

        Private Const WM_VSCROLL As Integer = &H115
        Private automating As Boolean = False
        Private syncWith As ListBoxSyncScroll

        Public Property SyncWithLBSS() As ListBoxSyncScroll
            Get
                Return syncWith
            End Get
            Set(ByVal Value As ListBoxSyncScroll)
                If Not (Value Is Nothing) Then
                    syncWith = Value
                    If Not (Value.SyncWithLBSS Is Me) Then
                        Value.SyncWithLBSS = Me
                    End If
                End If
            End Set
        End Property

        Public Sub New(ByVal lb As ListBox)
            Me.AssignHandle(lb.Handle)
        End Sub

        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            If Not automating Then
                If m.Msg = WM_VSCROLL Then
                    If Not (syncWith Is Nothing) Then
                        syncWith.automating = True
                        SendMessage(syncWith.Handle, m.Msg, m.WParam, m.LParam)
                        syncWith.automating = False
                    End If
                End If
            End If

            MyBase.WndProc(m)
        End Sub

    End Class

End Class
SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Lol...once I get down to a low level dealing with WndProc()s and msgs it's hard to pull myself back out.  Thanx for the outside perspective Bob!

That does seem to work nicely and setting the TopIndex() property does NOT produce a corresponding Scroll message in the listbox.  This eliminates the need for the code I put in to prevent the infinite echoing back and forth of scroll messages between the listboxes.

Here is it what it looks like now (keep the clever ideas coming):

Public Class Form1
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
    Friend WithEvents ListBox2 As System.Windows.Forms.ListBox
    Friend WithEvents Button1 As System.Windows.Forms.Button
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.ListBox1 = New System.Windows.Forms.ListBox
        Me.ListBox2 = New System.Windows.Forms.ListBox
        Me.Button1 = New System.Windows.Forms.Button
        Me.SuspendLayout()
        '
        'ListBox1
        '
        Me.ListBox1.Location = New System.Drawing.Point(16, 8)
        Me.ListBox1.Name = "ListBox1"
        Me.ListBox1.Size = New System.Drawing.Size(120, 121)
        Me.ListBox1.TabIndex = 0
        '
        'ListBox2
        '
        Me.ListBox2.Location = New System.Drawing.Point(152, 8)
        Me.ListBox2.Name = "ListBox2"
        Me.ListBox2.Size = New System.Drawing.Size(120, 121)
        Me.ListBox2.TabIndex = 1
        '
        'Button1
        '
        Me.Button1.Location = New System.Drawing.Point(16, 136)
        Me.Button1.Name = "Button1"
        Me.Button1.Size = New System.Drawing.Size(120, 24)
        Me.Button1.TabIndex = 2
        Me.Button1.Text = "Button1"
        '
        'Form1
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(288, 190)
        Me.Controls.Add(Me.Button1)
        Me.Controls.Add(Me.ListBox2)
        Me.Controls.Add(Me.ListBox1)
        Me.Name = "Form1"
        Me.Text = "Form1"
        Me.ResumeLayout(False)

    End Sub

#End Region

    Private lbss As ListBoxSyncScroll

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim i As Integer
        Dim item As String
        For i = 1 To 25
            item = "Item" & i
            ListBox1.Items.Add(item)
            ListBox2.Items.Add(item)
        Next

        lbss = New ListBoxSyncScroll(Me.ListBox1, Me.ListBox2)
    End Sub

    Private Class ListBoxSyncScroll
        Inherits NativeWindow

        Private Const WM_VSCROLL As Integer = &H115
        Private sourceLB As ListBox
        Private targetLB As ListBox
        Private bidirectional As ListBoxSyncScroll

        Private Sub New()
        End Sub

        Public Sub New(ByVal lb1 As ListBox, ByVal lb2 As ListBox)
            sourceLB = lb1
            targetLB = lb2
            If Not (lb1 Is Nothing) Then
                Me.AssignHandle(lb1.Handle)
            End If
            bidirectional = New ListBoxSyncScroll
            bidirectional.sourceLB = lb2
            bidirectional.targetLB = lb1
            If Not (lb2 Is Nothing) Then
                bidirectional.AssignHandle(lb2.Handle)
            End If
        End Sub

        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            If (m.Msg = WM_VSCROLL) AndAlso (Not (targetLB Is Nothing)) Then
                targetLB.TopIndex = sourceLB.TopIndex
            End If

            MyBase.WndProc(m)
        End Sub

    End Class

End Class
One final (hopefully) revision.  The WndProc sub should be:

        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            MyBase.WndProc(m)

            If (m.Msg = WM_VSCROLL) AndAlso (Not (targetLB Is Nothing)) Then
                targetLB.TopIndex = sourceLB.TopIndex
            End If
        End Sub

This keeps them synced properly while you hold down the scrollbar and drag continuosly.  With MyBase.WndProc(m) at the bottom, the second listbox is off by one and doesn't get completely synced until you actually let go of the mouse.

~IM
Cool b-) *GRIN*

Bob
SETP,

This has been a great example of the "collaborative" efforts of experts resulting in a better, cleaner solution.

If you use the code I posted, then please Split the points and give Bob some credit for his valuable input.

=)

Mike
...and if anyone at Microsoft reads this post...why doesn't the .Net Listbox have a built-in Scroll event?  The lowly VB6 ListBox had one...

???
Mike,
Do you have 2005 Beta?  I have installed it, but I haven't looked that deeply at it.  I would be interested to see what they did with the ListBox.

Bob
Not yet no...but I have thought about grabbing it numerous times.

I'll check it out...
Avatar of SETP
SETP

ASKER

Thanks guys so much for your exellent response. I'm also thinking about installing the 2005 Beta 2. Microsoft posted me the CD's - I just have to install it. I'll let you guys know if they included an event for the Scoll event on the ListBox when I test it. If they haven't, it would defenitely be worth while letting Microsoft know.

Kind Regards
Fabricio
South Africa
I just tried "Visual Basic 2005 Express Edition Beta 2" and there is no Scroll event for the ListBox.

Let me know if the non-express edition has it.  =)
No scroll event :(

Bob
Thanx for the update Bob.  Seems we are in the minority and part of only a small group of people interested in a scroll event for the listbox.  =O

You'd think since VB6 had that event there would be a large number of people looking for that missing functionality.  Wonder why it isn't included...

Of course, one could say, "Look how easy it was for you to write your own wrapper for that message with VB.Net.  Do we have to do everything for you?"

Lol....    ;)
Like any development, decisions were made, and I believe that WinForms is not a big priority at Micro$oft.  I don't believe that they devote a significant amount of time to thinking over these seemingly insignificant problems in relation to all the other problems that they do have.  

I guess it takes enough people to make a big noise, like edit-and-continue, serial port interface, FTP, etc., for Micro$oft to actually listen.  I read something about them trying to win back what they refer to as "garage" developers.

Bob
Avatar of SETP

ASKER

Garage developers? Mmm.... Well, I'm glad they at least brought back Edit-And-Continue, and serial port interface (I use that a lot). Maybe in VB.NET 2007 we'll get the scroll event... :)