Click here to Skip to main content
Click here to Skip to main content

Include/Exclude List Boxes

, 25 Feb 2000
Rate this:
Please Sign up or sign in to vote.
How to package lots of standard functionality into a CListBox derived class.
  • Download demo project - 124 Kb
  • Download source files - 105 Kb
  • The problem

    Several times now I've found the need for a pair of list boxes that are linked. Items can appear in either of the list boxes, but not in both. Items can be moved between list boxes, and have processes performed on them. Their order within the list boxes can often be shuffled.

    InclExclLB1.gif (4992 bytes)

    The first time I ended up with all of the code to do this spread throughout the message handlers of the enclosing dialog box. Not very neat, but it worked. Then I needed to do something very similar and found myself reinventing the wheel, cutting and pasting from one dialog box to another. I stopped and pulled all of the code out and placed it where it should be, in classes especially designed to solve my problem.

    The result was a set of classes that can be plugged into any dialog box with minimum fuss and do all of the work required for linked list boxes, and much more besides.


    Just a pair of list boxes?

    As I thought about the problem I realised that the include/exclude control was just a special case of a pair of the kind of extended list box that I am always inventing. A list box with buttons closely associated with it. The buttons allow the user to perform processes on the selected items in the list box, or move items into, or out of the box. The buttons are often only enabled when items are selected or when there are a certain number of items or more in the list box. Where only one button is associated with the list box the action that the button performs can often also be achieved by double clicking the item.


    A better list box?

    An extended list box that does just what we want can be created as a subclass of CListBox. We can introduce the concept of a "list box item processor", an abstract class which provides an interface for performing processing on a list box item. A processor can then be associated with the list box so that double click handling can be performed inside the list box rather than outside of it. By associating a list of buttons with the list box, the buttons can have their enabled/disabled state managed by it. When items are added or removed the state of all associated buttons can be automatically adjusted. Where the buttons are required to perform an action on a selected item, when pressed, we can associate a list box item processor with them to allow them to process the item.

    There are some peculiarities with the standard CListBox class that means that it's hard to write code that can work with both a single selection box and a multiple selection box without change. We can wrap up these differences so that we have a consistent interface to either.

    We can use message reflection so that all of the code for our extended list box exists inside our CListBox subclass. That way objects of our subclass can handle their own messages rather than having the containing dialog box handle them for us.

    The end result is a list box that makes it easier to do most of the "standard" things that you might want to do with a list box. All the code is in one place, so it's easy to put it in a library and use it in lots of dialog boxes. Associated buttons are managed automatically by the list box, and as a user you just need to derive a class from the list box item processor to manipulate the items when they are double clicked or when an associated button is pressed.


    The classes involved

    The following class diagram shows the classes involved in building our extended list box:

    InclExclLB2.gif (5075 bytes)

    To take advantage of the extended functionality of CJBListBox simply use in place of a regular CListBox. The extended list box offers the following member functions: (see the source code for more information!)

    • SetDefaultProcessor() - sets up a list processor to handle double clicks.
    • AssociateButton() - associates a button with the list. If a button ID is supplied then the list box will create and manage the button. If a list processor is supplied it will be used to handle clicking on the button otherwise the box's default processor will be used. If a reference to an existing CButton is supplied, rather than a button ID, then the list box will still manage the enabled state of the button but the button can look after its own OnClicked() processing.
    • AddItem() - adds a string and optional data pointer to the list box and updates the state of any associated buttons.
    • InsertItem() - inserts an item at a specific index and updates the state of any associated buttons.
    • RemoveItem() - removes an item and any associated data pointer from the list box and updates the state of any associated buttons.
    • GetSelectionCount() returns the number of items selected whether the box is single or multi selection.
    • GetSelectedItems() - returns an array of indexes to selected items whether the box is single or multi selection.
    • SelectItem() - selects an item and updates any associated buttons.
    • CancelSelection() - cancels either a particular selection, or any current selection, and updates the state of any associated buttons.
    • MoveItemUp() - moves the specified item up one slot in the list box.
    • MoveItemDown() - moves the specified item down one slot in the list box.
    • ProcessSelectedItems() - apply the list box's default processor, or a supplied processor, to all selected items.

    The only other class that a user of the list box must be aware of is CJBListBox::ItemProcessor. This is a very simple abstract base class.

    class CJBListBox::ItemProcessor
    {
       public :
    
          // Destruction...
    
          virtual ~ItemProcessor() { } 
    
          // Process an item
    
          virtual void ProcessSelectedItem(
             const int nIndex,
             const CString &theString,
             void *pData,
             PostProcessAction_e &action) = 0;
    };
    

    ProcessSelectedItem() gets called to process the list box item and gets passed the item's index, string and any data stored with it. The enum returned can be set to indicate what should be done with the item after the function returns (deselection, deletion, etc).

    If multiple items are selected then each will be passed in turn to the list processor. This is possibly a weakness of the design, but I have yet to come across a situation where I needed to know about all of the items in a multi-selection before I could process any of them.


    Known problems with item manipulation

    At present the code doesn't handle a multiple selection move up or down which retains the items as selected after the move. This is because I can't find a way to build an extended selection programatically. Also I don't expect multiple selections where some items are moved and some deleted to work. I haven't tried too hard to fix these as I don't require the functionality - if you do require it, and you do fix up the code to handle it, please let me know!


    Other related classes

    Due to the member functions supplied by our extended list box it's easy use list item processors to rearrange the order of items within the list box, or simply remove items. These kinds of item processors are just "simple item processors" since all they do is return an action to be performed. Since they're handy to have I provided a template simple item processor class. To use just instantiate with the action you wish to perform, e.g.

    CJBListBox::TSimpleItemProcessor<CJBListBox::MoveDown> downProcessor;
    CJBListBox::TSimpleItemProcessor<CJBListBox::Delete> deleteProcessor;
    


    To nest, or not to nest...

    All of the classes involved are contained in a namespace. Initially I thought that was good enough, but I realized that many of the classes were merely implementation details of other classes and weren't for external use. I'd already placed the declaration for the list node class inside the list itself. I followed this practice to its logical conclusion by placing the button inside the list, and the list and item processor inside the CJBListBox class. I've yet to decide if this has added complexity, or whether it was necessary, and I had to jump through a few hoops to keep class wizard happy.

    Nesting the classes has added a tighter level of coupling to the classes. In one way this is good because the classes that are coupled are implementation details of the classes they are coupled to. However, I dislike the way that more source files are dependant on each other.


    Building the include/exclude control

    Once we have built this extended list box solving our original problem is easy. We just take two of our extended list boxes, write list item processors that removes an item from one box and inserts it into the other, wire up a few buttons, and we're done.


    The classes involved

    Once we have the extended list box, the rest is easy...

    InclExclLB3.gif (3024 bytes)

    CJBListBoxPair is a very simple class. It has two public CJBListBox data members and a nested, private, list swap item processor. The control can be simply added to a dialog box and hooked up to the controls by calling one function: Create() which takes the control IDs of the two list boxes and the two buttons which move items from one box to the other. The control can be extended by associating more buttons with the list boxes if required.

    The decision to make the two list boxes public data members was based on the fact that if we didn't make them publicly accessible then forwarding functions for each of the useful member functions would need to be written. These forwarding functions would also need to be able to determine which of the two boxes they should be applied to. This seemed unnecessary.

    The list swap item processor was relatively easy to create. The processor needs to be given a list box to add items to. When its ProcessSelectedItem() function is called it simply adds the item to its list box and returns "Delete" to delete the item from the box it was originally in.

    See the article on Len's homepage for the latest updates.

    License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

    About the Author

    Len Holgate
    Software Developer (Senior) JetByte Limited
    United Kingdom United Kingdom
    Len has been programming for over 30 years, having first started with a Sinclair ZX-80. Now he runs his own consulting company, JetByte Limited and has a technical blog here.
     
    JetByte provides contract programming and consultancy services. We can provide experience in COM, Corba, C++, Windows NT and UNIX. Our speciality is the design and implementation of systems but we are happy to work with you throughout the entire project life-cycle. We are happy to quote for fixed price work, or, if required, can work for an hourly rate.
     
    We are based in London, England, but, thanks to the Internet, we can work 'virtually' anywhere...
     
    Please note that many of the articles here may have updated code available on Len's blog and that the IOCP socket server framework is also available in a licensed, much improved and fully supported version, see here for details.

    Comments and Discussions

     
    AnswerRe: drag-drop ??? PinmemberHQH20-Jun-07 19:18 

    General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

    Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

    | Advertise | Privacy | Mobile
    Web01 | 2.8.140709.1 | Last Updated 26 Feb 2000
    Article Copyright 2000 by Len Holgate
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid