在PC上做按键重绘很简单,网上资料也很多,但是 这并不能用在WM上。
在把PC上的那一套搬到WM上后,就产生了一系列的问题:
我继承了CButton,做了个分不同状态贴图的button
Q1:在自己做的button中,要根据button的不同状态进行贴图
我是在DrawItem中对button的状态进行判断时,并不是像网上搜到的资料那样 ODS_SELECTED == state
就是按下,感觉状态很乱,这个 ODS_SELECTED ==
state 条件会走到,但是马上会被新的状态冲掉,在视觉上也没有发生什么变化。
长按能有效果吗?
答案是不能,长按的话会导致在屏幕上鼠标的地方出现一个计时小圆圈....
这是为什么呢?
A1:
一般来说,在UI开发中,常常需要使用位图按钮。在MFC中,也提供了直接的类(CBitmapButton)供开发者使用。这个类在PC平台下工作得很好,但在Windows
Mobile平台下,会有一个问题:当用户点击按钮后,并没有正确的显示击中状态的问题。
后来我想了下,觉得这个问题的原因在于,Mobile平台下,CBitmapButton只有在LButtonDown的时候才把状态设置为选中,且这个时间很短暂,所以在DrawItem绘出的击中状态位图很快就被刷新为了未击中状态。由于这个时间很短,所以看起来就像是没有变化一样。另外,当用户长按按钮时,系统会将WM_LBUTTONDOWN消息转化为WM_CONTEXTMENU消息,所以在长按时不会一直显示击中状态,而是会出现一个计数圈的动画。
所以,如果想要实现较好效果的自绘按钮,需要在CBitmapButton基础上稍微进行修改。当然,也可以直接在CButton的基础上修改,那无非就是把CBitmapButton已经实现了的LoadBitmaps()和SizeToContent()方法重新实现下。
以下是在CBitmapButton基础上实现自绘按钮的过程:
一、实现自绘控件。其实实现自绘的步骤几乎都一样:自定义一个类,派生于目标控件类,然后重写CWnd::PreSubclassWindow方法,在调用基类方法之前,先调用ModifyStyle设置风格为OWNERDRAW。
void CBmpButton::PreSubclassWindow()
{
this->ModifyStyle(NULL, BS_OWNERDRAW);
CWnd::PreSubclassWindow();
}
然后,重写CButton::DrawItem函数,在其中根据状态来进行绘制。
二、如果是在PC平台上,则上述两个步骤就可以了。但在Window
Mobile平台上,有PC平台有些许不同:就如上面所提到的那样,当长时间点击屏幕时,会产生WM_CONTEXTMENU消息,这个消息的默认响应函数将会在屏幕上绘制一个动画圈圈,造成这个圈所占区域的重绘。更重要的是,这个时候,在DrawItem中所得到的状态,不再是ODS_SELECTED,所以会出现点击后,击中状态一闪而过,看起这根本没有变化。另外,还会有重绘时造成的闪烁感。所以,需要在自定义的类中,重写OnLButtonDown和OnLButtonUp函数,在这两个函数中进行状态的重绘。另外,还需要在OnLButtonDown中禁用Press-Hold手势(即,长时间点击)。
void CBmpButton::PreSubclassWindow()
{
this->ModifyStyle(NULL, BS_OWNERDRAW);//set
owner-draw CWnd::PreSubclassWindow();
}
void CBmpButton::OnLButtonDown(UINT nFlags, CPoint
point)
{ //draw selected state
CClientDC
clientDC(this);
CRect rc;
GetClientRect(&rc);
this->DrawButton(1, clientDC.GetSafeHdc(), rc);
//forbidden Press-Hold
gesture
SHRGINFO shrg;
shrg.cbSize =
sizeof(shrg);
shrg.hwndClient =
m_hWnd;
shrg.ptDown = point;
shrg.dwFlags =
SHRG_RETURNCMD | SHRG_NOANIMATION;
if (GN_CONTEXTMENU ==
::SHRecognizeGesture(&shrg) )
{ return ; }
CBitmapButton::OnLButtonDown(nFlags, point);
}
void CBmpButton::OnLButtonUp(UINT nFlags, CPoint
point)
{ //draw normal state
CClientDC
clientDC(this);
CRect rc;
GetClientRect(&rc);
this->DrawButton(1, clientDC.GetSafeHdc(), rc);
//redraw
this->Invalidate();
this->UpdateWindow();
CBitmapButton::OnLButtonUp(nFlags,
point);
}
void CBmpButton::DrawItem(LPDRAWITEMSTRUCT lpDIS)
{
return
CBitmapButton::DrawItem(lpDIS);
}
BOOL CBmpButton::DrawButton(UINT nState, HDC hDC, const CRect
&rcItem)
{
HDC
hMemDC = ::CreateCompatibleDC(hDC);
if
(!hMemDC)
{ return FALSE; }
SetBkMode(hMemDC,
TRANSPARENT);
switch (nState)
{ case
0://normal
。。。。。
break;
case 1://selected
。。。
break;
case 2://focused
break;
case 3://disabled
break;
default:
break;
}
//get size of bitmap
BITMAP bmpTemp;
m_bitmap.GetObject(sizeof(BITMAP),
&bmpTemp);
CSize
sizeBmp(bmpTemp.bmWidth, bmpTemp.bmHeight);
BOOL bBlt =
::StretchBlt(hDC, rcItem.left, rcItem.top, rcItem.Width(),
rcItem.Height(), hMemDC, 0, 0, sizeBmp.cx, sizeBmp.cy,
SRCCOPY);
::DeleteDC(hMemDC);
return bBlt;
}
通过这样的方式,就可以实现期望的效果了
Q2:同样是在这个自制的button里面,按键按下能得到WM_LBUTTONDOWN
,但是当我拽着光标到button外再释放,这个自制的button里面也能收到WM_LBUTTONUP 在button区域外释放光标也能收到buttonup的消息??
A2:
事实证明,这是在进行调试的时候打断点导致的.....
Windows只把键盘消息发送给拥有输入焦点的窗口。鼠标消息与此不同:只要鼠标跨越窗口或者在某窗口中按下鼠标按键,那么窗口消息处理程序就会收到鼠标消息,而不管该窗口是否活动或者是否拥有输入焦点。Windows为鼠标定义了21种消息,不过,其中有11个消息和显示区域无关(下面称之为「非显示区域」消息),Windows程序经常忽略这些消息。
当您把鼠标移过窗口的显示区域时,Windows并不为鼠标的每个可能的图素位置都产生一个WM_MOUSEMOVE消息。您的程序接收到WM_MOUSEMOVE消息的次数,依赖于鼠标硬件,以及您的窗口消息处理程序在处理鼠标移动消息时的速度。换句话说,Windows不能用未处理的WM_MOUSEMOVE消息来填入消息队列。
如果您在非活动窗口的显示区域中按下鼠标左键,Windows将把活动窗口改为在其中按下鼠标按键的窗口,然后把WM_LBUTTONDOWN消息送到该窗口消息处理程序。当窗口消息处理程序得到WM_LBUTTONDOWN消息时,您的程序就可以安全地假定该窗口是活动化的了。不过,您的窗口消息处理程序可能在未接收到WM_LBUTTONDOWN消息的情况下先接收到了WM_LBUTTONUP的消息。如果在一个窗口中按下鼠标按键,然后移动到使用者窗口释放它,就会出现这种情况。类似的情况,当鼠标按键在另一个窗口中被释放时,窗口消息处理程序只能接收到WM_LBUTTONDOWN消息,而没有相应的WM_LBUTTONUP消息。
Q3:在父窗口中:
因为是动态生成的 就是说 我是根据某个值循环create生成了很多的button
不能通过敲代码来设定它们的消息处理函数 那么我该怎么来进行他们的消息处理(例如处理他们的buttonup
buttondown...等消息)
我重载OnCommand():
wID = (WORD) LOWORD (wParam);
wNotifyCode = (WORD) HIWORD (wParam)
得到的wID是正确的 ,可是wNotifyCode 为0
这是为什么呢?不能这样做吗?那应该怎样做??
A3:
会有这个疑问,还是因为自己对WM_COMMAND 不熟悉,以为 wNotifyCode
是WM_LBUTTONDOWN 等这样的消息.... -_-! 昨天也有些急躁,愣是没觉得把
wNotifyCode 和 WM_消息 相比较有什么问题,这根本就不是一个等级的啊!
子窗体被触发时,向父窗体发送一个WM_COMMAND消息,父窗体的窗口函数处理这个消息,进行相关的处理。lParam表示子窗口句柄,LOWORD(wParam)表示子窗口ID,HIWORD
(wParam)表示通知码(例如单击,双击,SETFOCUS等)。
按钮通知码的可能值在Windows表头文件中定义如下:
表9-1
按钮通知码标识符
值
BN_CLICKED
0
BN_PAINT
1
BN_HILITE or BN_PUSHED
2
BN_UNHILITE or BN_UNPUSHED
3
BN_DISABLE
4
BN_DOUBLECLICKED or BN_DBLCLK
5
BN_SETFOCUS
6
BN_KILLFOCUS
7
实际上,您不会看到这些按钮值中的大多数。从1到4的通知码是用于一种叫做BS_USERBUTTON的已不再使用的按钮的(它已经由BS_OWNERDRAW和另一种不同的通知方式所替换)。通知码6到7只有当按钮样式包括标识BS_NOTIFY才发送。通知码5只对BS_RADIOBUTTON、BS_AUTORADIOBUTTON和BS_OWNERDRAW按钮发送,或者当按钮样式中包括BS_NOTIFY时,也为其它按钮发送。
控件的自画:
首先在创建控件的时候当然就是指定BS_OWNERDRAW的STYLE,这个STYLE是告诉控件,别自己处理外观,让主程序来处理你的外观,这时你就有权决定这个控件是画成什么样子了。然后就是处理WM_DRAWITEM的消息,利用
LPDRAWITEMSTRUCT pdis = (LPDRAWITEMSTRUCT) lParam;
来取得一些必要的信息,如按钮的DC,位置等。这才能对这个DC的内容进行绘画啊。COMMON
CTRL的STYLE都在COMMCTRL.H头文件里。
按钮在以下状态时会对它的父窗口发送WM_COMMAND的消息:
按了一次(BN_CLICKED),取得焦点(BN_SETFOCUS),失去焦点(BN_KILLFOCUS)等。
这个是按钮的发送WM_COMMAND的条件,其他的控件什么时候会发送WM_COMMAND消息可查看该控件的通知码(在wParam的高位HIWORD)。例如,滚动条控件在被滚动的时候会向它的父窗体发送消息,但是不是WM_COMMAND消息,而是WM_VSCROLL和WM_HSCROLL消息。这只是为了说明凡是子控件,都会在适当的条件下向它的父窗体发送消息。无论是WM_COMMAND还是WM_NOTIFY或是WM_VSCROLL消息等。MoveWindow会产生WM_SIZE消息。
在Windows3.1里,控件会将mouse,
keybord等等的消息通知它的父窗口, 使用的消息就只有WM_COMMAND, 事件种类和控件ID被包含在wParam中,
控件的句柄包含在lParam中。由于wParam和
lParam已经满了,当控件要向父窗口发送其它特殊消息同时附带很多信息的时候就没有地方可以存放它们了。所以Windows3.1中定义了许多其它的消息种类,比如WM_VSCROLL,
WM_CTLCOLOR等等,每种消息wParam,lParam中附带的信息是不同的。
当到了Win32后,控件的种类越来越多,当然不可以为每一个控件都定义一套消息,这样也不利于系统的扩充。所以在Win32中定义了唯一一个强大的消息
WM_NOTIFY。当然WM_NOTIFY也遵守原来的消息规则,既只带参数wParam和lParam。唯一不同处在于,此时的lParam中传送的是一个NMHDR指针。不同的控件可以按照规则对NMHDR进行扩充,因此WM_NOTIFY消息传送的信息量可以相当的大,这个可以看看MSDN中的相关说明,TreeControl中就有很多这种消息。
现在就可以知道为什么有ON_MESSAGE ,ON_COMMAND, , ON_NOTIFY了。
ON_MESSAGE是处理所有的Windows的消息的,因为所有的消息都以相同的格式传送,也就是ID, WPARAM,
LPARAM.
ON_COMMAND是专门处理WM_COMMAND消息的,这样我们就不用自己解开WM_COMMAND中wParam和lParam中传送的控件ID,
事件种类…,所有的都在MFC内部解决了:),当然方便了。
ON_NOTIFY更是不用说了,看看他的处理函数,是不是把NMHDR解出来了。
这样一样就一目了然了,ON_COMMAND和ON_NOTIFY都可以用ON_MESSAGE来处理,只不过自己要多做很多事情。ON_COMMAND和ON_NOTIFY最好就不要互换了!