c++mfccedit

Inherited CEdit Control (for vertical centered Text) behaves strange


I am using the code in the first answer of this question: How can we vertically align text in edit box? to center the text in a CEdit control vertically.

Here is the used Class CEditVC

/// HEADER //////////////////////////////////////////

class CEditVC : public CEdit
{
public:
    CEditVC();

protected:
    CRect m_rectNCBottom;
    CRect m_rectNCTop;

public:
    virtual ~CEditVC();

protected:
    afx_msg void OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp);
    afx_msg void OnNcPaint();
    afx_msg UINT OnGetDlgCode();

    DECLARE_MESSAGE_MAP()
};

/// IMPLEMENTATION /////////////////////////////////////////

CEditVC::CEditVC()
    : m_rectNCBottom(0, 0, 0, 0)
    , m_rectNCTop(0, 0, 0, 0)
{
}

CEditVC::~CEditVC()
{
}

BEGIN_MESSAGE_MAP(CEditVC, CEdit)
    ON_WM_NCCALCSIZE()
    ON_WM_NCPAINT()
    ON_WM_GETDLGCODE()
END_MESSAGE_MAP()

void CEditVC::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp) 
{
    CRect rectWnd, rectClient;

    //calculate client area height needed for a font
    CFont *pFont = GetFont();
    CRect rectText;
    rectText.SetRectEmpty();

    CDC *pDC = GetDC();

    CFont *pOld = pDC->SelectObject(pFont);
    pDC->DrawText("Ky", rectText, DT_CALCRECT | DT_LEFT);
    UINT uiVClientHeight = rectText.Height();

    pDC->SelectObject(pOld);
    ReleaseDC(pDC);

    //calculate NC area to center text.

    GetClientRect(rectClient);
    GetWindowRect(rectWnd);

    ClientToScreen(rectClient);

    UINT uiCenterOffset = (rectClient.Height() - uiVClientHeight) / 2;
    UINT uiCY = (rectWnd.Height() - rectClient.Height()) / 2;
    UINT uiCX = (rectWnd.Width() - rectClient.Width()) / 2;

    rectWnd.OffsetRect(-rectWnd.left, -rectWnd.top);
    m_rectNCTop = rectWnd;

    m_rectNCTop.DeflateRect(uiCX, uiCY, uiCX, uiCenterOffset + uiVClientHeight + uiCY);

    m_rectNCBottom = rectWnd;

    m_rectNCBottom.DeflateRect(uiCX, uiCenterOffset + uiVClientHeight + uiCY, uiCX, uiCY);

    lpncsp->rgrc[0].top +=uiCenterOffset;
    lpncsp->rgrc[0].bottom -= uiCenterOffset;

    lpncsp->rgrc[0].left +=uiCX;
    lpncsp->rgrc[0].right -= uiCY;

}

void CEditVC::OnNcPaint() 
{
    Default();

    CWindowDC dc(this);
    CBrush Brush(GetSysColor(COLOR_WINDOW));

    dc.FillRect(m_rectNCBottom, &Brush);
    dc.FillRect(m_rectNCTop, &Brush);
}

UINT CEditVC::OnGetDlgCode() 
{
    if(m_rectNCTop.IsRectEmpty())
    {
        SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED);
    }

    return CEdit::OnGetDlgCode();
}

I have the new CEdit inherited control created like this:

// Header File
CEditVC *mp_inputUser;

// Source File
OnInitDialog()
{
mp_inputUser = new CEditVC();
mp_inputUser->Create(WS_VISIBLE | WS_CHILD, CRect(10,10,100,100), this, 1);
}

But the control shows no cursor and if I type a character it divorces itself into 2 pieces and acts very strange.

What could possibly cause this? Is there a later (better) version of doing this?


Solution

  • Okay so I just want to let you know that after lots of debugging I got behind the issue why the OnNcCalcSize method results in a strange output.

    The OnNcCalcSize method gets called 3 times in an empty VS 2013 MFC project. The first two calls occur when there isnt even a dialog or CEditVC Control set up! That results in the ClientRect being 0,0,0,0. The code UINT uiCenterOffset = (rectClient.Height() - uiVClientHeight) / 2; will NOT work as expected as the calculated uiVClientHeight is something above 0 and is being subtracted from the clientRect height which is 0. The UINT will overflow and every further use will result in undefined behaviour.

    So to work around this issue in latest MFC projects I came up with:

    void CEditVC::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp)
    {
        CRect rectWnd, rectClient;
    
        GetClientRect(rectClient);
    
        // Workaround for calls with an empty Client Rect
        if (rectClient.Height() == 0)
        {
            CEdit::OnNcCalcSize(bCalcValidRects, lpncsp);
            return;
        }
    
        // Everything is back to default down here
        CFont *pFont = GetFont();
        CRect rectText;
        rectText.SetRectEmpty();
    
        CDC *pDC = GetDC();
    
        CFont *pOld = pDC->SelectObject(pFont);
        pDC->DrawText(L"Ky", rectText, DT_CALCRECT | DT_LEFT);
        UINT uiVClientHeight = rectText.Height();
    
        pDC->SelectObject(pOld);
        ReleaseDC(pDC);
    
        GetWindowRect(rectWnd);
    
        ClientToScreen(rectClient);
    
        UINT uiCenterOffset = (rectClient.Height() - uiVClientHeight) / 2;
        UINT uiCY = (rectWnd.Height() - rectClient.Height()) / 2;
        UINT uiCX = (rectWnd.Width() - rectClient.Width()) / 2;
    
        rectWnd.OffsetRect(-rectWnd.left, -rectWnd.top);
        m_rectNCTop = rectWnd;
    
        m_rectNCTop.DeflateRect(uiCX, uiCY, uiCX, uiCenterOffset + uiVClientHeight + uiCY);
    
        m_rectNCBottom = rectWnd;
    
        m_rectNCBottom.DeflateRect(uiCX, uiCenterOffset + uiVClientHeight + uiCY, uiCX, uiCY);
    
        lpncsp->rgrc[0].top += uiCenterOffset;
        lpncsp->rgrc[0].bottom -= uiCenterOffset;
    
        lpncsp->rgrc[0].left += uiCX;
        lpncsp->rgrc[0].right -= uiCY;
    }
    

    So I just added the if-statement on top to check if the function deals with an empty CRect and if that is the case, just use the base method of CEdit.

    I suppose the class was written for old C++ MFC environments like VS6 when MFC or WinApi procedures were a little different...