Wednesday, February 25, 2009

C++ Windows API 实现避免屏幕保护和待机,休眠.

昨天贴出正在开发的BinPlayer配置窗口的截图.上面有一个"Prevent Standby and Screen Saver"的check box. 中文也就是"避免系统待机和屏幕保护程序".
后来就有人问我这个是怎么实现的,想想也是,虽然很简单,但是找起来却是问题多多.

我先给出我推荐的方式,再说原因:
使用一个Timer,使用较短的时间定时运行下面这个函数.

void resetSystemIdleTime () {
SetThreadExecutionState(ES_DISPLAY_REQUIRED);
SetThreadExecutionState(ES_SYSTEM_REQUIRED);
}

实际上这个函数作用就是重置两个系统参数,这个不是唯一的方法.在我看来,是比较好的方法.

在csdn上其他方法也很多,有模拟事件的,有激活窗口的.什么都有,一句话:不择手段.

唯独见到的正规一点的方法是:使用

SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, FALSE, NULL, 0);
SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, TRUE, NULL, 0);


来临时关闭和开启屏幕保护.类似的方法可以实现避免待机.不过这有个问题,那就是如果程序中途中断出错关闭,会造成重新启动前,系统的屏幕保护不能运行. 这虽然问题不大,但是我还是不希望BinPlayer有这样的"隐患".

至于我在上面说的resetSystemIdleTime方法,只有一个问题:这个Timer的时间怎么定.
不过我要说明的是,这两个函数运行的代价非常低,因为任何鼠标事件,键盘事件,都会"运行"这两个函数.所以如果时间设的非常短,也不会影响性能,只是给人一种没有必要的感觉.

一般来说,1分钟内是合适的,因为屏幕保护和待机的最短激活时间也就是1分钟.我也见过有BT到1分钟休眠的用户...

Tuesday, February 24, 2009

BinPlayer 配置对话框的实现 Windows API C/C++, TreeView

新学期开始了,今天开始上课.虽然这学期课很紧,但是BinPlayer仍然保持每天开发.经过两天尝试.做出了配置窗口的大致模型.下面是运行截图.


之前也尝试过多种版本,比如微软自己常用的tab标签型,KMPlayer的tree型.最终选择了这种样式.
我个人认为是在美观和简易上做的折衷.类似的样式在原版emule上也可以看到,不过那些有某些缺陷,样子是一样的.

左边的东西实质上是一个treeview,不过拥有样式TVS_SHOWSELALWAYS | TVS_FULLROWSELECT 这个TreeView只有根节点,没有子节点.
另外使用了TreeView_SetItemHeight来设置行的高度.一个Imagelist来设置图标.

做好之后要做的就是捕捉几个消息处理就是了.我在这里只对TVN_BEGINDRAG, TVN_BEGINRDRAG, TVN_SELCHANGED三个消息做了处理.同时还避免了emule上出现的一些小瑕疵.
右边被切换的对话框我使用CreateDialogIndirect来加速切换速度.

时间很晚了,毕竟开学了,要早睡早起~.下面给出部分关键代码,有空再把本文补充地完善些:



// self defined struct : for save infomation of items
struct ConfigerItemsInfo {
//dialog call back function
INT_PTR (CALLBACK * CallBackProc)(HWND, UINT, WPARAM, LPARAM);
//icon and string id in resources file
UINT iconID;
UINT StringID;
//dialog hwnd
HWND hwnd;
//dialog Template
DLGTEMPLATE * dlgTemplate;
};


//function to initial Dialog

//get TreeView Cotrol
HWND hwndTemp = GetDlgItem(hwndDlg,IDC_TREE_CONFIG);
//set new height
TreeView_SetItemHeight(hwndTemp, 22);

// get Configer class
if (NULL == BP_Configer) BP_Configer = new module_configurer();
// BP_Configer->getImageList() return the imagelist initied
TreeView_SetImageList(hwndTemp, BP_Configer->getImageList(), TVSIL_NORMAL);
// number of Items
int Temp = BP_Configer->getItemsCount();
TCHAR TempString[MAX_PATH];
for (int i=0; i
ConfigerItemsInfo * cfgitem;
// Get Item config info
cfgitem = BP_Configer->getItemByID(i);
// load string from resources
loadStringByID(cfgitem->StringID, TempString, MAX_PATH);
AddItemToTreeView(hwndTemp, TempString, cfgitem->iconID, (LPARAM)cfgitem);
}

//Add Item To TreeView
bool AddItemToTreeView (HWND hwndTV, LPWSTR lpszItem, int ImageId, LPARAM lparam) {
TVITEM tvi;
TVINSERTSTRUCT tvins;
HTREEITEM hti;

tvi.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_PARAM | TVIF_SELECTEDIMAGE;

// Set the text of the item.
tvi.pszText = lpszItem;
tvi.cchTextMax = sizeof(tvi.pszText)/sizeof(tvi.pszText[0]);

// Assume the item is not a parent item, so give it a
// document image.
tvi.iImage = ImageId;
tvi.iSelectedImage = ImageId;

// Save the heading level in the item's application-defined
// data area.
tvi.lParam = lparam;
tvins.item = tvi;
tvins.hInsertAfter = TVI_LAST;


tvins.hParent = TVI_ROOT;

// Add the item to the tree-view control.
if (NULL == TreeView_InsertItem(hwndTV,&tvins)) return false;
else return true;
}

Saturday, February 21, 2009

C/C++ 递归 搜索 文件夹 Windows API (SDK)

这样的代码我相信网络上到处都有,但是我大概看了下,很多都是有问题的代码.

核心的就是3个函数FindFirstFile, FindNextFile, FindClose;
建议不要使用C\C++的文件函数,那样会有很多很多问题.比如Unicode云云~

FindFirstFile如果想搜索一个文件夹里面的文件,需要在文件夹地址末尾加上"*"
FindNextFile成功的时候返回的是非0.
FindClose是和FindFirstFile一一对应的.FindNextFile不需要调用FindClose;

下面给出一个实例代码,这个代码是我为我的一个软件写的,在有些地方需要说明:
1.函数的第一个参数必须是一个文件夹路径,由于这个是一个核心函数,所以没有使用防御式编程的手动.
2.使用了lstrcpy这样的函数,没有使用Safe String函数.
3.函数的第二个参数是一个回调函数,当找到一个不是文件夹的文件时,会调用此函数.


void findRecurFiles(TCHAR * CurrentFilePath, void (__cdecl *CallBackFindFile)(TCHAR *)) {
WIN32_FIND_DATA FindFileData;
TCHAR TempFilePath[MAX_PATH];
TCHAR TempFilePathNext[MAX_PATH];
lstrcpy(TempFilePath, CurrentFilePath);
lstrcat(TempFilePath, TEXT(\\));
lstrcat(TempFilePath, TEXT("*"));
HANDLE hFind = INVALID_HANDLE_VALUE;

hFind = FindFirstFile(TempFilePath, &FindFileData);
if (hFind == INVALID_HANDLE_VALUE) return;

do {
if (!(FindFileData.dwFileAttributes&FILE_ATTRIBUTE_DIRECTORY)) {
lstrcpy(TempFilePathNext, CurrentFilePath);
lstrcat(TempFilePathNext, TEXT(\\));
lstrcat(TempFilePathNext, FindFileData.cFileName);
CallBackFindFile(TempFilePathNext);
} else if (FindFileData.cFileName[0] != TEXT('.')) {
lstrcpy(TempFilePathNext, CurrentFilePath);
lstrcat(TempFilePathNext, TEXT(\\));
lstrcat(TempFilePathNext, FindFileData.cFileName);
findRecurFiles(TempFilePathNext, CallBackFindFile);
}
} while (FindNextFile(hFind, &FindFileData)!= 0);
FindClose(hFind);
}

//sample usage

void msgMe(TCHAR * CurrentFilePath) {
MessageBox(NULL, CurrentFilePath, TEXT("OK"), MB_OK);
}

int _tmain(int argc, _TCHAR* argv[])
{
findRecurFiles(TEXT("C:\\"), msgMe);
return 0;
}

Monday, February 16, 2009

Windows API 使用控件ToolTip

ToolTip是个无处不在的控件.他不像别的控件要占一块地盘,他更多的时候是附着在其他控件上.就想这样:


既然ToolTip使用在各种各样的场合,那它必然很灵活... 所以他使用起来一定有点麻烦.这个逻辑在这里貌似没错.
ToolTip不是始终显示的控件,所以如果写错代码... 往往找不到北:"这东西不见了... 但是.. 它在哪里?",虽然MSDN给出了几个常见用法,但显然是有点问题的它没有说清楚.
主要是TOOLINFO.uFlags的细节.

如果只是想为一个控件给出提示,最好使用TTF_SUBCLASS.当有鼠标悬停事件发生时,他能够延时自动显示.
如果是为一个控件给出可变的信息来表示状态,TTF_TRACK | TTF_ABSOLUTE是最佳的解决方案.不过此时要通过TTM_TRACKPOSITION消息和TTM_TRACKACTIVATE消息来控制显示与否.并且通过TTM_SETTOOLINFO消息来控制.

common comtrol 6里面有很多控件都有ToolTip的属性.有些窗体熟悉也可以配置ToolTip,这时候可以避免繁琐的ToolTip编写.

下面给出了BinPlayer的现在版本中声音控件的ToolTip实现代码:(上图第2个)这是一个测试代码,写得不太规范,也不太通用,但毕竟是可以运行的.有空再给出详细的ToolTip使用实例.


//定义这几个量,因为TOOLINFO这个结构会被一直使用,所以把它作为静态量.
static TOOLINFO ToolInfo;
//控制是否显示/隐藏
static bool IsToolInfoTrack = false;
//显示的点和字
static POINT pt;
static TCHAR ToolWords[4];

//创建ToolTip的函数
HWND CreateTrackingToolTip(TOOLINFO * s_toolItem, HWND hDlg, WCHAR* pText) {
// Create a ToolTip.
pText[0] = 0;
HWND hwndTT = CreateWindowEx(WS_EX_TOPMOST,
TOOLTIPS_CLASS, NULL,
WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP,
CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT,
hDlg, NULL, hInst,NULL);

s_toolItem->cbSize = sizeof(TOOLINFO);
s_toolItem->uFlags = TTF_IDISHWND | TTF_TRACK | TTF_ABSOLUTE;
s_toolItem->hwnd = hDlg;
s_toolItem->hinst = hInst;
s_toolItem->lpszText = pText;
s_toolItem->uId = (UINT_PTR)hDlg;
GetClientRect (hDlg, &s_toolItem->rect);

// Associate the ToolTip with the tool window.
SendMessage(hwndTT, TTM_ADDTOOL, 0, (LPARAM) (LPTOOLINFO) s_toolItem);
return hwndTT;
}

//使用这个函数来创建ToolTip
CreateTrackingToolTip(& ToolInfo, hWnd, ToolWords);

//鼠标松开事件
SendMessage(BP_trie.ToolTip, TTM_TRACKACTIVATE, (WPARAM)FALSE, (LPARAM)&ToolInfo);
IsToolInfoTrack = false;

//鼠标按下且移动事件
//ThumbRect是被拖动滑块的RECT
pt.x = ThumbRect.left;
pt.y = ThumbRect.top - 18;
//Position是一个double,从0~1,表示拖动位置,在这里很安全
wsprintf(ToolInfo.lpszText, TEXT("%d\0"), (int)(Position * 100));
SendMessage(BP_trie.ToolTip, TTM_SETTOOLINFO, 0, (LPARAM)&ToolInfo);
//TTF_TRACK属性就意味着TTF_ABSOLUTE属性,比如映射到窗口的位置上.
ClientToScreen(hWnd, &pt);
SendMessage(BP_trie.ToolTip, TTM_TRACKPOSITION, 0, (LPARAM)MAKELONG(pt.x, pt.y));
if (!IsToolInfoTrack) {
SendMessage(BP_trie.ToolTip, TTM_TRACKACTIVATE, (WPARAM)TRUE, (LPARAM)&ToolInfo);
IsToolInfoTrack = true;
}

Saturday, February 14, 2009

编程常用工具之 Depends

这是一个古老简单的工具.他不是IDE,也不能帮你弄代码.他的作用是:分析dll.或者准确的说是:Module Dependency Checker.这个工具很有用,尤其在使用第三方dll的时候.
这个工具上表明是Microsoft Corporation,官网是http://www.dependencywalker.com/

它是免费使用,并且被很多工具收录其中,据说Windows安装CD里面就有,不过我还没找到.(话说Windows安装包里面有很多好玩的东东... 当然,那些修改版们基本上把它们都删了...) .. 跟往常一样看截图



它最基本的功能,或者说最常用的功能就是两个:
1.查看这个dll有那些接口(这里指的是函数).
2.看这个dll依赖哪些dll.

这个工具的一些细节功能我就不说了,把上面工具栏依次点一下就都明白了.我说说这个工具使用的时候有几点要注意:

dll依赖功能上,我做了实验,貌似他不能检测出手动LoadLibrary的情况... 至少他没有默认显示... 换句话说,他可能仍然要依赖别的dll,而这里没有显示出来.
大家都知道dll的载入搜索是有顺序的,具体顺序MSDN写的很清楚.depends的搜索顺序和你自己的程序的搜索顺序不一定一样... 换句话说:有可能depends检验能够顺利载入的dll你的程序却找不到. 反之也是. 在说的明朗点: 编程的时候时刻小心dll的位置.

Monday, February 9, 2009

C/C++ , GDI , GDI+ 双缓冲解决窗口闪烁 (Windows API)

刚开始利用Windows绘图函数进行编程的时候,我老是觉得:为什么我的程序比别人的要显得卡.
后来才明白,原来大多数程序都使用了"双缓冲"技术. 然后我又有一个问题:为什么Windows不在内部实现双缓冲呢? 都来知道,在Vista有个WDM, 差不多就有一个双缓冲功能...

闲话少说:GDI实现双缓冲过程:

取得DC
  1. 创建一个位图:HBITMAP CreateCompatibleBitmap
  2. 创建两个个兼容内存DC:CreateCompatibleDC
  3. 两个DC分别绑定"要画的图"和"刚才创建的位图":SelectObject
  4. 把想画的样子画在"刚才创建的位图"上:BitBlt, StretchBlt等函数
  5. 把"刚才创建的位图"画在真正的DC上:BitBlt, StretchBlt等函数
  6. 释放该释放的东西,位图和内存DC:DeleteObject, DeleteDC

再来个GDI+的过程:
  1. 根据窗口大小建立合适的位图Gdiplus::Bitmap,并且给他创建Gdiplus::Graphics
  2. 取得DC,创建Gdiplus::Graphics
  3. 取得想画的图Gdiplus::Image
  4. DrawImage,把东西都画在Gdiplus::Bitmap上.
  5. DrawImage,把Gdiplus::Bitmap画在DC的Gdiplus::Graphics上
  6. 释放该释放的东西.

C/C++ i18n 国际化 多语言 解决方案 (Windows API)

这次先当回标题党.
i18n 程序员都是大懒蛋,我也不例外. i18n就是internationalization,国际化.
说白了就是能让一个程序显示不同的语言. 类是的还有l10n,本地化,一般是两种语言,其中一种是英语.

理论上讲,如果想要实现i18n,使用Unicode方案是必须,好在微软的NT平台是基于UTF-16的.
所有的ANSI程序都是通过内部转化为UTF-16实现,所以在NT下编写ANSI程序是不明智的.

扯远了,那C/C++做的Windows API程序怎么实现i81n呢.
有没有像.NET,STRUTS提供的那样的专用函数呢? 如果你只是想知道答案,那就是没有.真的没有.
我们只能使用程序控制的手段实现i18n.

那有哪些实现方法呢?

纯资源dll
ini语言文件
xml语言文件
自定义的语言数据文件
大多数的程序选择前3者.其中纯资源dll还有两种实现方案.

资源文件里面包含菜单,对话框,String表.
只包含String表
其中后者的本质是和方案2,3是一样的.这个是个程序员后能明白.

如果想追求效率,使用方案1的1方案,但是要修改就麻烦了.
最求绿色简单,String表,或者ini语言文件
最求全面,必然是选择xml

其他:
使用微软为使用XML提供了多套方案.如MSXML,XmlLite.但是小程序建议不要使用.因为他的依赖性比较高.
ini的话Windows API提供的几个函数,都是GetPrivateProfile和SetPrivateProfile开头的.不过微软表示这几个函数都是为了兼容16位Windows而存在.只支持ANSI字符集.相对不过通用.微软建议使用注册表存东西.(难道要我用注册表存多语言...)
使用DLL作为资源只要合理使用LoadLibrary和LoadString函数.

PS:今天不知道谁把bin32贴在cb上... 这下可好... 一个连内测水平都没到的软件半成品被几百个人下载去... 唉...

Friday, February 6, 2009

ListView 控件的使用之一:如何绑定数据 (简洁的方案). (C++, Windows API)

今天只开篇...头疼...

当我开始深入Windows API编程的时候,我一直觉得Windows的窗体概念的设计是非常有意思的.
在编程的时候有一些和尝试格格不入的概率.尽管刚开始会理解困难,但是编程的时候却发现有着重大的优越性.

不过我对Windows的大多数common Control的设计却不怎么喜欢.
他让会让我这样的新手有时候觉得... 啊...怎么会这样.. 比如说我要捕获一个按钮的XXX事件. 按照我现在的知识面要么翻译整个消息.要么做钩子.. 我觉得两者都不是"正常"的手段. 应该发个消息,替换其处理函数,这样的功能才会显得高效.不知道他为什么不这么设计...也许有类似的功能,我没发现?...

不过对Listview控件的设计我却觉得十分的优秀.我们最常用的Explorer的主要部分就是一个TreeView.
很多系统软件的主要部分也是Listview... 可以说相当的常用.



这个控件虽然看起来简单,但是却是我发现的最复杂的控件之一:
有多复杂呢?
它有135个专用宏,126个专用消息,36个专用通知,27个专用结构.它还有一堆别的控件很少见的专用扩展STYLE.
别的不说,一般控件有两三个专用的结构...它有27个...

我最初使用Listview的时候就给这复杂给吓坏了... 当时在做小学期的软件,拿着几个最基本的添加删除宏.
自己做了一个Liked List,然后动态刷新显示...不停地ListView_SetItemText()...现在想想真是吃力不讨好的事情.

好了,直接说如何绑定数据.
这个简洁的手段的核心就是使用LVITEM的lParam参数.(LVITEM是记录ListView里面Item数据的核心结构.)

1.首先定义一个结构,这个结构用来存储需要保存的单个item的数据.
2.最是当添加数据的时候,使用C++的特性为他new 一个这样的结构,然后把它传给LVITEM的lParam
3.当任何时候需要取得这个数据时使用ListView_GetItem宏将其取出.此时该干嘛干嘛...
4.当需要删除时,先使用free将对应的内存释放..

参考代码:PlayListItem为自定义的结构.
struct PlayListItem {
TCHAR szInfo[MAX_PATH];
TCHAR szTime[10];
TCHAR szFileFullPath[MAX_PATH];
};

//插入数据示例
PlayListItem * rgInfo = new PlayListItem();
HWND hWndListView;
int index;
LVITEM lvI;

...//init

lvI.iItem = index;
lvI.iImage = index;
lvI.iSubItem = 0;
lvI.lParam = (LPARAM) rgInfo;
lvI.pszText = LPSTR_TEXTCALLBACK; // sends an LVN_GETDISP message.
ListView_InsertItem(hWndListView, &lvI);

//取数据示例:
LPNMITEMACTIVATE lpnmitem;
LVITEM lvItem;
....

case NM_DBLCLK:
plvdi = (NMLVDISPINFO*)lParam
lvItem.iItem = lpnmitem->iItem;
lvItem.mask = LVIF_PARAM;
ListView_GetItem(ListView,&lvItem);

//LVN_GETDISPINFO消息实例(由于使用了LPSTR_TEXTCALLBACK)
case LVN_GETDISPINFO:
plvdi = (NMLVDISPINFO*)lParam;
switch (plvdi->item.iSubItem) {
case 0:
plvdi->item.pszText = ((PlayListItem *)plvdi->item.lParam)->szInfo;
break;
case 1:
plvdi->item.pszText = ((PlayListItem *)plvdi->item.lParam)->szTime;
break;
default:
break;
}

Thursday, February 5, 2009

可拖拽文件窗口的使用.WS_EX_ACCEPTFILES和DragQueryFile

如果窗口拥有扩展属性WS_EX_ACCEPTFILES
其就可以使其可以接受拖拽效果
当拖拽成功时,被拖拽的窗口接受到WM_DROPFILES消息.
这时候,应该手动调用DragQueryFile函数看看被拖拽的是谁~ 然后处理.
UINT DragQueryFile(
HDROP hDrop,
UINT iFile,
LPTSTR lpszFile,
UINT cch
);

这个很简单,固然很简单,每个看MSDN的人都能找得到.
不过这个函数却有着Windows函数的某类函数的特点:不同参数值,不同行为表示.

这个函数的第二个参数iFile,如果iFile=0xFFFFFFFF,它则表示要查询拖拽了几个文件.(返回值)
如果iFile是其他值,则表示我要查询被拖拽第iFile个文件,其返回值是被复制的字节长度.

类似的还有GetEnvironmentVariable这样的函数,使用的时候很灵活.

另外cch是被复制的最大长度.
前段时间刚发现一个东西MAX_PATH, 它为260;

过去做程序老是有一个问题...这个文件名会不会很长....这个URL会不会很长....,曾经有一段时间我做程序都自定义一个MAX_FILE_LENGTH,然后在程序说明上说:支持文件地址长度小于XXX...

原来在Windows的最大文件长度是255!!!URL也是一样.... 做过实验,在Windows文件名太长系统会自动使用缩略文件名的功能.

不多说,给出使用范例.

case WM_DROPFILES:
//...
TCHAR Buffer[MAX_PATH];
unsigned int i;
unsigned int NumberOfDrags;
NumberOfDrags = DragQueryFile((HDROP)wParam, 0xFFFFFFFF, Buffer , MAX_PATH);
for (i = 0, NumberOfDrags , i++) {
DragQueryFile((HDROP)wParam, i, Buffer , MAX_PATH);
//actions here
}

Wednesday, February 4, 2009

自绘Tracebar控件 (Windows API)

Tracebar是一个common control.
先罗嗦一下:
我有个习惯,就是使用common control的时候尽量避免自己重绘.这样做有许多好处:
1.编程简单,不容易出问题.
2.不会因为界面增加程序体积.
3.能够自动拥有系统提供的主题和特效.

当然有的时候会出现点问题.
比如Tracebar控件.我一直认为这样的控件本应该设计得更好.下面是两个版本的截图.
两者绝对都符合我的风格:至简.


但是前者的两个Tracebar怎么看都不像是播放器的控件.
就连以界面简洁为特征之一的Foobar2000都进行了一定的重绘.


好了,兜了一圈直入正题.- -~
捕获NM_CUSTOMDRAW这个Notify(我不知道这个应该怎么翻译,消息?肯定不是.通知?通知好像有别的地方用到了....)
通常被认为正确的使用方法是这样:
case NM_CUSTOMDRAW:
lpDraw = (LPNMCUSTOMDRAW)lParam;
switch(lpDraw->dwDrawStage) {
case CDDS_PREPAINT:
return CDRF_NOTIFYITEMDRAW;
case CDDS_ITEMPREPAINT:
switch(lpDraw->dwItemSpec) {
case TBCD_CHANNEL:
....
return CDRF_SKIPDEFAULT;
case TBCD_THUMB:
....
return CDRF_SKIPDEFAULT;
}
break;
}
break;


要注意的主要是返回值,尤其是CDDS_PREPAINT消息的返回值.
具体的细节像TBCD_CHANNEL和TBCD_THUMB我就不说了,我不是MSDN. ^_^

可以说这个和通常的重绘没有两样.但是Tracebar自身设计的问题,却有点让程序员难以驾驭.



这个问题是通常大家使用Tracebar控件常见的问题.面对这种问题.我至今没有找到较好的解决方案.

强制进行背景重绘,强制窗口覆盖等方法通常都有效,却都不是通用的解决方案.最终我在BinPlayer的解决方案是自己绘制了这些"类"Tracebar控件...毕竟以后要支持PNG皮肤,很多东西都要做到很通用.

Monday, February 2, 2009

win32 自绘按钮的简单使用和小技巧. (Windows API)

按钮属于common control, 如果要在按钮上完全绘制自己的图案就应该使用"自绘按钮".
要想使用很简单,在创建按钮的时候,加入BS_OWNERDRAW的STYLE.这样在需要绘制的时候,其父窗体就会收到一个"WM_DRAWITEM"消息. 同时使用一个DRAWITEMSTRUCT结构来控制内容.其代码大致如下:
DRAWITEMSTRUCT* pdis;
...
case WM_DRAWITEM:
pdis = (DRAWITEMSTRUCT*) lParam;
switch(pdis->CtlID) {
case ID_PLAY_RESUMEPAUSE:
if (pdis->itemState & ODS_SELECTED) ...
}
...
...

首先根据CtlID熟悉看是谁在画, 然后使用itemState熟悉决定画什么, 关于itemState还是MSDN比较全.

不过这样的代码却有点小问题, 当双击按钮的时候, 并没有像普通的按钮被解析为两次点击.
为什么出现这个情况其实很容易理解, 在Windows中按钮和菜单被认为是一个东西的不同形式(当然还包括check box云云).. 菜单怎么可能被双击呢?
但是按钮毕竟不是菜单,那如果我非要他把双击作为两次单击怎么办呢? 要做的其实很简单, 并不需要手动勾住双击事件. 只要设置一下按钮的属性.

LONG dwValue = GetClassLong(hButton, GCL_STYLE);
SetClassLong(hButton, GCL_STYLE, dwValue & ~CS_DBLCLKS);


CS_DBLCLKS是窗体类的通用熟悉,它表明窗体愿意接受双击. 去掉这个属性, 两次点击, 就是两次点击, 一次也少不了.

这个东西其实很Normal, 但是为什么要拿出来说捏, 不是我半夜闲得蛋疼. 是我发现常见的播放器中只有Media Player的播放按钮能够有效屏蔽双击. 网上甚至有人说这个必须要自己做消息解析才行... 我估计是他只用过MFC,没用过API...

PS:为BinPlayer做的自绘按钮.算是效果图吧