views:

1014

answers:

5

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 { };
}
+3  A: 

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

Kevin Laity
That's dangerous, since that would expose two adds (the second of which experienced devs will default to). Also the polymorphic reference (to the base class) wouldn't include the custom add method. (1) listBox.Add(obj) <-=-> (2) listBox.Items.Add(obj)
Michael Meadows
I've considered (and even prototyped) this idea, but I'm trying to keep within bounds of the existing framework types. The existing legacy code only knows about System.Windows.Forms.ListBox and not about any specific subclass. And the framework completely fails to give hooks to override in this case
Simon Gillbee
Hmm, I guess I'm out of suggestions then :)
Kevin Laity
A: 

Another idea - if it's your ListBox, then don't you know when the Items are changing?

John Saunders
Does it matter who's instance it is? As long as you have a reference you can pretty much do as you will.
leppie
The problem is that I am modifying legacy code which breaks encapsulation rules hands out references to the ListBox willy nilly. So I have a lot of code making direct changes to the list box. Very poor design, but I'm stuck with it :(
Simon Gillbee
+1  A: 

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....

Michael Todd
I forgot about being able to override the WndProc method. That neatly solves my problem. I can create a custom ListBoxEx class, override this virtual method to get the new behavior, but still pass this around as a ListBox. That's exactly what I wanted. Thanks!
Simon Gillbee
A: 

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).

Michael Meadows
A: 

You can use the Layout event.

pedro j.