一直用VC6来写程序,也做过不少小的工具,有一天哥们让我帮他写个小东西,很快帮他搞定,他拿到手一看,OH MY GOD!怎么这么难看!
这句话让我很不爽,程序嘛,能实现功能就可以了,给你留出输入接口,你点击一个按钮,程序自动给你计算,然后把结果返还给你,这就是程序的使命嘛,当然我是这样认为的。
哥们的一句话,伤了我的心,点开我写的程序,OH MY GOD!越看越丑!简直到了让人无法容忍的地步,再效率的软件也会因为这样的界面大打折扣。
可是怎样去写自己的界面呢,完全不知道如何下手,查资料说,有一个好的方法,重写自己的函数。柿子要拣软的捏,我就先从最常见最简单的按钮下手。
一、理论依据
既然需要去公有继承CButton类,我就需要先知道这个CButton是怎么回事,都是什么属性和方法,MSDN和度娘是个好东西,首先看一下继承图表:CObject -> CCmdTarget -> CWnd -> CButton
简单的捋一捋,CButton是CWnd派生来的,那么按钮实质上就是一个窗口,然后CButton类赋予了按钮特有的属性,那么我在自己写一个类,从CButton派生,然后写自己的方法来实现绘制控件,理论上是行得通的。按钮控件可以直接从工具栏上拖动创建,也可以在代码中通过Create函数去创建,顺序是先调用CButton的构造函数来初始化一个CButton对象,然后调用Create函数去设定CButton对象的各种属性并用到该对象上。
CButton::Create 函数原型:主要用于在代码中动态创建按钮
BOOL Create( LPCTSTR lpszCaption, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID );
lpszCaption是按钮上显示的文本;
dwStyle指定按钮风格,可以是按钮类型与窗口风格的组合
rect 指定按钮的大小和初始化位置;
pParentWnd指示拥有按钮的父窗口,不能为NULL;
nID指定与按钮关联的ID号,很重要,用于消息响应。
返回值:若成功,返回非0;否则返回0
CButton::PreSubclassWindow() //CWnd类的虚函数
CButton::DrawItem 重载该函数绘制一个CButton对象,由框架调用该函数,我们实现自绘按钮,就是通过重载这个函数实现的。
二、代码编写
步骤:
1、启动VC6,创建一个基于对话框的应用程序,此处太简单就不截图了
2、随便拖动几个按钮出来,不写任何代码,编译运行,如图:
看一下VC6自动生成的界面,按钮很丑吧,鼠标滑过没任何变化,仅在按钮获得焦点时多一个虚线框,丑,简直丑的难以容忍。
3、利用向导(CTRL+W)添加一个类,基类选择CButton,工程会自动添加一个头文件:MyButton.h 一个实现文件:MyButton.cpp
文章最后附上工程文件,有兴趣的可以下载看看,按自己喜欢的样式修改。
// MyButton.h 代码 添加自己的成员变量、重写方法 //重绘风格按钮,由CButton公有派生 class CMyButton : public CButton { public: CMyButton(); protected: //按钮的外边框 CPen m_BoundryPen; //光标滑过时按钮的内边框 CPen m_InsideBoundryPenLeft; CPen m_InsideBoundryPenRight; CPen m_InsideBoundryPenTop; CPen m_InsideBoundryPenBottom; //按钮获得焦点时按钮的内边框 CPen m_InsideBoundryPenLeftSel; CPen m_InsideBoundryPenRightSel; CPen m_InsideBoundryPenTopSel; CPen m_InsideBoundryPenBottomSel; //按钮的底色,包括有效和无效两种状态 CBrush m_FillActive; CBrush m_FillInactive; CBrush m_FillOver; //光标滑过时,按钮背景色 //按钮的状态 BOOL m_bOver; //鼠标位于按钮之上时该值为true,反之为flase BOOL m_bTracking; //在鼠标按下没有释放时该值为true BOOL m_bSelected; //按钮被按下时该值为true BOOL m_bFocus; //按钮获得当前焦点时为true protected: virtual void PreSubclassWindow(); //这个可以动态设置控件,很重要 public: virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); virtual void DoGradientFill(CDC *pDC, CRect* rect); virtual void DrawInsideBorder(CDC *pDC, CRect* rect); virtual ~CMyButton(); protected: //主要是处理鼠标消息 //{{AFX_MSG(CMyButton) afx_msg void OnMouseMove(UINT nFlags, CPoint point); afx_msg LRESULT OnMouseLeave(WPARAM wParam, LPARAM lParam); afx_msg LRESULT OnMouseHover(WPARAM wParam, LPARAM lParam); afx_msg BOOL OnEraseBkgnd(CDC* pDC); //}}AFX_MSG DECLARE_MESSAGE_MAP() }; // MyButton.cpp //构造函数,初始化各画刷数据 CMyButton::CMyButton() { //按钮外边框 宽度 颜色 m_BoundryPen.CreatePen(PS_INSIDEFRAME | PS_SOLID, 1, RGB(148,171,189)); //光标滑过内边框颜色 m_InsideBoundryPenLeft.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(128,214,255)); m_InsideBoundryPenRight.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(63,149,200)); m_InsideBoundryPenTop.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(128,214,255)); m_InsideBoundryPenBottom.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(63,149,200)); //按钮背景颜色,可用、置灰 m_FillActive.CreateSolidBrush(RGB(249,250,252)); //可用状态按钮颜色 m_FillInactive.CreateSolidBrush(RGB(235, 234, 216)); //不可用/置灰 状态按钮颜色 // m_FillOver.CreateSolidBrush(RGB(175,227,251)); //鼠标经过时,按钮颜色 m_FillOver.CreateSolidBrush(RGB(46,168,241)); //鼠标经过时,按钮颜色 //当按钮获得焦点时,按钮边框 m_InsideBoundryPenLeftSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(153, 198, 252)); m_InsideBoundryPenTopSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(162, 201, 255)); m_InsideBoundryPenRightSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 3, RGB(162, 189, 252)); m_InsideBoundryPenBottomSel.CreatePen(PS_INSIDEFRAME | PS_SOLID, 2, RGB(162, 201, 255)); //初始化各状态为 FALSE m_bOver = m_bSelected = m_bTracking = m_bFocus = FALSE; } //析构函数:删除画刷对象,释放资源 CMyButton::~CMyButton() { m_BoundryPen.DeleteObject(); m_InsideBoundryPenLeft.DeleteObject(); m_InsideBoundryPenRight.DeleteObject(); m_InsideBoundryPenTop.DeleteObject(); m_InsideBoundryPenBottom.DeleteObject(); m_FillActive.DeleteObject(); m_FillInactive.DeleteObject(); m_FillOver.DeleteObject(); m_InsideBoundryPenLeftSel.DeleteObject(); m_InsideBoundryPenTopSel.DeleteObject(); m_InsideBoundryPenRightSel.DeleteObject(); m_InsideBoundryPenBottomSel.DeleteObject(); } //消息映射表 BEGIN_MESSAGE_MAP(CMyButton, CButton) //{{AFX_MSG_MAP(CMyButton) ON_WM_MOUSEMOVE() ON_MESSAGE(WM_MOUSELEAVE, OnMouseLeave) ON_MESSAGE(WM_MOUSEHOVER, OnMouseHover) ON_WM_ERASEBKGND() //}}AFX_MSG_MAP END_MESSAGE_MAP() / // CMyButton message handlers //添加Owner Draw属性 void CMyButton::PreSubclassWindow() //CWnd类的虚函数 { //框架调用这个成员函数以允许在窗口被子类化之前进行其它必要的子类化。 //重载这个函数以允许控件的动态子类化。这是个高级可重载函数。 CButton::PreSubclassWindow(); //调用这个函数修改窗口的风格, //此函数的厉害之处在于可以在窗口创建完成后修改窗口风格,虽然也有一些属性改不了。 //修改按钮为自绘风格,很重要!!! ModifyStyle(0, BS_OWNERDRAW); //设置按钮的有效区域 CRgn rgn; //圆角矩形 CRect rc; //直角矩形 GetClientRect(&rc); //获取按钮的区域,只重绘按钮区域 //有效区域为一个角半径为30的圆角矩形,后2个参数指定圆角大小 rgn.CreateRoundRectRgn(rc.left,rc.top,rc.right,rc.bottom,30,30); //SetWindowRgn 函数是设置了一个圆角窗口的区域. //只有被包含在这个区域内的地方才会被重绘,而不包含在区域内的其他区域系统将不会显示. SetWindowRgn(rgn,TRUE); rgn.DeleteObject(); } //鼠标移动消息 void CMyButton::OnMouseMove(UINT nFlags, CPoint point) { if (!m_bTracking) //左键按下,并释放时 { TRACKMOUSEEVENT tme; tme.cbSize = sizeof(tme); tme.hwndTrack = m_hWnd; tme.dwFlags = TME_LEAVE | TME_HOVER; tme.dwHoverTime = 1; m_bTracking = _TrackMouseEvent(&tme); } CButton::OnMouseMove(nFlags, point); } //光标移出按钮区域 LRESULT CMyButton::OnMouseLeave(WPARAM wParam, LPARAM lParam) { m_bOver = FALSE; m_bTracking = FALSE; InvalidateRect(NULL, FALSE); //使矩形区域无效,重绘 return 0; } //有鼠标消息,则重绘按钮区域 LRESULT CMyButton::OnMouseHover(WPARAM wParam, LPARAM lParam) { m_bOver = TRUE; InvalidateRect(NULL); return 0; } /* LPDRAWITEMSTRUCT 结构体包含在WINUSER.H中 //为需要自绘的控件提供必要的信息, //对应消息:WM_DRAWITEM //该结构是由收到的WM_DRAWITEM消息的lParam参数提供的 typedef struct tagDRAWITEMSTRUCT { UINT CtlType; //控件类型 ODT_BUTTON - 按钮 ODT_COMBOBOX - 组合框 ODT_LISTBOX - 列表框 //ODT_MENU - 菜单项 ODT_STATIC - 静态文本控件 ODT_TAB - tab控件 UINT CtlID; //自绘控件ID,菜单项不需要此成员 UINT itemID; //菜单项ID、列表框或组合框中某项的索引值 UINT itemAction; //指定绘制行为 ODA_DRAWNTIRE - 整个控件被绘制 //ODA_FOCUS - 获得或失去焦点时绘制 ODA_SELECT - 选中状态绘制 UINT itemState; //绘制完成后,控件的可见状态 //ODS_HOTLIGHT - 光标位于控件之上,控件高亮颜色显示 HWND hwndItem; //组合框、列表框和按钮等控件的句柄,如为菜单项,则为菜单句柄 HDC hDC; //绘制操作所使用的设备环境 RECT rcItem; //将被绘制的控件矩形区域 DWORD itemData; //用于菜单项、列表框、组合框等 } DRAWITEMSTRUCT, NEAR *PDRAWITEMSTRUCT, FAR *LPDRAWITEMSTRUCT; */ void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { //从lpDrawItemStruct获取控件的相关信息 CRect rect = lpDrawItemStruct->rcItem; CDC *pDC = CDC::FromHandle(lpDrawItemStruct->hDC); //从句柄中获得dc int nSaveDC = pDC->SaveDC(); UINT state = lpDrawItemStruct->itemState; POINT pt ; TCHAR strText[MAX_PATH + 1]; //按钮控件caption,WINDEF.H : MAX_PATH 260 ::GetWindowText(m_hWnd, strText, MAX_PATH); //画按钮的外边框,它是一个半径为5的圆角矩形 pt.x = 5; pt.y = 5; CPen* hOldPen = pDC->SelectObject(&m_BoundryPen); pDC->RoundRect(&rect, pt); //获取按钮的状态 if (state & ODS_FOCUS) //被选中、获取焦点时 { m_bFocus = TRUE; m_bSelected = TRUE; } else { m_bFocus = FALSE; m_bSelected = FALSE; } if (state & ODS_SELECTED || state & ODS_DEFAULT) { m_bFocus = TRUE; } pDC->SelectObject(hOldPen); //获取系统绘制控件时的默认尺寸,SM_CXEDGE - Dimensions, in pixels, of a 3-D border. //These are the 3-D counterparts of SM_CXBORDER and SM_CYBORDER. rect.DeflateRect(CSize(GetSystemMetrics(SM_CXEDGE), GetSystemMetrics(SM_CYEDGE))); //根据按钮的状态填充按钮的底色 CBrush* pOldBrush; if (m_bOver) //光标位于控件之上 { //鼠标经过绘制内边框或者填充底色,可从下面选择 //鼠标经过时,填充内边框 pOldBrush = pDC->SelectObject(&m_FillActive); DoGradientFill(pDC, &rect); /*鼠标经过时,填充底色*/ // pOldBrush = pDC->SelectObject(&m_FillOver); // pDC->FillRect(&rect,&m_FillOver); } else { pOldBrush = pDC->SelectObject(&m_FillInactive); DoGradientFill(pDC, &rect); } //光标在控件之上,或者获得焦点时,绘制内边框 if (m_bOver || m_bSelected) DrawInsideBorder(pDC, &rect); pDC->SelectObject(pOldBrush); //显示按钮的文本 if (strText!=NULL) { CFont* hFont = GetFont(); CFont* hOldFont = pDC->SelectObject(hFont); CSize szExtent = pDC->GetTextExtent(strText, lstrlen(strText)); //设置文本在控件居中显示, //获取控件矩形中心点 - 文本宽度的一半 得到绘制文本矩形区的坐上点 x 坐标 //同理计算得出 y 坐标 CPoint pt( rect.CenterPoint().x - szExtent.cx / 2, rect.CenterPoint().y - szExtent.cy / 2); if (state & ODS_SELECTED) pt.Offset(1, 1); int nMode = pDC->SetBkMode(TRANSPARENT); //设置文本背景色透明 if (state & ODS_DISABLED) //绘制按钮置灰不可用文本 pDC->DrawState(pt, szExtent, strText, DSS_DISABLED, TRUE, 0, (HBRUSH)NULL); else pDC->DrawState(pt, szExtent, strText, DSS_NORMAL, TRUE, 0, (HBRUSH)NULL); pDC->SelectObject(hOldFont); pDC->SetBkMode(nMode); } pDC->RestoreDC(nSaveDC); } //绘制按钮的底色,主要是高亮颜色渐变,有效果,但是不很明显 void CMyButton::DoGradientFill(CDC *pDC, CRect* rect) { CBrush brBk[64]; int nWidth = rect->Width(); int nHeight = rect->Height(); CRect rct; //初始化64个画刷,光标经过时为高亮 for (int i = 0; i < 64; i ++) { if (m_bOver) { if (m_bFocus) brBk[i].CreateSolidBrush(RGB(255 - (i / 4), 255 - (i / 4), 255 - (i / 3))); else brBk[i].CreateSolidBrush(RGB(255 - (i / 4), 255 - (i / 4), 255 - (i / 5))); } else { if (m_bFocus) brBk[i].CreateSolidBrush(RGB(255 - (i / 3), 255 - (i / 3), 255 - (i / 4))); else brBk[i].CreateSolidBrush(RGB(255 - (i / 3), 255 - (i / 3), 255 - (i / 5))); } } //绘制颜色渐变色 for (i = rect->top; i <= nHeight + 2; i ++) { rct.SetRect(rect->left, i, nWidth + 2, i + 1); pDC->FillRect(&rct, &brBk[((i * 63) / nHeight)]); } for (i = 0; i < 64; i ++) brBk[i].DeleteObject(); } //绘制按钮的内边框 void CMyButton::DrawInsideBorder(CDC *pDC, CRect* rect) { CPen *pLeft, *pRight, *pTop, *pBottom; if (m_bSelected && !m_bOver) { pLeft = & m_InsideBoundryPenLeftSel; pRight = &m_InsideBoundryPenRightSel; pTop = &m_InsideBoundryPenTopSel; pBottom = &m_InsideBoundryPenBottomSel; } else { pLeft = &m_InsideBoundryPenLeft; pRight = &m_InsideBoundryPenRight; pTop = &m_InsideBoundryPenTop; pBottom = &m_InsideBoundryPenBottom; } //实现原理,在控件所在矩形边界内画4条线 CPoint oldPoint = pDC->MoveTo(rect->left, rect->bottom - 1); CPen* pOldPen = pDC->SelectObject(pLeft); pDC->LineTo(rect->left, rect->top + 1); pDC->SelectObject(pRight); pDC->MoveTo(rect->right - 1, rect->bottom - 1); pDC->LineTo(rect->right - 1, rect->top); pDC->SelectObject(pTop); pDC->MoveTo(rect->left - 1, rect->top); pDC->LineTo(rect->right - 1, rect->top); pDC->SelectObject(pBottom); pDC->MoveTo(rect->left, rect->bottom); pDC->LineTo(rect->right - 1, rect->bottom); pDC->SelectObject(pOldPen); pDC->MoveTo(oldPoint); if (m_bSelected && !m_bOver) DrawFocusRect(pDC->m_hDC,rect); } BOOL CMyButton::OnEraseBkgnd(CDC* pDC) { //发生重绘时,禁止绘制底色 return TRUE; }
程序运行界面对比:图1设置鼠标经过填充背景 图2设置鼠标经过仅绘制内边框。
最后贴上工程文件:http://download.csdn.net/detail/shortcoder/6558467
共同学习VC,腾讯:508233969