This seems like a fundamentally simple question. I have a WinForms dialog box with a listbox. This control is not populated via data-binding but is filled with calls to
listBox.Items.Add (obj);
It is possible that this call can be made asynchronously from various places and I would like to hook the listbox and watch for changes in its data members so that I can perform other UI changes (such as enable or disable controls which interact with the listbox based on the number of items in the list).
Unfortunately, unless I'm being completely clueless, there does not seem to be an event or virtual method which can be hooked to detect this. I can hook for select changes and (for CheckedListBox) I can hook for check-state changes. But not for changes to the underlying data collection.
I know this is possible in Win32 (there is a window message for this). What am I missing?
[Edited by Simon]
Solution
I was pointed to the correct solution (which I have marked as the accepted answer) which is to override the WndProc method of the ListBox and handle the listbox messages manually. Here is the solution that I settled on (and works). It could be modified to provide more details in the event, or split the messages into separate events, but for my needs this is sufficient.
using System;
using System.Windows.Forms;
public class CheckedListBoxEx : CheckedListBox
{
public CheckedListBoxEx() { }
private const int LB_ADDSTRING = 0x180;
private const int LB_INSERTSTRING = 0x181;
private const int LB_DELETESTRING = 0x182;
private const int LB_RESETCONTENT = 0x184;
protected override void WndProc(ref Message m)
{
if (m.Msg == LB_ADDSTRING ||
m.Msg == LB_INSERTSTRING ||
m.Msg == LB_DELETESTRING ||
m.Msg == LB_RESETCONTENT)
{
ItemsChanged(this, EventArgs.Empty);
}
base.WndProc(ref m);
}
public event EventHandler ItemsChanged = delegate { };
}
I don't know of any event that you can watch to show that an item has been added to a ListBox. Perhaps you can use the Win32 method you described instead (i.e. grab a handle, use WndProc, etc.).
Alternately, perhaps you can use another class that adds items instead. For example, rather than calling the Add method on the ListBox directly, you could have user-actions call the Add method inside the new class which then adds the item to the ListBox. You could set an event inside that class that would allow you to watch what's been added.
I also like the idea of subclassing the ListBox as mentioned by another poster....
Here's a post on another forum that recommends creating a child class that includes that behaviour.
http://www.eggheadcafe.com/forumarchives/netframeworkcompactframework/jul2005/post23265940.asp
This solution seems to work - in my situation, I implemented it in VB. I just created an ExtendedListbox class which inherits the listbox control, implements INotifyPropertyChanged and shadows the Items Property.
Imports System.ComponentModel
Public Class ExtendedListBox:Inherits ListBox:Implements INotifyPropertyChanged
Public Shadows Property Items() As ObjectCollection
Get
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Items"))
Return MyBase.Items
End Get
Set(value As ObjectCollection)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("Items"))
MyBase.Items.Clear()
For Each o As Object In value
MyBase.Items.Add(o)
Next
End Set
End Property
Public Event PropertyChanged(sender As Object, e As PropertyChangedEventArgs) Implements INotifyPropertyChanged.PropertyChanged
End Class
Unfortunately, there's no easy way to do this using inheritance or events. You should be able to override the Add method of the Items class, but you can't get to it! You may be able to intercept the message loop to figure out when this is happening, but that's beyond my experience.
One thing I noticed from your question is that you mention items are being added asynchronously. Don't do that. Your problem may be solved if you synchronize on the form's thread (if your problem is that the control isn't updating).