Introduction
As mentioned by Jensen Harris, many applications tend to steal screen real-estate from the user by adding a status bar for the sole reason of getting a resize “grippie”:
This article is dedicated to eating a pie while keeping it whole. I will show you how to add a “grippie” without using a status bar.
Creating the “grippie”
The first surprise is that a grippie is in fact a scroll bar. Well, at least, it was a surprise for me. Since I am fond of MFC for UI jobs, I am choosing an MFC dialog application to show the trick. First of all, I set my dialog border style to “Resizing
”; otherwise, why bother with “grippie” at all? In my dialog, I add a new member variable of type CScrollBar
, like this:
#pragma once
class CGrippieTestDlg : public CDialog
{
public:
CGrippieTestDlg(CWnd* pParent = NULL);
enum { IDD = IDD_GRIPPIETEST_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
protected:
HICON m_hIcon;
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnBnClickedOk();
private:
CScrollBar m_grippie;
};
Now, I add a handler for the dialog’s WM_CREATE
message and the MFC wizard provides me with the handler OnCreate(LPCREATESTRUCT lpCreateStruct)
in my dialog class. I add the following code in the handler to create my “grippie”:
#define GRIPPIE_SQUARE_SIZE 11
int CGrippieTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
CRect clientRect;
GetClientRect(&clientRect);
CRect grippieRect;
grippieRect.right=clientRect.right;
grippieRect.bottom=clientRect.bottom;
grippieRect.left=clientRect.right-GRIPPIE_SQUARE_SIZE;
grippieRect.top=clientRect.bottom-GRIPPIE_SQUARE_SIZE;
m_grippie.Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP|WS_CLIPSIBLINGS,
grippieRect, this, 0);
return 0;
}
Here we are with a nice “grippie”, and it works fine. But wait! The damn thing stays in the same place when you resize the window. Well, no problem, we are going to fix that.
Moving the grippie on window resize
We will fix it by adding a handler for the dialog’s WM_SIZE
event:
void CGrippieTestDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
CRect clientRect;
GetClientRect(&clientRect);
if(m_grippie.m_hWnd!=NULL)
m_grippie.SetWindowPos(NULL,
clientRect.right-GRIPPIE_SQUARE_SIZE,
clientRect.bottom-GRIPPIE_SQUARE_SIZE,
GRIPPIE_SQUARE_SIZE,
GRIPPIE_SQUARE_SIZE,
SWP_NOZORDER|SWP_SHOWWINDOW);
}
Now, let’s wrap it up in a nice class.
CGrippie class
I will add a class to my project that is derived from CScrollBar
and is called CGrippie
:
#pragma once
#define GRIPPIE_SQUARE_SIZE 11
class CGrippie : public CScrollBar
{
DECLARE_DYNAMIC(CGrippie)
CGrippie(const CGrippie& other);
void operator=(const CGrippie& other);
public:
CGrippie();
virtual ~CGrippie();
protected:
DECLARE_MESSAGE_MAP()
public:
BOOL Create(CWnd* parent);
void OnParentSize(void);
private:
CWnd* m_parent;
};
Here is the implementation:
#include "stdafx.h"
#include "GrippieTest.h"
#include "Grippie.h"
IMPLEMENT_DYNAMIC(CGrippie, CScrollBar)
CGrippie::CGrippie()
{
m_parent=NULL;
}
CGrippie::~CGrippie()
{
}
BEGIN_MESSAGE_MAP(CGrippie, CScrollBar)
END_MESSAGE_MAP()
BOOL CGrippie::Create(CWnd* parent)
{
m_parent=parent;
if(m_parent==NULL)
return FALSE;
CRect clientRect;
m_parent->GetClientRect(&clientRect);
CRect grippieRect;
grippieRect.right=clientRect.right;
grippieRect.bottom=clientRect.bottom;
grippieRect.left=clientRect.right-GRIPPIE_SQUARE_SIZE;
grippieRect.top=clientRect.bottom-GRIPPIE_SQUARE_SIZE;
return CScrollBar::Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP,
grippieRect, m_parent, 0);
}
void CGrippie::OnParentSize(void)
{
if(m_parent==NULL)
return;
CRect clientRect;
m_parent->GetClientRect(&clientRect);
if(m_hWnd!=NULL)
SetWindowPos(NULL, clientRect.right-GRIPPIE_SQUARE_SIZE,
clientRect.bottom-GRIPPIE_SQUARE_SIZE,
GRIPPIE_SQUARE_SIZE, GRIPPIE_SQUARE_SIZE,
SWP_NOZORDER|SWP_SHOWWINDOW);
}
Now, all we need is to change the type of the m_grippie
variable in the dialog class to CGrippie
and change the implementation of the dialog message handlers like this:
int CGrippieTestDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialog::OnCreate(lpCreateStruct) == -1)
return -1;
m_grippie.Create(this);
return 0;
}
void CGrippieTestDlg::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
m_grippie.OnParentSize();
}
Well, in case you are using VS2005, you are done now, but for VS2003 code, there is still a small thing to be handled: that is the mouse pointer shape when it is in the grippie area, but not in the bottom-right corner of the dialog. The shape should be like this: . But instead, it is a regular mouse pointer.
Handling the mouse pointer in VS2003
To fix the problem, I am going to add a handler for the WM_SETCURSOR
message. To do so, I am going to add an event to the message map:
BEGIN_MESSAGE_MAP(CGrippie, CScrollBar)
ON_WM_SETCURSOR()
END_MESSAGE_MAP()
Override the parent method OnSetCursor
in CGrippie
with the following code:
BOOL CGrippie::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message)
{
if ( pWnd == this && nHitTest == HTCLIENT )
{
::SetCursor( ::LoadCursor( NULL, IDC_SIZENWSE ) );
return( TRUE );
}
return CScrollBar::OnSetCursor(pWnd, nHitTest, message);
}
Well, that is it! We have a grippie in the dialog without that status bar!