6.2. Controls

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

scot@stingray.com, 6/1/95

6.2.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, mikew@marlin.ssnet.com, mfc-l 6/1/95

6.2.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, mikew@marlin.ssnet.com, mfc-l 6/1/95

6.2.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.2.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,

jmccabe@portage1.portup.com, mfc-l, 7/18/95

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

jmccabe@portage1.portup.com, 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.2.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.

joej@golddisk.com, programmer.misc, 8/21/95, programmer.misc

6.2.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.2.9. How do I change the background color of a BUTTON???

NOTE: THE METHOD IN 6.2.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.2.10. Why isn't CEdit putting things on separate lines?

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

sutor@watson.ibm.com, mfc-l, 8/7/95

6.2.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() );

jahans@slb.com, 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);

6.2.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: 75300.2062@compuserve.com for details.

scot@stingray.com

6.2.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);
}

mikem@abelcomputers.com, email, 9/7/95

6.2.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 (jodyp@andyne.on.ca)