Search code examples
visual-c++castingcomboboxmfc

dynamic_cast of CWnd to CComboBox is yeilding a null pointer in OnSize function


Code:

void CChristianLifeMinistryEditorDlg::OnSize(UINT nType, int cx, int cy)
{
    CResizingDialog::OnSize(nType, cx, cy);

    const CWnd* pFocus = GetFocus();
    CComboBox* pFocusCombo = nullptr;

    if (pFocus != nullptr)
    {
        if (pFocus->GetParent()->IsKindOf(RUNTIME_CLASS(CComboBox)))
        {
            pFocusCombo = dynamic_cast<CComboBox*>(GetFocus()->GetParent());
        }
    }

    for (CWnd* pWnd = GetWindow(GW_CHILD); pWnd != nullptr; pWnd = pWnd->GetNextWindow(GW_HWNDNEXT))
    {
        if (pWnd == pFocusCombo)
        {
            // TODO: Sadly, by now, the control has already got all the text selected.
            //pFocusCombo->SetEditSel(LOWORD(dwEditSel), HIWORD(dwEditSel));
        }
        else if (pWnd->IsKindOf(RUNTIME_CLASS(CComboBox)))
        {
            // This only works for combo boxes that are bound to controls
            auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
            pCombo->SetEditSel(-1, -1);
        }
        else
        {
            CString strClassName;
            if (::GetClassName(pWnd->GetSafeHwnd(), strClassName.GetBuffer(_MAX_PATH), _MAX_PATH))
            {
                if (strClassName == _T("ComboBox"))
                {
                    auto* pCombo = (CComboBox*)pWnd;
                    //auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
                    pCombo->SetEditSel(-1, -1);
                }
            }
            strClassName.ReleaseBuffer();
        }
    }

    if (m_pHtmlPreview != nullptr)
    {
        m_lblHtmlPreview.GetWindowRect(m_rctHtmlPreview);
        ScreenToClient(m_rctHtmlPreview);

        m_pHtmlPreview->MoveWindow(m_rctHtmlPreview);
    }
}

I display the whole function for context. But I am specifically interested in this bit:

CString strClassName;
if (::GetClassName(pWnd->GetSafeHwnd(), strClassName.GetBuffer(_MAX_PATH), _MAX_PATH))
{
    if (strClassName == _T("ComboBox"))
    {
        auto* pCombo = (CComboBox*)pWnd;
        //auto* pCombo = dynamic_cast<CComboBox*>(pWnd);
        pCombo->SetEditSel(-1, -1);
    }
}
strClassName.ReleaseBuffer();

During code analysis updates I had many situations where I had to update C-Style casts. A lot of the time I was able to use static_cast, but, in some instances the compiler would then tell me I should use dynamic_cast.

I then found that my application was not working correctly and in debug mode isolated it to this bit:

//auto* pCombo = (CComboBox*)pWnd;
auto* pCombo = dynamic_cast<CComboBox*>(pWnd);

It turned out that the cast pointer pCombo was null. Yet, this never happens when I use the C-Style cast. As a result I have reverted to the C-Style cast. I saw this discussion (MFC Classes and C++ style casts) but I can't see that this is the reason (temporary pointers).

What cast should I be using that I can rely on in this situation?


Solution

  • C-style casting will try different c++ casting, it may choose reinterpret_cast static_cast. This is converting CWnd* to CComboBox*, for example:

    CWnd* wnd = GetDlgItem(IDC_COMBO1);
    CComboBox* combo = (CComboBox*)wnd;
    

    In general, parent class can't "always" be converted to child. It depends if CComboBox m_combobox; for that ID was created.

    A) m_combobox does exist:

    In this case our wnd can be a reference to m_combobox.

    CWnd::GetDlgItem etc. cast &m_combobox to CWnd*, they pass it around.

    dynamic_cast checks it and converts back to CComboBox*.

    MFC's IsKindOf will confirm if m_combobox was created.

    B) m_combobox doesn't exist:

    In this case our wnd is CWnd* object. MFC never created CComboBox for that control.

    dynamic_cast tests it, can't convert to CComboBox*

    static_cast works if wnd's classname is "ComboBox"


    The code below should be okay. In this case you can skip dynamic_cast if you want, rely on static_cast. But it's better to use dynamic_cast if possible.

    CComboBox* ptr = nullptr;
    if (wnd->IsKindOf(RUNTIME_CLASS(CComboBox)))
        ptr = dynamic_cast<CComboBox*>(wnd);
    if(!ptr)
    {
        CString classname;
        ::GetClassName(wnd->m_hWnd, classname.GetBuffer(_MAX_PATH), _MAX_PATH);
        classname.ReleaseBuffer();
        if(classname == L"ComboBox")
            ptr = static_cast<CComboBox*>(wnd);
    }