Copying ListBox Contents to the Clipboard

Example Debugging Techniques

Home
Back To Tips Page

One of the arguments about not using a ListBox as a logging control is "the contents can't be selected and copied to the clipboard".  This is simply untrue.  It isn't all that hard to write a copy-to-clipboard routine.  This article shows how to do this, including all the necessary support to make it work well.

Creating a subclass

First, to make this work well, the ListBox must have the Extended option for Selection, and because its purpose is to store output in chronological order, the Sort property must be set to FALSE:

The subroutine for copy-to-clipboard can be triggered by a Ctrl+C shortcut key or by a popup menu item.  To do this, I prefer to handle this within the ListBox control itself. 

So first, you use the ClassWizard to create a new subclass, which I'll call CListBoxEx for this project.

When the Add Class dialog comes up, select the type of class to be an MFC class:

Then type in the name of your class and select its base class, which in this case is CListBox

Coupling the control to the class

You can use the ClassWizard to add a new control variable.  Select the control, right-click, and select Add Variable...

This brings up the Add Variable dialog

You can bind this to CListBoxEx simply by typing in the class name.  You should also change the Access to be protected.  For reasons that defy analysis, because they make no sense, the default is to declare control variables as public, which is completely pointless because there is never a reason to access these variables from outside the class.  The Variable Name is the name of the control variable in the program; by convention, I always start these as c_.  The other fields have been filled in because of the control that you right-clicked on.

If you had already created a CListBox variable, you just manually edit it to be a CListBoxEx variable. 

Don't forget to make sure that the ListBoxEx.h header file is included in the header file for the dialog, property page, or form view in which the control variable is declared.

Implementing a popup menu

We are going to create a popup menu.  For the features this article describes, we only need two: Select All and Copy.  Your application may wish to add other menu items to handle application-specific features.

When the Add Resource dialog comes up, select Menu, then click OK.

While it is possible to create popup menus "on the fly", I find it most effective to pre-create them in the Resource Editor.  If you have a situation where you don't want to have all the menu items present, create a popup menu with all the possible items and simply delete, on-the-fly, the ones you don't need.  This makes localization a lot easier.  If you must add menu items dynamically, make sure that there are no hardwired constants in your native language; use the STRINGTABLE resource.  Then click into the fields of the menu and add new fields.  The first field you need to set is the top-level menu name.

When I create a menu for popups, I always change the "menu bar" name to be informative; in this case I called it Popup Menu For ListBox.  This name serves absolutely no useful purpose as far as your program is concerned; it serves a very important purpose as far as you are concerned: it provides documentation of what this menu is for.

We would also like to change the ID of the menu.  Go to the Resource View, right click on the menu (probably called something useless like IDR_MENU1), select Properties, and change the ID of the menu item.

My naming convention for popup menus is IDR_something_POPUP.  The implementation of this will be handled in the WM_CONTEXTMENU handler.

Handling Messages

In the Messages handler, add handlers for WM_CHAR and WM_CONTEXTMENU

The = handlers are "reflected handlers".  In the old Windows 1.0 model, these messages were sent to the parent of the control.  Unfortunately, this meant that the parent had to know the details of the implementation of a control, which violates all kinds of concepts of modularity.  In MFC, this can be avoided by the "reflection" mechanism by which a message is sent back to the child.  It is sent as a special message, computed by taking the incoming message code and adding the magical value WM_REFLECT_BASE (0xBC00) to it.  A WM_whatever message reflected to a control will be handled by an ON_WM_whatever_REFLECT entry in the Message Map.

Adding WM_COPY

In addition, we want to handle a WM_COPY message.  However, this is not one of the messages that appears in the list of possible messages.  Therefore, we have to hand-edit the Message Map and write our own handler.  To do this, we need to understand the message we are about to handle.  According to the Microsoft documentation,


WM_COPY Message

An application sends the WM_COPY message to an edit control or combo box to copy the current selection to the clipboard in CF_TEXT format.

Syntax

To send this message, call the SendMessage function as follows.

lResult = SendMessage(    // returns LRESULT in lResult 
   (HWND) hWndControl,    // handle to destination control 
   (UINT) WM_COPY,        // message ID 
   (WPARAM) wParam,       // = (WPARAM) () wParam;
   (LPARAM) lParam        // = (LPARAM) () lParam;
); 

Parameters

wParam
Not used; must be zero.
lParam
Not used; must be zero.

Return Value

This message does not return a value.


Now, you may wonder about how we handle Unicode text.  The answer here is that we will save it as Unicode text, because, if you read the documentation, you will discover that CF_TEXT is called a "synthesized format" as well.  But for now, we only have to create the handler.

Microsoft has made a really fundamental design error in the ClassWizard: they declare all the message handlers as public methods.  This makes no sense whatsoever, and consequently you should simply remove all these public declarations and make the message handlers protected.  Then add your own handler.  Note that you must make the handler conform to the general message handler prototype

LRESULT handlername(WPARAM, LPARAM);

No other prototype is permissible.  You must not use types other than WPARAM and LPARAM for the parametersYou must not use any type other than LRESULT for the result type.  Even if you don't use the parameters, you must declare them, otherwise the program will not compile.

So given that the ClassWizard has constructed

protected:
	DECLARE_MESSAGE_MAP()
public:
    afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
};

You will want to change it to read

protected:
	DECLARE_MESSAGE_MAP()
    afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);
    afx_msg void OnContextMenu(CWnd* /*pWnd*/, CPoint /*point*/);
    afx_msg LRESULT OnCopy(WPARAM, LPARAM);
};

Note the removal of the erroneous public declaration.  There is absolutely no reason these methods would need to be public, and as good practice, the number of public variables and methods of a class should be as small as possible.

Go into the ListBoxEx.cpp file and create the handler.

One of the first things you should do is delete the inclusion of the "project.h" file.  It has no place in any module of your application except the CWinApp-derived class and the CMainFrame class.  No other module in your system should know about the existence of the CWinApp class.  (This is a heuristic; in the very rare and exotic situations in which you actually do need access to your derived CWinApp class, you will know.  You will typically encounter one or two situations per year in which you are doing sufficiently weird and advanced code that would necessitate this).

Your choices here are to either remove the #include directive entirely, or replace it with #include "resource.h".  Since we will need to pop up a menu, we will need to include resource.h.  So the #include directives should be

#include "stdafx.h"
#include "resource.h"
#include "ListBoxEx.h"

Then you have to add an entry to the Message Table to invoke the method

ON_MESSAGE(WM_COPY, OnCopy)

and finally, you implement the actual handler code.

/****************************************************************************
*                             CListBoxEx::OnCopy
* Inputs:
*       WPARAM: ignored, 0
*       LPARAM: ignored, 0
* Result: LRESULT
*       Logically void, 0, always
* Effect: 
*       Invokes the copy operation
****************************************************************************/

LRESULT CListBoxEx::OnCopy(WPARAM, LPARAM)
    {
     DoCopy();
     return 0;
    } // CListBoxEx::OnCopy

We will code the DoCopy method shortly.

Note, however, the documentation of this function.  Every function should have such a header.  In the case of a message that returns "no value", you still have to return one, and 0 is as good a value as any to return.

Implementing the shortcuts: WM_CHAR

Note that in the menu item, I wrote the menu text as

&Select All\tCtrl+A
&Copy\tCtrl+C

This does hardwire the shortcuts; it is a long and more involved discussion to explain how to get the user's current shortcuts from the accelerator table or by some other means, and that is not going to be part of this discussion.

Ctrl+A and Ctrl+C are both characters, which is why we added the WM_CHAR handler.  These character codes are specifically 0x0001 for Ctrl+A and 0x0003 for Ctrl+C.

Here's the code for the OnChar handler:

#define SHORTCUT_SELECT_ALL 0x0001
#define SHORTCUT_COPY       0x0003

void CListBoxEx::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
    {
     switch(nChar)
        { /* nChar */
         case SHORTCUT_SELECT_ALL:
            DoSelectAll();
            return;
         case SHORTCUT_COPY:
            DoCopy();
            return;
        } /* nChar */
    CListBox::OnChar(nChar, nRepCnt, nFlags);
    }

Implementing the WM_CONTEXTMENU handler

This is quite simple for a popup menu.  However, it ignores the issues of enabling, disabling, or otherwise manipulating the menu contents, but we will show that later.

void CListBoxEx::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
    {
     CMenu menu;
     menu.LoadMenu(IDR_LISTBOX_POPUP);
     CMenu * popup = menu.GetSubMenu(0);

     popup->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
    }

All this does is load the menu, extract the first popup item, and call TrackPopupMenu.  However, note that we use this as the target of the menu messages (the last parameter of TrackPopupMenu) so the menu item will be directed to the handler we've added.  Message routing does not work at this level.

You may be tempted to put several popup menus in a single "generic" top-level menu resource.  Avoid doing this.  It serves no useful purpose, and merely results in fragile and unmaintainable code.  Each popup menu should be in its own private menu resource.  This makes it easier to maintain the menus.

Handling Menu Items

We need to implement handlers for our popup menu.  To do this, we first need to create them.  To do this, right-click on each menu item and select the Add Handler... option.  Due to terminal brain damage on the part of the VS designer, this, for reasons that apparently only make sense to someone living in a dimension not our own, puts you into the editor; there is no option to just add the skeleton of a handler and go back and immediately add another, so you can add all your handlers at once.  So it is more than a bit of a pain to use effectively.

Once you select this, you will have to explicitly select the target class from the Class list.  The default will most likely be the first class, CAboutDlg in this case, which would be wrong.  It would not be "natural" for a control subclass to have a command handler, which is why you must select it yourself.

The bodies of these menu handlers are quite simple

void CListBoxEx::OnPopupmenuforlistboxSelectall()
    {
     DoSelectAll();
    }

void CListBoxEx::OnPopupmenuforlistboxCopy()
    {
     DoCopy();
    }

Selecting All

This is remarkably simple

void CListBoxEx::DoSelectAll()
    {
     CListBox::SelItemRange(TRUE, CListBox::GetCount());
    } // CListBoxEx::DoSelectAll

Copying to clipboard

Most of the effort in this code is to check for failures of the various API calls and respond.  I simplified this by merely returning without doing anything, except in the Debug version I will have an ASSERT(FALSE) to let me get at the debugger.  Here's the copy-to-clipboard code

void CListBoxEx::DoCopy()
    {
     CArray<int,int> sels;
     int n = CListBox::GetSelCount();
     if(n <= 0)
        return; // nothing to copy

     sels.SetSize(n);
     CListBox::GetSelItems(n, sels.GetData());

     CString s;
     //*****************************************************************************
     // This segment of code only works if the listbox is non-owner-draw,          *
     // or is owner-draw with LBS_HASSTRINGS                                       *
     // So first check to make sure this is true                                   *
     //*****************************************************************************
     ASSERT( (GetStyle() & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE)) == 0 || //*
             (GetStyle() & LBS_HASSTRINGS) == LBS_HASSTRINGS);                   //*
                                                                                 //*
     // Extract the text                                                         //*
     for(int i = 0; i < n; i++)                                                  //*
        { /* copy items */                                                       //*
         CString t;                                                              //*
         CListBox::GetText(sels[i], t);                                          //*
         t += _T("\r\n");                                                        //*
         s += t;                                                                 //*
        } /* copy items */                                                       //*
     //*****************************************************************************

     HGLOBAL g = ::GlobalAlloc(GMEM_MOVEABLE, (s.GetLength() + 1) * sizeof(TCHAR));
     if(g == NULL)
        { /* alloc failed */
         ASSERT(FALSE);  // failed to allocate memory
         return;
        } /* alloc failed */

     LPTSTR p = (LPTSTR)::GlobalLock(g);
     if(p == NULL)
        { /* lock failed */
         ASSERT(FALSE);
         return;
        } /* lock failed */

     StringCchCopy(p, s.GetLength() + 1, (LPCTSTR)s);

     ::GlobalUnlock(g);

     if(!OpenClipboard())
        { /* clipboard open failed */
         ASSERT(FALSE);
         GlobalFree(g);
         return;
        } /* clipboard open failed */

     if(!EmptyClipboard())
        { /* empty failed */
         ASSERT(FALSE);
         GlobalFree(g);
         return;
        } /* empty failed */

#ifdef _UNICODE
#define CF_ CF_UNICODETEXT 
#else
#define CF_ CF_TEXT
#endif
     if(::SetClipboardData(CF_, g) == NULL)
        { /* SetClipboardData failed */
         ASSERT(FALSE); //
         ::CloseClipboard();
         ::GlobalFree(g);
         return;
        } /* SetClipboardData failed */
     ::CloseClipboard();
    } // CListBoxEx::DoCopy

This code has a segment that only works for non-owner-draw ListBox controls, or owner-draw ListBox controls that have the LBS_HASSTRINGS style.  This is because the GetLBText method is only defined for controls meeting these requirements.  When I do this in my Logging ListBox Control, I have a common superclass for the ItemData objects which has a virtual method that returns a single string (in my case, I'm saving the strings to a file), and I would call that to convert the ItemData value to a string.  So to handle this, I rewrote that segment to use a virtual method, which I've called GetString, and I rewrote the loop to be

     CString s;

     // Extract the text
     for(int i = 0; i < n; i++)
        { /* copy items */
         CString t;       
         t = GetString(i);
         t += _T("\r\n");
         s += t;
        } /* copy items */

This code will now work for any type of ListBox. To use an owner-draw ListBox, for example, all you would need to do is derive a new class from CListBoxEx and implement a new GetString method suitable for your needs in it.

The GetString method of the default superclass is now

/* virtual */ CString CListBoxEx::GetString(int i)
    {
     //*****************************************************************************
     // This segment of code only works if the listbox is non-owner-draw,          *
     // or is owner-draw with LBS_HASSTRINGS                                       *
     // So first check to make sure this is true                                   *
     //*****************************************************************************
     ASSERT( (GetStyle() & (LBS_OWNERDRAWFIXED | LBS_OWNERDRAWVARIABLE)) == 0 ||
             (GetStyle() & LBS_HASSTRINGS) == LBS_HASSTRINGS);

     // Extract the text
     CString t;
     CListBox::GetText(i, t);
     return t;
    } // CListBoxEx::GetString

Avoiding Memory Allocation

The number of elements which are selected cannot be known in advance.  Not until the GetSelCount is issued is it known how many elements have been selected.  Then GetSelItems will fill in an array of int values which represent the indices of the selected elements.  So obviously this needs to be allocating dynamically.  A programmer might be tempted to do something quite horrific,such as

LPINT sels = (LPINT)malloc(n * sizeof(int));

which would be particularly bad in a C++ program.  And it also requires that every return after that makes sure that a

free(sels);

is performed or there will be a storage leak.

A C++ programmer knows that malloc is bad, and might be tempted to write

LPINT sels = new int[n];

which really isn't a whole lot better.  It still requires that every path that exits the function execute

delete [] sels;

so we have merely replaced one bad solution with another bad solution that has nice syntactic sugar. 

The clean way to handle this is to use either std::vector or CArray.  I prefer to use the MFC collections, so I write

CArray<int, int> sels;
sels.SetSize(n);
CListBox::GetSelItems(n, sels.GetData());

The GetData method on an array of elements of type T will return a T * pointer, so what I get from sels.GetData() is an LPINT.  But because this is a local object, its destructor will automatically free up the allocated storage when this variable leaves scope, thus it doesn't matter how we leave, we're guaranteed the storage will be cleaned up.

Actually, we don't avoid memory allocation, we avoid doing explicit memory allocation, which requires explicit memory deallocation.  So we're really avoiding memory allocation problems.

The Clipboard Formats

You would expect that you should store the text in CF_TEXT format, which is ANSI text.  In fact, that is what the documentation of WM_COPY suggests is going to happen.  However, if you are a Unicode application, you don't really want to have to convert the text to 8-bit code, particularly because this can result in information loss.  So the default clipboard format you use should depend upon the nature of the application.  For a Unicode app, you should use the format CF_UNICODETEXT.  If you read about the topic "Synthesized Clipboard Formats", you will discover that if someone does a GetClipboardData using style CF_TEXT, any clipboard data in CF_UNICODETEXT will be converted to CF_TEXT by the clipboard mechanism.  Therefore, I wrote the conditional that defined the CF_ symbol to be either CF_TEXT or CF_UNICODETEXT, based on the mode of compilation.

Enabling Menu Items

Note that there are a couple problems with this control, with the popup menu.  Even if nothing is selected, you still have the Copy item enabled:

If the list is empty, the Select All is still enabled.

Now, you might think that you could ON_UPDATE_COMMAND_UI handlers for these menu items.  Unfortunately, this won't work.  The logic that routes messages to the ON_UPDATE_COMMAND_UI handlers is part of the MFC Mainframe-based logic, and is not available when the messages are being routed to the control itself.  Therefore, you have to add logic to the OnContextMenu handler.  So this requires explicit menu manipulation.

In situations where I need menus that provide several options, not all of which should appear, I simply drop in all the options at design time, and use DeleteMenu to delete the ones I don't need.

void CListBoxEx::OnContextMenu(CWnd* /*pWnd*/, CPoint point)
    {
     CMenu menu;
     menu.LoadMenu(IDR_LISTBOX_POPUP);
     CMenu * popup = menu.GetSubMenu(0);

     int itemcount = CListBox::GetCount();
     int selcount = CListBox::GetSelCount();
     popup->EnableMenuItem(ID_POPUPMENUFORLISTBOX_SELECTALL,
                           MF_BYCOMMAND | ((itemcount > 0 && selcount != itemcount) ? MF_ENABLED : MF_GRAYED));

     popup->EnableMenuItem(ID_POPUPMENUFORLISTBOX_COPY,
                           MF_BYCOMMAND | (selcount > 0 ? MF_ENABLED : MF_GRAYED));

     popup->TrackPopupMenu(TPM_LEFTALIGN, point.x, point.y, this);
    }

The result of the sequence of enables is to produce the following situations.  From left to right, an empty ListBox, one with no selections, one with some, but not all, items selected, and one with all items selected.

You may notice that the Clear button is enabled even if the ListBox is empty.  This problem is addressed in a companion essay on Resizing, which shares the same code sample.

Cut and Paste

It should be obvious now that you can also implement Cut functionality and Paste functionality.  However there are a couple tricks and design issues that you must address.

In the case of Cut, you must not actually cut the elements from the list if you could not store them in the clipboard.  Therefore, the DoCopy function would have to be a BOOL-returning function, rather than the void it currently is defined as.  All the error situations would return FALSE, and successful completion would return TRUE.  This takes care of knowing the data is "safe in the clipboard".

To delete elements, after you have done the GetSelItems, you should delete them from highest-numbered to lowest-numbered.  The loop would look something like

for(int i = sels.GetSize() - 1; i >= 0; i--)
   { /* delete each */
    DeleteString(i);
   } /* delete each */

 This is because, if you delete from lowest to highest, after you delete the first element, all the other elements shift downward, and the selection numbers now reference different elements than those that had been selected.

Paste requires a lot of design decisions.  For example, once you read the lines from the clipboard, exactly which lines do you replace in the ListBox if there are non-contiguous selections?  Or does "Paste" always mean "Append"?  Or is it only valid if there is a contiguous selection, which it will replace?  What happens if there is no selection?  Do you append, or insert at the caret position?  All of these would be dictated by the needs of your application and the presentation you wished to make to the user.

Undo/Redo

Don't go there.  Really.  If you implement cut and/or paste, you need to have an undo list.  But the values in the Undo list have to represent logical entities which may shift around because of what is being undone. Short of keeping a "snapshot" of the entire contents of the ListBox, and create new versions of it for each level of undo, you're going to have a lot of problems.  And keeping undo lists means you can't just delete the objects; this screams "smart pointers".

Testing the Unicode version

Well, we believe that this application is Unicode-compatible, but let's test it.  First, we have to create Unicode Debug and Release configurations.  We can do this by dropping down the configuration selection box and selecting the Configuration Manager... option

When the Configuration Manager comes up, select the Active Solution Configuration dropdown, and select the <New...> option

This will bring up the New Solution Configuration dialog:

Type in a name for a configuration in the Solution Configuration Name box; here I chose "Unicode Debug".  Then use the Copy Settings From to specify an existing solution (this is the easiest way to create a new solution).  In this case, I created it from the Debug configuration.  The box comes up checked by default; leave it that way.  Now click OK.

Repeat this same procedure to create a Unicode Release version.

Close the Configuration Manager.

Next, right click the Properties of the project.  Go to Configuration and select Multiple Configurations...

Select the configurations whose properties you are going to modify, and for this example, it will be the two Unicode configurations

Click OK.

At this point, any modifications you make will be set in the selected configurations.

Select the C/C++, Preprocessor option.  Then go to the Preprocessor Definitions and add the symbols UNICODE and _UNICODE to the list of definitions.  They are separated by semicolons.

Because this is Unicode, we have to also change the name of the entry point.  Otherwise we will get a linker error

msvcrt.lib(crtexew.obj) : error LNK2019: unresolved external symbol _WinMain@16 referenced in function _WinMainCRTStartup

As described in KB article 125750, you need to change the entry point name to wWinMainCRTStartup.  To do this, select the Linker, Advanced option and change the Entry Point name (which is initially blank).

Click OK and you're ready to build!

If you run this in Unicode mode, it will save the text as CF_UNICODETEXT, but you can successfully paste the clipboard text into an ANSI applications.

The GDI

Well, not that thing that does graphics.  I'm talking about the Graphical Developer Interface, a topic I address in depth in another essay.  Why should the users get all the benefit of these fancy interfaces?  But I'm going to show how I can make an extension to this system that illustrates both the notion of dynamic menus and how to implement a nicer form of debugging.  What I'm going to do is add, in Debug mode only, a menu item that allows me to see the clipboard formats.  Since I'm lazy and don't like to look up names by number, I'm going to make this slick.

I wanted to demonstrate that the Unicode and ANSI versions manage the clipboard differently, but in fact in a compatible fashion.  You might have other goals in your application.

First, I add a new menu item to the menu, "EnumClipboardFormats".  Using the standard methods, I add a handler for it.  Then I added a DoClipboardFormats function that does the work, added a Ctrl+E case, and so on.  But I put all this code under the #ifdef _DEBUG conditional.

Next, go in and mark all the code that has been generated with #ifdef _DEBUG.  Note that you should never put more than one function or one small section of declarations under this conditional.  There are few programs more tedious to debug than those that start with an #ifdef _DEBUG ahead of one function, and forty or fifty functions later there is an #endif.  Don't do it.  Each function that is conditional should be surrounded by an #ifdef/#endif pair. This way you don't ever accidentally plunk down a piece of code in the scope of some massive conditional compilation, and wonder why it is giving you trouble.  A couple extra lines make it easy to manage this complexity.

In the Unicode version, if I do a Copy operation, I will store CF_UNICODETEXT.  But if I enumerate the clipboard formats, I see

This is because EnumClipboardFormats "enumerates formats in the order that they were placed on the clipboard. If you are copying information to the clipboard, add clipboard objects in order from the most descriptive clipboard format to the least descriptive clipboard format. If you are pasting information from the clipboard, retrieve the first clipboard format that you can handle. That will be the most descriptive clipboard format that you can handle. The system provides automatic type conversions for certain clipboard formats. In the case of such a format, this function enumerates the specified format, then enumerates the formats to which it can be converted."  So all those other formats shown, CF_LOCALE, CF_TEXT and CF_OEMTEXT, represent automatic conversions that can be done.  Now compare this to the same display from the ANSI version:

so we know that the Unicode version is generating the text in the correct format.

Let's look at the changes that were necessary for implementing this "Graphical Developer Interface"

Modifying generated code

Add #ifdef directives around the generated code in the .h and .cpp files:

In the class definition of CListBoxEx

protected: // methods
#ifdef _DEBUG
    void DoEnumClipboardFormats();
#endif
protected: // message handlers
#ifdef _DEBUG
    afx_msg void OnPopupmenuforlistboxEnumclipboardformats();
#endif

In the Message map:

#ifdef _DEBUG
    ON_COMMAND(ID_POPUPMENUFORLISTBOX_ENUMCLIPBOARDFORMATS, OnPopupmenuforlistboxEnumclipboardformats)
#endif

In the OnChar handler

#ifdef _DEBUG
#define SHORTCUT_ENUM_CLIPBOARD_FORMATS 0x0005
#endif
#ifdef _DEBUG
         case SHORTCUT_ENUM_CLIPBOARD_FORMATS:
            DoEnumClipboardFormats();
            return;
#endif

The OnContextMenu handler:

#ifndef _DEBUG
     popup->DeleteMenu(ID_POPUPMENUFORLISTBOX_ENUMCLIPBOARDFORMATS, MF_BYCOMMAND);
#endif

Note that this is an #ifndef handler; this line of code is only generated in Release builds.  This means that you can add the menu item at design time, and it will be removed at runtime.  Think of this as the "natural" way to do things, and don't make any silly excuses about it being "inefficient".  It really doesn't matter.  If you think it matters in the slightest, consult my essay on optimziation.

The handler for the menu item:

#ifdef _DEBUG
void CListBoxEx::OnPopupmenuforlistboxEnumclipboardformats()
    {
     DoEnumClipboardFormats();
    }
#endif

And finally, the actual code that implements this interface

#ifdef _DEBUG
void CListBoxEx::DoEnumClipboardFormats()
    {
     VERIFY(OpenClipboard());
     int fmt = 0;
     CString fmtstr;

     while(TRUE)
        { /* enumerate */
         fmt = ::EnumClipboardFormats(fmt);
         if(fmt == 0)
            break; // all formats have been enumerated
#define DECODE_FMT(x) case x: fmtstr += _T(#x); break;
         switch(fmt)
            { /* fmt decode */
             DECODE_FMT(CF_BITMAP);
             DECODE_FMT(CF_DIB);
             DECODE_FMT(CF_DIBV5);
             DECODE_FMT(CF_DIF);
             DECODE_FMT(CF_DSPBITMAP);
             DECODE_FMT(CF_DSPENHMETAFILE);
             DECODE_FMT(CF_DSPTEXT);
             DECODE_FMT(CF_ENHMETAFILE);
             DECODE_FMT(CF_HDROP);
             DECODE_FMT(CF_LOCALE);
             DECODE_FMT(CF_METAFILEPICT);
             DECODE_FMT(CF_OEMTEXT);
             DECODE_FMT(CF_PALETTE);
             DECODE_FMT(CF_RIFF);
             DECODE_FMT(CF_SYLK);
             DECODE_FMT(CF_WAVE);
             DECODE_FMT(CF_TEXT);
             DECODE_FMT(CF_TIFF);
             DECODE_FMT(CF_UNICODETEXT);
             default:
                { /* default */
                 CString t;
                 t.Format(_T("%d"), fmt);
                 fmtstr += t;
                 break;
                } /* default */
            } /* fmt decode */
         fmtstr += _T("\r\n");
        } /* enumerate */
     ::CloseClipboard();
     AfxMessageBox(fmtstr, MB_ICONINFORMATION | MB_OK);
    } // CListBoxEx::DoEnumClipboardFormats
#endif

Now a really cute technique to know about, and which is illustrated here, is something as close to C# or Java "reflexivity" that you are likely to be able to achieve in C or C++.  The trick is to use the stringizing operator, #, to convert the argument to a string.  So the macro invocation

DECODE_FMT(CF_BITMAP);

actually expands to the C code

case CF_BITMAP: fmtstr += _T("CF_BITMAP"); break;;

This technique can be used any time you have a set of symbolic constants or enumeration types for which you need a printed representation.

Summary

This project demonstrates several dialog box and control techniques.  In an accompanying essay, I show how you can limit the minimum or maximum size that a dialog can be resized to with the mouse, and how you can dynamically rearrange controls in response to a resize request.

download.gif (1234 bytes)

 [Dividing Line Image]

The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright © 2006, The Joseph M. Newcomer Co. All Rights Reserved
Last modified: May 14, 2011