Click here to Skip to main content
15,893,622 members
Articles / Desktop Programming / MFC

Control Positioning and Sizing using a C++ Helper Class

Rate me:
Please Sign up or sign in to vote.
4.88/5 (43 votes)
20 Sep 2005CPOL11 min read 205.4K   4.1K   97  
Add layout management of controls to a CWnd or CDialog using a C++ helper class.
// Filename: GroupBox.cpp
// 2005-08-07 nschan Initial revision.
// 2005-09-07 nschan Added OnCheckReposition().

#include "stdafx.h"
#include "GroupBox.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

// Constants.
static const int  TEXT_RECT_HEIGHT = 11;
static const int  TOP_OFFSET = 4;
static const int  INDENT_WIDTH = 5;
static const UINT CHECK_REPOSITION_MESSAGE = WM_USER + 1;

// Helper functions for drawing.
static void DrawLeftSide(CDC* pDC, CPen* penLight, CPen* penDark, int x, int y, int length)
{    
    pDC->SelectObject(penDark);
    pDC->MoveTo(x+1,y);
    pDC->LineTo(x,y);
    pDC->LineTo(x,y+length);

    pDC->SelectObject(penLight);
    pDC->MoveTo(x+1,y+1);
    pDC->LineTo(x+1,y+length);

}

static void DrawTopSide(CDC* pDC, CPen* penLight, CPen* penDark, int x, int y, int length, const CString& text)
{    
    int offset = TOP_OFFSET;

    if ( length < 16 || text.IsEmpty() )
    {
        // Draw top line straight across, offset by 6 from the client rect top.
        pDC->SelectObject(penDark);
        pDC->MoveTo(x,y+offset);
        pDC->LineTo(x+length,y+offset);

        pDC->SelectObject(penLight);
        pDC->MoveTo(x,y+offset+1);
        pDC->LineTo(x+length,y+offset+1);
    }
    else
    {
        // Draw left indent.
        pDC->SelectObject(penDark);
        pDC->MoveTo(x,y+offset);
        pDC->LineTo(x+INDENT_WIDTH,y+offset);
        pDC->SelectObject(penLight);
        pDC->MoveTo(x,y+offset+1);
        pDC->LineTo(x+INDENT_WIDTH,y+offset+1);

        // Create a font.
        CRect rectText;
        CFont font;
        LOGFONT lf;
        ::ZeroMemory(&lf, sizeof(lf));
        lf.lfHeight = 80;
        ::lstrcpy(lf.lfFaceName, "MS Sans Serif");
        if ( font.CreatePointFontIndirect(&lf) )
        {
            // Draw the text.
            rectText.left   = x + INDENT_WIDTH + 2;
            rectText.top    = y - 2;
            rectText.right  = x + length - INDENT_WIDTH - 2;
            rectText.bottom = y + TEXT_RECT_HEIGHT - 1;
            CFont* pOldFont = pDC->SelectObject(&font);
            int oldBkMode = pDC->SetBkMode(TRANSPARENT);
            pDC->DrawText(text, &rectText, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_CALCRECT);
            pDC->DrawText(text, &rectText, DT_LEFT | DT_VCENTER | DT_SINGLELINE);
            pDC->SetBkMode(oldBkMode);
            pDC->SelectObject(pOldFont);
        }

        // Draw right indent.
        pDC->SelectObject(penDark);
        pDC->MoveTo(x+INDENT_WIDTH+2+rectText.Width()+2,y+offset);
        pDC->LineTo(x+length,y+offset);
        pDC->SelectObject(penLight);
        pDC->MoveTo(x+INDENT_WIDTH+2+rectText.Width()+2,y+offset+1);
        pDC->LineTo(x+length,y+offset+1);
    }
}

static void DrawRightSide(CDC* pDC, CPen* penLight, CPen* penDark, int x, int y, int length)
{    
    pDC->SelectObject(penDark);
    pDC->MoveTo(x,y);
    pDC->LineTo(x,y+length);

    pDC->SelectObject(penLight);
    pDC->MoveTo(x+1,y);
    pDC->LineTo(x+1,y+length);

}

static void DrawBottomSide(CDC* pDC, CPen* penLight, CPen* penDark, int x, int y, int length)
{    
    pDC->SelectObject(penDark);
    pDC->MoveTo(x,y);
    pDC->LineTo(x+length-1,y);

    pDC->SelectObject(penLight);
    pDC->MoveTo(x,y+1);
    pDC->LineTo(x+length-1,y+1);
    pDC->LineTo(x+length-1,y-1);

}

// CGroupLine ////////////////////////////////////////////////////////////////

IMPLEMENT_DYNAMIC(CGroupLine,CWnd)

BEGIN_MESSAGE_MAP(CGroupLine,CWnd)
    //{{AFX_MSG_MAP(CGroupLine)
    ON_WM_CREATE()
    ON_WM_ERASEBKGND()
    ON_WM_PAINT()
    ON_WM_SIZE()
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()

CGroupLine::CGroupLine()
{
    m_text = "";

    m_isLeft   = false;
    m_isTop    = false;
    m_isRight  = false;
    m_isBottom = false;

    // Bitmap for memory DC drawing.
    m_bitmap = NULL;
    m_bitmapSize = CSize(0,0);
}

CGroupLine::~CGroupLine()
{
    delete m_bitmap;
}

void CGroupLine::SetText(const CString& text)
{
    m_text = text;
    if ( ::IsWindow(m_hWnd) )
    {
        Invalidate(TRUE);
    }
}

void CGroupLine::SetLeft()
{
    m_isLeft   = true;
    m_isTop    = false;
    m_isRight  = false;
    m_isBottom = false;
}

void CGroupLine::SetTop()
{
    m_isLeft   = false;
    m_isTop    = true;
    m_isRight  = false;
    m_isBottom = false;
}

void CGroupLine::SetRight()
{
    m_isLeft   = false;
    m_isTop    = false;
    m_isRight  = true;
    m_isBottom = false;
}

void CGroupLine::SetBottom()
{
    m_isLeft   = false;
    m_isTop    = false;
    m_isRight  = false;
    m_isBottom = true;
}

int CGroupLine::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    return 0;
}

BOOL CGroupLine::OnEraseBkgnd(CDC* pDC)
{
    // Return TRUE to indicate further erasing is not needed.
    return TRUE;
}

void CGroupLine::OnPaint()
{
    CPaintDC dc(this);

    DrawAll(&dc);
}

void CGroupLine::OnSize(UINT nType, int cx, int cy)
{
    CWnd::OnSize(nType, cx, cy);
}

void CGroupLine::DrawAll(CDC* pDC)
{
    CRect rectClient;
    GetClientRect(&rectClient);

    if ( rectClient.Width() == 0 || rectClient.Height() == 0 )
        return;

    COLORREF crBack  = ::GetSysColor(COLOR_BTNFACE);
    COLORREF crLight = ::GetSysColor(COLOR_BTNHILIGHT);
    COLORREF crDark  = ::GetSysColor(COLOR_BTNSHADOW);

    if ( rectClient.Width() < 2 || rectClient.Height() < 2 )
    {
        pDC->FillSolidRect(&rectClient, crBack);
        return;
    }
    
    CPen penLight, penDark;
    if ( !penLight.CreatePen(PS_SOLID, 0, crLight) )
        return;
    if ( !penDark.CreatePen(PS_SOLID, 0, crDark) )
        return;

    // Record old DC state.
    CPen* pOldPen = pDC->SelectObject(&penLight);

    // Draw the group box side.
    if ( m_isLeft )
    {
        DrawLeftSide(pDC, &penLight, &penDark, rectClient.left, rectClient.top, rectClient.Height());
    }
    else if ( m_isTop )
    {
        CDC memoryDC;
        if ( memoryDC.CreateCompatibleDC(pDC) )
        {
            // Only create a new bitmap if the existing one is too small.
            BOOL bitmapReady = TRUE;
            if ( m_bitmapSize.cx < rectClient.Width() || m_bitmapSize.cy < rectClient.Height() )
            {
                delete m_bitmap;
                m_bitmap = new CBitmap;
                bitmapReady = m_bitmap->CreateCompatibleBitmap(pDC, rectClient.Width(), rectClient.Height());
                m_bitmapSize = CSize(rectClient.Width(), rectClient.Height());
            }

            if ( bitmapReady )
            {
                CBitmap* pOldBitmap = memoryDC.SelectObject(m_bitmap);
    
                memoryDC.FillSolidRect(rectClient.left, rectClient.top, rectClient.Width(), TEXT_RECT_HEIGHT, crBack);
                DrawTopSide(&memoryDC, &penLight, &penDark, rectClient.left, rectClient.top, rectClient.Width(), m_text);

                pDC->BitBlt(rectClient.left, rectClient.top, rectClient.Width(), rectClient.Height(),
                    &memoryDC, 0, 0, SRCCOPY);

                memoryDC.SelectObject(pOldBitmap);
            }
            else
            {
                delete m_bitmap;
                m_bitmap = NULL;
                m_bitmapSize = CSize(0,0);
            }
        }
    }
    else if ( m_isRight )
    {
        DrawRightSide(pDC, &penLight, &penDark, rectClient.left, rectClient.top, rectClient.Height());
    }
    else if ( m_isBottom )
    {
        DrawBottomSide(pDC, &penLight, &penDark, rectClient.left, rectClient.top, rectClient.Width());
    }

    // Restore old DC state.
    pDC->SelectObject(pOldPen);
}

// CGroupBox /////////////////////////////////////////////////////////////////

IMPLEMENT_DYNAMIC(CGroupBox,CWnd)

BEGIN_MESSAGE_MAP(CGroupBox,CWnd)
    //{{AFX_MSG_MAP(CGroupBox)
    ON_WM_CREATE()
    ON_WM_SHOWWINDOW()
    ON_WM_SIZE()
    //}}AFX_MSG_MAP
    ON_MESSAGE(CHECK_REPOSITION_MESSAGE, OnCheckReposition)
END_MESSAGE_MAP()

CGroupBox::CGroupBox()
{
    m_text = "";

    m_leftLine   = new CGroupLine;
    m_topLine    = new CGroupLine;
    m_rightLine  = new CGroupLine;
    m_bottomLine = new CGroupLine;
}

CGroupBox::~CGroupBox()
{
    delete m_leftLine;
    m_leftLine = NULL;
    delete m_topLine;
    m_topLine = NULL;
    delete m_rightLine;
    m_rightLine = NULL;
    delete m_bottomLine;
    m_bottomLine = NULL;
}

void CGroupBox::SetText(const CString& text)
{
    m_text = text;
    m_topLine->SetText(text);
    Reposition();
}

const CString& CGroupBox::GetText() const
{
    return m_text;
}

BOOL CGroupBox::PreCreateWindow(CREATESTRUCT& cs)
{
    // Make the groupbox transparent.
    cs.dwExStyle |= WS_EX_TRANSPARENT;

    return CWnd::PreCreateWindow(cs);
}

int CGroupBox::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
    CRect rectClient;
    rectClient.SetRect(lpCreateStruct->x, lpCreateStruct->y,
                       lpCreateStruct->x+lpCreateStruct->cx,
                       lpCreateStruct->y+lpCreateStruct->cy);

    if ( !CreateLeftLine(rectClient) )
        return -1;

    if ( !CreateTopLine(rectClient) )
        return -1;

    if ( !CreateRightLine(rectClient) )
        return -1;

    if ( !CreateBottomLine(rectClient) )
        return -1;

    return 0;
}

void CGroupBox::OnShowWindow(BOOL bShow, UINT nStatus)
{
    int nCmdShow;
    if ( bShow )
        nCmdShow = SW_SHOW;
    else
        nCmdShow = SW_HIDE;

    if ( bShow )
        Reposition();

    if ( ::IsWindow(m_leftLine->m_hWnd) )
        m_leftLine->ShowWindow(nCmdShow);

    if ( ::IsWindow(m_topLine->m_hWnd) )
        m_topLine->ShowWindow(nCmdShow);

    if ( ::IsWindow(m_rightLine->m_hWnd) )
        m_rightLine->ShowWindow(nCmdShow);

    if ( ::IsWindow(m_bottomLine->m_hWnd) )
        m_bottomLine->ShowWindow(nCmdShow);

    SetWindowPos(&wndBottom, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOREDRAW);
}

void CGroupBox::OnSize(UINT nType, int cx, int cy)
{
    CWnd::OnSize(nType, cx, cy);

    Reposition();

    PostMessage(CHECK_REPOSITION_MESSAGE, 0, 0);
}

LRESULT CGroupBox::OnCheckReposition(WPARAM wParam, LPARAM lParam)
{
    // Check to make sure the groupline windows are within
    // the groupbox boundaries. If not, we issue a reposition.

    CRect rectBox;
    GetWindowRect(&rectBox);

    if ( m_leftLine != NULL && ::IsWindow(m_leftLine->m_hWnd) )
    {
        CRect rectLine;
        m_leftLine->GetWindowRect(&rectLine);

        if ( !rectBox.PtInRect(CPoint(rectLine.left, rectLine.top)) )
        {
            Reposition();
            return 0;
        }
    }

    if ( m_bottomLine != NULL && ::IsWindow(m_bottomLine->m_hWnd) )
    {
        CRect rectLine;
        m_bottomLine->GetWindowRect(&rectLine);

        if ( !rectBox.PtInRect(CPoint(rectLine.right-1, rectLine.bottom-1)) )
        {
            Reposition();
            return 0;
        }
    }

    return 0;
}

void CGroupBox::CalcLeftRect(const CRect& rectClient, CRect& rect)
{
    int offset = 2 + TOP_OFFSET;

    rect.SetRect(rectClient.left, rectClient.top+offset,
                 rectClient.left+2, rectClient.bottom-2);
    
    if ( rectClient.Width() < 2 )
        rect.right = rect.left;
    if ( rect.top > rect.bottom )
        rect.top = rect.bottom;
}

void CGroupBox::CalcTopRect(const CRect& rectClient, CRect& rect)
{
    if ( m_text.IsEmpty() )
    {
        rect.SetRect(rectClient.left+2, rectClient.top+2,
                     rectClient.right-2, rectClient.top+2+TOP_OFFSET+2);
    }
    else
    {
        rect.SetRect(rectClient.left+2, rectClient.top+2,
                     rectClient.right-2, rectClient.top+2+TEXT_RECT_HEIGHT);
    }

    if ( rect.left > rect.right )
        rect.right = rect.left;
}

void CGroupBox::CalcRightRect(const CRect& rectClient, CRect& rect)
{
    int offset = 2 + TOP_OFFSET;

    rect.SetRect(rectClient.right-2, rectClient.top+offset,
                 rectClient.right, rectClient.bottom-2);

    if ( rectClient.Width() < 2 )
        rect.left = rect.right;
    if ( rect.top > rect.bottom )
        rect.top = rect.bottom;
}

void CGroupBox::CalcBottomRect(const CRect& rectClient, CRect& rect)
{
    rect.SetRect(rectClient.left, rectClient.bottom-2,
                 rectClient.right, rectClient.bottom);

    if ( rectClient.Height() < 2 )
        rect.top = rect.bottom;
}

bool CGroupBox::CreateLeftLine(const CRect& rectClient)
{
    CWnd* parentWnd = GetParent();
    if ( parentWnd == NULL )
        return false;

    CRect rect;
    CalcLeftRect(rectClient, rect);

    m_leftLine->SetLeft();
    if ( !m_leftLine->Create(NULL, _T("CGroupLine"),
                             WS_CHILD | WS_VISIBLE,
                             rect,
                             parentWnd,
                             IDC_STATIC) )
    {
        return false;
    }

    return true;
}

bool CGroupBox::CreateTopLine(const CRect& rectClient)
{
    CWnd* parentWnd = GetParent();
    if ( parentWnd == NULL )
        return false;

    CRect rect;
    CalcTopRect(rectClient, rect);

    m_topLine->SetTop();
    if ( !m_topLine->Create(NULL, _T("CGroupLine"),
                            WS_CHILD | WS_VISIBLE,
                            rect,
                            parentWnd,
                            IDC_STATIC) )
    {
        return false;
    }

    return true;
}

bool CGroupBox::CreateRightLine(const CRect& rectClient)
{
    CWnd* parentWnd = GetParent();
    if ( parentWnd == NULL )
        return false;

    CRect rect;
    CalcRightRect(rectClient, rect);

    m_rightLine->SetRight();
    if ( !m_rightLine->Create(NULL, _T("CGroupLine"),
                              WS_CHILD | WS_VISIBLE,
                              rect,
                              parentWnd,
                              IDC_STATIC) )
    {
        return false;
    }

    return true;
}

bool CGroupBox::CreateBottomLine(const CRect& rectClient)
{
    CWnd* parentWnd = GetParent();
    if ( parentWnd == NULL )
        return false;

    CRect rect;
    CalcBottomRect(rectClient, rect);

    m_bottomLine->SetBottom();
    if ( !m_bottomLine->Create(NULL, _T("CGroupLine"),
                               WS_CHILD | WS_VISIBLE,
                               rect,
                               parentWnd,
                               IDC_STATIC) )
    {
        return false;
    }

    return true;
}

void CGroupBox::Reposition()
{
    if ( !::IsWindow(m_hWnd) )
        return;

    // Get the client rect in screen coords.
    CRect rectScreen;
    GetClientRect(&rectScreen);
    ClientToScreen(&rectScreen);

    // Use the parent wnd to convert screen coords to client coords.
    CWnd* parentWnd = GetParent();
    if ( parentWnd == NULL )
        return;
    CRect rectBox = rectScreen;
    parentWnd->ScreenToClient(&rectBox);

    // Reposition each side.
    CRect rectNew;
    UINT swpFlags = SWP_NOACTIVATE | SWP_NOZORDER;
    if ( ::IsWindow(m_bottomLine->m_hWnd) )
    {
        CalcLeftRect(rectBox, rectNew);
        m_leftLine->SetWindowPos(NULL, rectNew.left, rectNew.top, rectNew.Width(), rectNew.Height(), swpFlags);
        
        CalcTopRect(rectBox, rectNew);
        m_topLine->SetWindowPos(NULL, rectNew.left, rectNew.top, rectNew.Width(), rectNew.Height(), swpFlags);  
        
        CalcRightRect(rectBox, rectNew);
        m_rightLine->SetWindowPos(NULL, rectNew.left, rectNew.top, rectNew.Width(), rectNew.Height(), swpFlags);
        
        CalcBottomRect(rectBox, rectNew);
        m_bottomLine->SetWindowPos(NULL, rectNew.left, rectNew.top, rectNew.Width(), rectNew.Height(), swpFlags);
    }
}

// END

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions