6.3. Controls

Microsoft Visual C++/Microsoft Foundation Classes


6.3. Controls

6.3.1. How do I get a CControl from a Dialog Template?

You can get a pointer to a control from a already created dialog control by doing a simple typecast of the results from GetDlgItem. Here's an example that creates a CButton from a checkbox with ID : IDC_CHECK1.

void my_function(CDialog * pDialog)
{
    CButton* pButton = (CButton*)pDialog->GetDlgItem(IDC_CHECK1);
    ASSERT(pButton != NULL);
    pButton->SetCheck(m_bShowState);
}

Note that it's always safer to check for the validity of the results from GetDlgItem.

[email protected], 6/1/95

6.3.2. How do I subclass a control using MFC?

Read the documentation on SubClassDlgItem. Here's an example of how to call it:

BOOL CMyDialog::OnInitDialog()
{
    //Do your subclassing first.
    m_MyControl.SubClassDlgItem(ID_MYCONTROL, this);
 
    //Let the base class do its thing.
    CDialog::OnInitDialog();
 
    // Perhaps do some more stuff
    // Be sure to call Ctl3d last, or it will cause
    // assertions from multiple subclassing.
    Ctl3dSubclassDlg(m_hWnd, CTL3D_ALL);
}

Mike Williams, [email protected], mfc-l 6/1/95

6.3.3. Why do I get an ASSERT when I subclass a control?

Make sure that you subclass the control BEFORE you call Ctl3dSubclassDlg, if the 3-d control DLL is loaded first, it will already have subclassed your controls and you will get an assert.

Mike Williams, [email protected], mfc-l 6/1/95

6.3.4. How do I validate the contents of a control when it loses focus?

NOTE: This is in the Microsoft Software Library.

The FCSVAL sample application was created to show how an application can do control-by-control validation in a dialog box.

The application itself is just a modal dialog box displayed by the CWinApp::InitInstance(). After displaying the dialog box, InitInstance() simply quits the application.

The important part of the sample takes place in the dialog-box class implementation: There are two edit controls. The first takes input of an integer between 1 and 20. The second takes a character string as input with length less than or equal to 5. When you Tab or mouse-click from control to control within the displayed dialog box, the contents of the control that is losing focus are validated.

The CFocusDlg Class

The application's functionality centers around the CFocusDlg class and its implementation of four message handlers (discussed below). Normal data exchange (DDX) and validation (DDV) using the routines provided by MFC take place in OnInitialUpdate(), when the dialog box is first displayed, and when the user chooses the OK button to accept the input. This is default behavior provided by ClassWizard when member variables are connected to dialog-box controls and can be examined in the dialog class DoDataExchange() function.

Validating control contents when switching focus from one control to the next is done by handling the EN_KILLFOCUS notification sent by the edit control that is losing focus. The idea here is to check the contents and, if they are not valid, to display the message box, inform the user, and then set the focus back to the control from which it came. Unfortunately, some difficulties arise when trying to set the focus (or display the message boxes) within a killfocus message handler. At this point, Windows is in an indeterminate state as it is moving focus from one control to the other. This is a bad place to do the validation and SetFocus() call.

The solution here is to post a user-defined message to the dialog box (parent) and do the validation and SetFocus() there, thus waiting for a safer time to do the work. (See "CFocusDlg::OnEditLostFocus()" in the file FOCUSDLG.CPP and "WM_EDITLOSTFOCUS user-defined message" in the file FOCUSDLG.H.)Another thing you will notice about this function is that it uses TRY/CATCH to do the validation. The provided DDX/DDV routines throw CUserExceptions when failing to validate or load a control's data. You should catch these and do the SetFocus() in the CATCH block.

Note: This sample has other cool stuff, but this is the major one I've seen asked about on the Net.

MS FAQ, 6/25/95

6.3.5. How do I enable/disable a bank of checkboxes?

I don't know about a magic way to do this using a single HWND, but there is a simple and self-documenting technique that I've been using for a long time. You can make a routine that accepts an array of UINTs (your control IDs) and a visibility flag.This function can be a stand-alone function, or you can put it inside a class. I have been collecting little utility functions like this and keep them in a CDialogBase class -- when I create a new dialog box in ClassWizard, I fix up the code to derive from CDialogBase instead of CDialog.

For example, the function might look like this:

void CDialogBase::ShowControls(UINT* pControls, UINT cControls, BOOL fVisible)
{
    for (UINT uIndex = 0; uIndex < cControls; uIndex++)
    {
        CWnd* pwnd = GetDlgItem(pControls[uIndex]);
        if (pwnd)
        {
       
    pwnd->ShowWindow(fVisible ? SW_SHOW : SW_HIDE);
           
                pwnd->EnableWindow(fVisible);
        }
    }
}

Then later, often in your OnInitDialog handler, you can call this function with your control group:

#define SIZEOF_ARRAY(a) (sizeof(a) / sizeof(a[0]))
{
    static UINT aGroup1[] = { DLG_CHBOX1, DLG_CHBOX2, DLG_STATIC1 };
    static UINT aGroup2[] = { DLG_LABEL2, DLG_LABEL7 };
    ShowControls(aGroup1, SIZEOF_ARRAY(aGroup1), TRUE);
    ShowControls(aGroup2, SIZEOF_ARRAY(aGroup2), FALSE);
}

You can find many uses for these control arrays later too... (Changing fonts in a series of controls, etc...) Good luck,

[email protected], mfc-l, 7/18/95

6.3.6. How do I change the background color of a control?

Your dialog can trap the WM_CTLCOLOR message, look up the MFC help file notes for CWnd::OnCtlColor(). Before a control is about to paint itself, the parent window receives a chance to set its own default text color and background brush.

[email protected], mfc-l, 7/18/95

Also check out the MS KB article ID: Q117778 TITLE: Changing the Background Color of an MFC Edit Control.

Ramesh, MSMFC, 7/19/95

6.3.7. How do I trap the key for my control?

Handle WM_GETDLGCODE and return the appropriate value. Remember that the listbox (or any other control) can only handle keyboard input when it has the focus.

[email protected], programmer.misc, 8/21/95, programmer.misc

6.3.8. How can I DDX with a multiple selection listbox?

Download MLBDDX.ZIP from the MSMFC library on CIS. You'll get all the necessary code. When the dialog closes, a provided CStringList will be filled with the selected items. Freeware.

-Patrick Philippot, CIS email, 8/3/95

6.3.9. How do I change the background color of a BUTTON???

NOTE: THE METHOD IN 6.3.6 WILL NOT WORK FOR BUTTONS!

If you want to change the color of a dialog button, you have to use owner-draw button. (you can use bitmap buttons) Changing the color through OnCtlColor() will not work for buttons. The following Knowledge Base articles (GO MSKB on CIS) may be of help to you.

ID: Q32685 TITLE: Using the WM_CTLCOLOR Message

ID: Q64328 SAMPLE: Owner-Draw: 3-D Push Button Made from Bitmaps with Text

This article explains sample code for a owner-draw button.

Ramesh, NetQuest., MSMFC, 8/3/95

6.3.10. Why isn't CEdit putting things on separate lines?

Make sure that the lines are separated with \r\n, not just \n.

[email protected], mfc-l, 8/7/95

6.3.11. How do I get to the CEdit in a combobox?

CComboCox combo;
CEdit edit;
// combobox creation ...
// ...
POINT tmpPoint = {1,1};
edit.SubclassWindow( combo.ChildWindowFromPoint(tmpPoint)->GetSafeHwnd() );

[email protected], mfc-l, 8/25/95

Or:

Look into the mfc sample - npp - npview.cpp! Turns out all combo's create their edits with an ID of 1001 (decimal) so - if pComboBox is the pWnd object pointing to the combo - all you need is:

pComboBox->GetDlgItem(1001);

UPD!! 6.3.12. How do I load more than 64K into an edit control?

The Rich Edit Control available in VC++ 2.1+ supports much more than 64k. The Wordpad sample is a great way to learn more about this subject. If you're stuck with 16-bit programming, I think that magma systems has a 16-bit DLL that does this. Contact Marc Adler at: [email protected] for details.

[email protected]

I have found under NT that unless you use SetLimitText( 0 ) (or send a EM_SETLIMITTEXT with WPARAM = 0) you are limited to adding about 32K programatically. You dont get any warning or EN_ERRSPACE; you just loose the end of text you add.

Stephen Lee [[email protected]]

6.3.13. How do I subclass the listbox portion of a combobox?

The listbox portion of a combobox is of type COMBOLBOX ( notice the 'L').    Because the ComboLBox window is not a child of the ComboBox window, it is not obvious how to subclass the COMBOLBOX control. Luckily, under the Win32 API, Windows sends a message to the COMBOBOX ( notice no 'L') called  WM_CTLCOLORLISTBOX before the listbox is drawn. The lParam passed with this message contains the handle of the listbox. For example:
 
LRESULT CFileUpdateCombo::OnCtlColorListBox(WPARAM wParam, LPARAM lParam)
{
    if ( ! m_bSubclassedListBox )
    {
        HWND hWnd = (HWND)lParam;
        CWnd* pWnd = FromHandle(hWnd);
        if ( pWnd && pWnd != this )
        {
            // m_ListBox is derived from CListBox
            m_ListBox.SubclassWindow(hWnd );
            m_ListBox.SetOwner(this);
            m_bSubclassedListBox = TRUE;
        }
    }
    return (LRESULT)GetStockObject(WHITE_BRUSH);
}

[email protected], email, 9/7/95

6.3.14. How do I inherit from a MFC standard control class and provide initialization code that works on both subclassed and non-subclassed controls?

[ed note: Ok, this probably isn't a FAQ, but I thought it sounded pretty cool.]

I have a fix, but you may not like it; however, it takes care of both subclassing methods.

If SubclassWindow() was virtual, all problems would be solved, as SubclassDlgItem calls SubclassWindow(), and common initialization could be called from this point, and from OnCreate(). Even better would be a virtual SetupWindow() function called from all initialization points by Microsoft's code.

C'est la vie. My fix might slow the message loop for the control in question, but so far I haven't seen any performance hits. Over-ride the virtual function WindowProc() for your control something like the following (call SetupWindow() in OnCreate() also):


LRESULT CExtendControl::WindowProc( UINT message, WPARAM wParam, LPARAM lParam)
{
    if (!m_bSetup)
        SetupWindow(); 
    return CEdit::WindowProc(message, wParam, lParam );
}
//This is a virtual function. Use it for Hwnd setup in all inherited
//classes. It will work for a subclassed window.
void CExtendControl::SetupWindow()
{
    ASSERT( m_hWnd );
    m_bSetup = TRUE;
    //*** Insert Initialization Code here!***
}

Jody Power ([email protected])

NEW!!  6.3.15.   How do you add controls to a CDialog dynamically, instead of using a dialog resource?

You can add controls to your dialog dynamically by using methods CWnd::Create() and CWnd::CreateEx() or overridables of CWnd::Create() in control window wrapper classes such as CEdit or CListbox, etc. For example, to create CEdit control, you can do the following:

1) add a member variable m_ec_myedit to your dialog .h file;

2) I assume that your dialog templete has some control with ID = IDC_ABOVE_DYNAMIC_EDIT, and you want your dynamically created edit control to have the same width and be placed under IDC_ABOVE_DYNAMIC_EDIT. Then add the following code under the call to CDialog::OnInitDialog() in your overriden OnInitDialog():

GetDlgItem(IDC_ABOVE_DYNAMIC_EDIT)->GetWindowRect(rect);
ScreenToClient(rect);
CRect rectNew(rect.left, rect.bottom+5, rect.right, rect.bottom+35);
m_myEdit.CreateEx(WS_EX_CLIENTEDGE, "EDIT", NULL
/*lpszWindowName*/,
WS_CHILD|WS_VISIBLE|WS_GROUP|WS_TABSTOP|WS_BORDER, rectNew.left, rectNew.top,
rectNew.Width(), rectNew.Height(), this->GetSafeHwnd(), NULL, NULL);
m_myEdit.ShowWindow(SW_SHOW);

It's that simple. The only thing that differs for different control classes is window styles. Usually, you can find the most important of style and extended style constants in online help.

[email protected]mfc-l, 6/12/98

NEW!! 6.3.16.  Why is my fixed-height owner drawn listbox's MeasureItem never called?

The MeasureItem function for a fixed-height owner drawn is only called once. The problem is that it is called before the MFC listbox object is associated with the Windows listbox control.  The solution is to invoke the listbox's MeasureItem from the OnMeasureItem function of the dialog containing the listbox:

void CExampleDlg::OnMeasureItem(int nIDCtl, LPMEASUREITEMSTRUCT lpMeasureItemStruct)
{
    CDialog::OnMeasureItem(nIDCtl, lpMeasureItemStruct);
 
    if (nIDCtl == IDC_LISTBOX)
        m_ListBox.MeasureItem(lpMeasureItemStruct);
}
 

Eric Bergman-Terrell, [email protected], 5/16/97