3

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?

Andrew Truckle
  • 17,769
  • 16
  • 66
  • 164
  • If you use IsKindOf(), you should then use a plain cast. The golden rule with MFC is: do exactly as it's done by the MS developpers... Since that's how they test it. anything else is somwhat untested and its behavior undefined. This will save you countless hours of debuging. – Michaël Roy Nov 01 '21 at 07:37
  • @michael I have followed the right process but as you can see there are instances where IsKindOf will not detect a combo. This is why I the additional check. – Andrew Truckle Nov 01 '21 at 08:22
  • If pWnd->IsKindOf (RUNTIME_CLASS(CComboBox ) returns FALSE, then casting pWnd to a CComboBox* is most definitely not recommended.... UNLESS the function you will be calling only calls SendMessage() on the CWnd.. In that case, don't even bother calling IsKindOf(), and use plain C-style casting. Yes, MS delvelopers do that once in a while. MFC is far from perfect, from a C++ perspective. Many times, MS devs simply call ASSERT_KIND_OF() before casting. – Michaël Roy Nov 01 '21 at 08:55

1 Answers1

3

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);
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • I confirm that using `reinterpret_cast` in this instance does work right. Why is it that in the other scenarios (like when using `IsKindOf` that the `dynamic_cast` works though? – Andrew Truckle Oct 31 '21 at 22:55
  • Should **all** my `dynamic_cast` actually be `reinterpret_cast`? – Andrew Truckle Oct 31 '21 at 22:56
  • 1
    *"But it can't convert parent to child."* - No, that's wrong. [`dynamic_cast`](https://en.cppreference.com/w/cpp/language/dynamic_cast) can move *"up, down, and sideways along the inheritance hierarchy."* – IInspectable Nov 01 '21 at 07:17
  • 2
    *"Functions like `CWnd::GetFocus` will return a reference to that object."* - No, not really. `GetFocus` needs to be prepared to work in cases where controls are backed by MFC types as well as those that aren't. Depending on this, it will produce a pointer to the concrete, derived control instance, or a generic, temporary `CWnd*`. It is the latter case, where a `dynamic_cast` fails. This part of the framework is a lot more involved than this answer suggests. – IInspectable Nov 01 '21 at 09:08
  • 1
    If you want to cast ignoring check, you'd better use `static_cast` instead of `reinterpret_cast`. For multiple inheritance `reinterpret_cast` may give very wrong reults. Sure MFC does not use multiple inheritances, but still a good habit to use the right cast here – Alex Guteniev Nov 01 '21 at 15:14
  • @AlexGuteniev I didn't know that. I guess the c-style cast also defaults to `static_cast`, not `reinterpret_cast`? – Barmak Shemirani Nov 01 '21 at 15:24
  • When there's inheritance relationship, C-style cast performs pointer adjustment, like `static_cast`, but, unlike `static_cast` can see through private/protected inheritance. But when types are incomplete, or no inheritance relationship, `static_cast` does not compile, but C-style cast performs `reinterpret_cast` – Alex Guteniev Nov 01 '21 at 15:27
  • https://stackoverflow.com/a/332086/2945027 – Alex Guteniev Nov 01 '21 at 15:36
  • 1
    I realize now that I don't really know what the hell I am talking about. I tried to shorten the answer, hopefully that's correct and relevant. – Barmak Shemirani Nov 01 '21 at 17:45
  • 1
    I have reverted to `static_cast`. And, now it is clear, in the sense I had mapped the combo only to a `CString` and not to an actual `CComboBox` variable. – Andrew Truckle Nov 02 '21 at 09:44
  • Note, a few of you (eg @IInspectable too) have been editing your answers(appreciated) but not tagging me in a comment when warranted. I am not notified when changes are made to answers so sometimes I miss your improvements. I only found this out because I thought I would check again. – Andrew Truckle Nov 02 '21 at 09:47
  • 1
    @AndrewTruckle sorry, I thought the asker is notified automatically of any changes to the answer. – Barmak Shemirani Nov 02 '21 at 12:25