没有控件库,是种痛苦;用wince上现成的控件,更是种痛苦。能显示图片的按钮有木有?!木有。菜单能不能支持图标?不能!列表能不能漂亮点?咋就这样,爱用不用!想换个平台,没办法!公司指定非用wince不可,就是不愿跟上技术的步法,只能看着其他的公司happy的进行着ios,android的开发。程序猿(媛)们怎么办?
这也算是本人的痛苦经历了,怎么办?只能自绘呗!这次先说说自绘按钮的事吧。最终的效果如下图所示,其中左边的按钮是能够显示多种状态的图像,而右边的按钮是通过图像创建了圆形的形状,同时也能够显示多种状态的图像。此外图像和文字的对齐方式也是可以设置的。
实现步骤
首先需要从CButton派生一个子类,比如CImageButton。然后实现自绘,必须通知按钮“hi,你不要绘制了,这交给我了”!,这可以通过覆盖父类的虚函数PreSubclassWindow,在其实现体中调用ModifyStyle函数来通知。具体参见下面的代码。
void CImageButton::PreSubclassWindow()
{
CButton::PreSubclassWindow();
// add owner draw styles
ModifyStyle(0, BS_OWNERDRAW);
}
之后,每次需要绘制按钮的时候,都会调用虚函数virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct),因而所有的绘制工作都需要放在这个函数的实现当中,这也是实现自绘按钮的主要工作所在,必须根据按钮的当前状态、大小、对齐方式、文字和图像大小等等来绘制。
自绘细节
- 从DrawItem的参数lpDrawItemStruct获取按钮状态、大小、绘图句柄。
- 根据按钮的状态,选择合适的图像以及调整绘制区域。
- 根据按钮的状态,绘制按钮的形状以及图像。
- 根据按钮的状态,绘制文字。
void CImageButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
ASSERT(lpDrawItemStruct != NULL);
// must have at least the first bitmap loaded before calling DrawItem
ASSERT(m_bitmapNorm.m_hObject != NULL); // required
//Get button state
BOOL isFocus = lpDrawItemStruct->itemState & ODS_FOCUS;
BOOL isPush = lpDrawItemStruct->itemState & ODS_SELECTED;
if (m_hMenu && m_isMenuDisplayed)
{
isPush = TRUE;
}
BOOL isDisable = lpDrawItemStruct->itemState & ODS_DISABLED;
//Get button rect
CRect btnRect(lpDrawItemStruct->rcItem);
CRect frameControlRect(btnRect);
int drawOffSet = 0;
// get GDI resource: font,CDC,bitmap...
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CBitmap* pBitmap = &m_bitmapNorm;
if (isDisable)
{
if (m_bitmapDisabled.m_hObject != NULL)
{
pBitmap = &m_bitmapDisabled;
}
}
if (isPush)
{
if (m_bitmapSel.m_hObject != NULL)
{
pBitmap = &m_bitmapSel;
}
if (!m_isCreateRgnFromBitmap)
{
drawOffSet = 1;
}
}
else if (isFocus)
{
if (m_bitmapFocus.m_hObject != NULL)
{
pBitmap = &m_bitmapFocus;
}
frameControlRect.DeflateRect(1,1);
}
// if don't use bitmap as rgn and disable, draw button face and frame rect
if (!m_isCreateRgnFromBitmap && !isDisable)
{
// fill button face with default system button face color
pDC->FillRect( btnRect , &CBrush( GetSysColor( COLOR_BTNFACE ) ) );
if (!isPush)
{
pDC->DrawFrameControl(frameControlRect,DFC_BUTTON,DFCS_BUTTONPUSH);
}
pDC->FrameRect(btnRect,CBrush::FromHandle((HBRUSH)GetStockObject(BLACK_BRUSH)));
}
// when button is disabled, if have not disable image,just fill image rect like text.
// otherwise, use disabled image to fill this rect
if (isDisable && m_bitmapDisabled.m_hObject == NULL)
{
//pDC->DrawState(CPoint(m_imageRect.left,m_imageRect.top),CSize(m_bitmap.bmWidth,m_bitmap.bmHeight),pBitmap,DST_BITMAP|DSS_DISABLED));
CRect imageRect(m_imageRect);
imageRect.OffsetRect(1,1);
pDC->FillRect(imageRect,&CBrush(GetSysColor(COLOR_WINDOW)));
imageRect.OffsetRect(-1,-1);
pDC->FillRect(imageRect,&CBrush(GetSysColor(COLOR_GRAYTEXT)));
}
else
{
CDC memDC;
memDC.CreateCompatibleDC(pDC);
CBitmap* pOld = memDC.SelectObject(pBitmap);
if (pOld == NULL)
return;
CRect imageRect(m_imageRect);
imageRect.OffsetRect(drawOffSet,0);
pDC->BitBlt(imageRect.left, imageRect.top, imageRect.Width(), imageRect.Height(),&memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOld);
memDC.DeleteDC();
}
CString text;
GetWindowText(text);
if (!text.IsEmpty())
{
if (isDisable)
{
CRect textRect(m_textRect);
textRect.OffsetRect(1,1);
pDC->SetTextColor(GetSysColor(COLOR_WINDOW));
int oldMode = pDC->SetBkMode(TRANSPARENT);
CFont* pOldFont = pDC->SelectObject(GetTextFont());
pDC->DrawText(text,textRect,DT_WORDBREAK| DT_CENTER|DT_VCENTER);
textRect.OffsetRect(-1,-1);
pDC->SetTextColor(GetSysColor(COLOR_GRAYTEXT));
pDC->DrawText(text,textRect,DT_WORDBREAK | DT_CENTER|DT_VCENTER);
if (pOldFont)
{
pDC->SelectObject(pOldFont);
}
pDC->SetBkMode(oldMode);
}
else
{
CRect textRect(m_textRect);
textRect.OffsetRect(drawOffSet,0);
pDC->SetTextColor(m_textColor);
int oldMode = pDC->SetBkMode(TRANSPARENT);
CFont* pOldFont = pDC->SelectObject(GetTextFont());
pDC->DrawText(text,textRect,DT_WORDBREAK | DT_CENTER|DT_VCENTER);
if (pOldFont)
{
pDC->SelectObject(pOldFont);
}
pDC->SetBkMode(oldMode);
}
}
if (!isDisable && isFocus && !m_isCreateRgnFromBitmap)
{
CRect focusRect(btnRect);
focusRect.DeflateRect(2,2);
focusRect.OffsetRect(drawOffSet,0);
pDC->DrawFocusRect(focusRect);
}
}
这当中有几个函数比较不常见,
DrawFrameControl
、
FrameRect
、
DrawFocusRect,
调整相应的参数就可以看出其绘制的内容了。
依据文字长度和图片大小自动调整按钮大小(SizeToContent)
自动调整大小,首先要计算出文字和图片所在空间大小,以及它们的对齐方式,之后就是简单的计算了。这里要强调的如何计算文字所占的空间大小,至于按照对齐方式计算可以查看源代码。
据了解,至少有2种方法可以计算出文字大小,这里介绍其中一种。这主要用到函数CDC::DrawText,代码如下。将其第三个参数设为DT_CALCRECT|DT_WORDBREAK,那么就可以通过第二个参数获得相应的文字大小。其中DT_CALCRECT表示此次调用不向设备输出(即不会真的绘制到屏幕上),只是计算文字大小;DT_WORDBREAK表示文字可自动换行。
CDC* pDC = GetDC();
if (pDC)
{
GetWindowText(text);
textRect.SetRectEmpty();
if (!text.IsEmpty())
{
CFont* pOldFont = pDC->SelectObject(GetTextFont());
pDC->DrawText(text,&textRect,DT_CALCRECT|DT_WORDBREAK);
pDC->SelectObject(pOldFont);
ReleaseDC(pDC);
}
}
创建不规则按钮
创建不规则按钮是通过掩码图片和颜色计算得出按钮的不规则区域,然后用新的区域来设置按钮的窗口区域及clip区域,详见下面的示例:
m_hClipRgn = CreateRgnFromBitmap(m_bitmapMask,RGB(255,255,255));
if (m_hClipRgn)
{
SetWindowRgn(m_hClipRgn,TRUE);
CDC* pDC = GetDC();
if (pDC)
{
SelectClipRgn(pDC->GetSafeHdc(),m_hClipRgn);
ReleaseDC(pDC);
}
}
计算区域的代码都放在了函数CreateRgnFromBitmap,这个是从codeproject的文章CxSkinButton里面“盗版”过来的,也没有细看。有兴趣的可以自己研究并分享下。
如何使用
// 图片按钮
m_imageButton.LoadBitmaps(IDB_INFO_BITMAP,IDB_INFO_PUSH_BITMAP,IDB_INFO_FOCUS_BITMAP);
m_imageButton.SetAlignStyle(AS_IMAGE_LEFT_TEXT_RIGHT);
m_imageButton.SizeToContent();
m_skinBtn.SizeToContent();
// 不规则按钮
m_skinBtn.LoadBitmaps(IDB_CIRCLE_NORM_BITMAP,IDB_CIRCLE_DOWN_BITMAP,IDB_CIRCLE_FOCUS_BITMAP,IDB_CIRCLE_DISABLE_BITMAP,IDB_CIRCLE_MASK_BITMAP);
m_skinBtn.SizeToContent();
源文件下载地址:
http://download.youkuaiyun.com/detail/ryanzll/4518230