// 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