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
Thanks
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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.ICon tainer
'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.ListB ox
Friend WithEvents ListBox2 As System.Windows.Forms.ListB ox
Friend WithEvents Button1 As System.Windows.Forms.Butto n
<System.Diagnostics.Debugg erStepThro ugh()> Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListB ox
Me.ListBox2 = New System.Windows.Forms.ListB ox
Me.Button1 = New System.Windows.Forms.Butto n
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.ListBox 2)
Me.Controls.Add(Me.ListBox 1)
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.ListB ox1, 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.Handl e)
End If
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Messa ge)
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
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.ICon
'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.ListB
Friend WithEvents ListBox2 As System.Windows.Forms.ListB
Friend WithEvents Button1 As System.Windows.Forms.Butto
<System.Diagnostics.Debugg
Me.ListBox1 = New System.Windows.Forms.ListB
Me.ListBox2 = New System.Windows.Forms.ListB
Me.Button1 = New System.Windows.Forms.Butto
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.ListBox
Me.Controls.Add(Me.ListBox
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.ListB
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
End If
End Sub
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Messa
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.Messa ge)
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
Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Messa
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
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
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
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...
I'll check it out...
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
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. =)
Let me know if the non-express edition has it. =)
No scroll event :(
Bob
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.... ;)
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
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
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... :)
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.ICon
'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.ListB
Friend WithEvents ListBox2 As System.Windows.Forms.ListB
<System.Diagnostics.Debugg
Me.ListBox1 = New System.Windows.Forms.ListB
Me.ListBox2 = New System.Windows.Forms.ListB
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.ListBox
Me.Controls.Add(Me.ListBox
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.ListB
lbss.SyncWithLBSS = New ListBoxSyncScroll(Me.ListB
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.Messa
If Not automating Then
If m.Msg = WM_VSCROLL Then
If Not (syncWith Is Nothing) Then
syncWith.automating = True
SendMessage(syncWith.Handl
syncWith.automating = False
End If
End If
End If
MyBase.WndProc(m)
End Sub
End Class
End Class