How to detect if items are added to a ListBox (or CheckedListBox) control
Asked Answered
D

4

23

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 { };
}
Dairyman answered 17/3, 2009 at 20:44 Comment(2)
@JohanPaul link is brokenTiertza
Uups, thanks. I removed the link, as I don't know where it should be linked today.Franglais
R
7

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

Rutherfurd answered 17/3, 2009 at 20:52 Comment(1)
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!Dairyman
M
3

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

Memberg answered 17/3, 2009 at 20:49 Comment(4)
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)Gervase
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 caseDairyman
Hmm, I guess I'm out of suggestions then :)Memberg
The link is broken now.Grillroom
I
2

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
Ilanailangilang answered 30/1, 2015 at 21:31 Comment(1)
Granted, this will fire every time the Items collection is accessed. In my case, that's ok, but I understand that it might not be ideal.Ilanailangilang
G
0

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

Gervase answered 17/3, 2009 at 20:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.