Using ON_UPDATE_COMMAND_UI with all controls in a Dialog

From the previous messages in this thread, you can see how it is possible to use the ON_UPDATE_COMMAND_UI macro with Dialog boxes by overriding the PreTranslateMessage(MSG *pMsg) and calling UpdateDialogControls(CCmdUI*, BOOL). However, what has not been discussed so far and one reason why the App Wizard does not offer the option of adding update handlers to any controls in a dialog is that these controls are likely to require more than just simple window enabling or disabling. As a result, you might run into a couple of problems.

Consider a dialog with a CComboBox control, if all that is required is to enable and disable this control depending on a certain condition, then the ON_COMMAND_UPDATE_HANDLER provides a convenient method to perform this operation:


void CMyDialog::OnUpdateMyComboBox(CCmdUI* pCmdUI)
{
	pCmdUI->Enable(m_bShowCombo);
}

However, if it is also necessary to update the selection of the CComboBox as well as enabling and disabling it then you might code the following:

void CMyDialog::OnUpdateMyComboBox(CCmdUI* pCmdUI)
{
	// Get the combo box
	CComboBox* pMyCombo = (CComboBox *) pCmdUI->m_pOther;

	// Update the selection according to our member variable
	pMyCombo->SetCurSel(m_nSel);

	pCmdUI->Enable(m_bShowCombo);
}

However, unfortunately the code above would lead you into all sorts of trouble. First of all, every time the UpdateDialogControls(CCmdUI*, BOOL) function is called (which will ultimately send out the desired ON_UPDATE_COMMAND_UI messages), the ComboBox as a result of the code in its update handler will change its current selection. All this is going to cause enough flicker to make it an irritating problem. The solution is as simple as is obvious; don’t update the selection if it will result in the same selection being made.

void CMyDialog::OnUpdateMyComboBox(CCmdUI* pCmdUI)
{
	CComboBox* pMyCombo = (CComboBox *) pCmdUI->m_pOther;

	int nCurSel = pMyCombo->GetCurSel();

	if (nCurSel != m_nSel)
		pMyCombo->SetCurSel( m_nSel );

	pCmdUI->Enable(m_bShowCombo);
}

Albeit that the above scenario isn't going to occur very often there are times when something similar will be required. However, we will still have another more immediate problem; that is, the user will not be able to make a selection using the ComboBox. Try it and you'll see why. If they try to, ON_UPDATE_COMMAND_UI messages will spring from all over the place and our combo box will be constantly trying to set the current selection.

The solution to this problem is slightly more taxing, but only just. Have a look at the following code:

// Does the current window have the focus
bool CMyDialog::HasFocus(const CWnd* pWndCtrl) const
{
	ASSERT(::IsWindow(pWndCtrl->m_hWnd));
	// if (pWndCtrl->m_hWnd == GetFocus()->m_hWnd) is insufficient, so...

	// Get window with focus
	CWnd* pFocusWnd = GetFocus();

	// Ensure its not null and is a window. Can occur if application loses and regains focus
	if (pFocusWnd == NULL || !::IsWindow(pFocusWnd->m_hWnd))
	return true;

	// Don't update if the control has the focus
	if (pFocusWnd->GetDlgCtrlID() == pWndCtrl->GetDlgCtrlID())
	return true;

	return false;
}

So now, all that is necessary is to place a call to the HasFocus() function (which does a bit more than checking for just focus) and do nothing if we do have the focus.


void CMyDialog::OnUpdateMyComboBox(CCmdUI* pCmdUI)
{
	CComboBox* pMyCombo = (CComboBox *) pCmdUI->m_pOther;

	If (HasFocus(pMyCombo)
		return;

	int nCurSel = pMyCombo->GetCurSel();

	if (nCurSel != m_nSel)
		pMyCombo->SetCurSel( m_nSel );

	pCmdUI->Enable(m_bShowCombo);
}

So with some very simple code, you can update all the controls in your dialog. If you only have a few controls then it really isn't worth it. However, if you have a fair few of them and in cases where their state changes regularly and perhaps even relies on the state of other controls in the dialog this could be a good way to go. Simply it is as follows:

Don't update if you have the focus
Don't update if you aren't changing any values

The demo exe and source below use the ON_UPDATE_COMMAND_UI macro with a little more vigour. The contents of one Combo box is updated to reflect the selection in another, CEdits perform similar things as other controls. Have a look why dont you.

Caveat Emptor

Oh yes the But...I almost forgot the But... But, ON_UPDATE_COMMAND_UI handling is done during your application OnIdle() function. If you consume loads of on idle time processing your handlers, you may find Windows gets a little fed up and the HDD will go into paging overdrive. As it happens, windows has some of its own stuff to do during OnIdle and if it doesn't get to do it... Saying that though, I've never had any problems and neither should you if keep your update handlers on the light side.

Download demo project (.exe) - 6 KB

Download source - 11 KB

Date Last Updated: January 25, 1999



Comments

  • Highlighted controls based on your code.

    Posted by Legacy on 03/31/1999 12:00am

    Originally posted by: Navi Singh

    I added a line to your HasFocus function to fix focus
    
    problems I encountered with comboboxes. GetFocus()
    returns the Edit control nested inside the combo control.
    Consequently, GetDlgCtrlID() returns 1001 and not the
    ID of the combo control.

    BOOL CFtpDlg::HasFocus(const CWnd* pWndCtrl) const
    {
    ASSERT(::IsWindow(pWndCtrl->m_hWnd));
    // if (pWndCtrl->m_hWnd == GetFocus()->m_hWnd) is insufficient, so...

    // Get window with focus
    CWnd* pFocusWnd = GetFocus();

    // added to make comboboxes work just right.
    //=================================================
    if(pFocusWnd->GetParent()->GetDlgCtrlID())
    pFocusWnd = pFocusWnd->GetParent();
    //=================================================

    // Ensure its not null and is a window. Can occur if application loses and regains focus
    if (pFocusWnd == NULL || !::IsWindow(pFocusWnd->m_hWnd))
    return TRUE;

    // Don't update if the control has the focus
    if (pFocusWnd->GetDlgCtrlID() == pWndCtrl->GetDlgCtrlID())
    return TRUE;

    return FALSE;
    }

    In my command handler I have the following:

    void CFtpDlg::OnUpdateHandler(CCmdUI* pCmdUI)
    {
    CWnd *pWnd = pCmdUI->m_pOther;

    BOOL bHasFocus = HasFocus(pWnd);
    COLORREF color = bHasFocus ? RGB(255, 0, 0) : GetSysColor(COLOR_BTNFACE);
    CPen pen(PS_INSIDEFRAME, 2 * GetSystemMetrics(SM_CYBORDER), color);

    CRect rect;
    pWnd->GetWindowRect(rect);

    CClientDC dc(this);
    dc.SaveDC();
    dc.SelectObject(pen); // the pen used to draw the rectangle's border.
    dc.SelectObject(GetStockObject(NULL_BRUSH)); // required to draw a transparent rectangle.
    ScreenToClient(rect); // convert the screen coordinates into client coordinates.
    InflateRect(rect, 2, 2);
    dc.Rectangle(rect); // draw the rect.
    dc.RestoreDC(-1);
    }

    Using your command handler logic, I have highlighted
    controls. The control under focus has a nice red rectangle
    around it.

    Navi

    Reply
Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The operational costs of managing an x86 base are taxing IT budgets, making it difficult to fund and staff new initiatives. Today's IT organization must seek efficiencies in its operations and shift to a more agile infrastructure that's flexible enough to adapt to future changes in the business. Read this Q & A session with Jed Scaramella, research manager for IDC's Enterprise Platforms and Data Center Trends, to learn how the integrated nature of the blade platform delivers critically needed efficiencies …

  • The latest release of SugarCRM's flagship product gives users new tools to build extraordinary customer relationships. Read an in-depth analysis of SugarCRM's enhanced ability to help companies execute their customer-facing initiatives from Ovum, a leading technology research firm.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds