有篇中文的,只不过用的是VC。其实也是调用API,应该看得懂 定制窗口标题栏 作者:james翻译:ddn78, 2003/01/06翻译说明:通过这篇文章,可以了解:替换窗口过程的操作 定制标题栏时需要注意那些问题和需要关注那些消息 如何防止闪烁 与非客户区相关的鼠标消息 如何编写纯Win32 SDK程序 简介 这篇文章将教你如何在标题栏中插入一个或多个按钮。源代码中包含一个完整的库,能够定制各种类型的窗口,包含标准的frame windows和tool windows(注:即WM_EX_TOOLWINDOW)。 子类化窗口( subclass the window ) 就像大多数的定制窗口一样,我们需要声明一个结构用于保存每个按钮的属性:typedef struct { UINT uCmd; //Command to send when clicked (WM_COMMAND) int nRightBorder; //Pixels between this button and buttons to the right HBITMAP hBmp; //Bitmap to display BOOL fPressed; //Is the button pressed in or out?.
} CaptionButton;我们还需要知道被插入按钮的索引,按钮数组和保存被替换原先窗口进程:typedef struct { CaptionButton buttons[MAX_TITLE_BUTTONS]; int nNumButtons; BOOL fMouseDown; // is the mouse button being clicked? WNDPROC wpOldProc; // old window procedure int iActiveButton; // the button index being clicked.} CustomCaption; 现在我们声明一个 InsertButton 函数。该函数执行如下一系列步骤在窗口的标题栏中插入一个按钮:.首个按钮被插入时在内存中分配一个 CustomCaption 结构对象 将该结构关联到指定的窗口中 (using SetProp API, if necessary) 用我们自定义的窗口过程替换到窗口原来的过程(WNDPROC) 插入按钮到按钮数组中 注意:我们插入一个按钮时不需要任何物理(physical)上的改变(注:我想所谓物理上的改变即不需要手动调整非客户区)。不像在有些定制的编辑控件中加入按钮那样需要在WM_NCCALCSIZE消息中为按钮分配足够的空间,在我们这里不需要。这是因为标题栏已经存在,我们只是在上面重画我们的按钮,仅此而已。 函数原型如下:BOOL Caption_InsertButton(HWND hwnd, UINT uCmd, int nBorder, HBITMAP hBmp);计算按钮位置(Calculating the button position) 我们定制的按钮没有固定的尺寸和位置,它的位置和尺寸完全依赖于当前窗口的位置和尺寸以及当前系统中设置的标题栏的高度。因此有必要写一个函数来计算在指定时间下每个按钮的位置和尺寸,当我们需要重画按钮,或者当我们准备处理鼠标事件时需要调用该函数。好吧,让我们看看该函数的样子:void GetButtonRect(CustomCaption *ctp, HWND hwnd, int idx, RECT *rect, BOOL fWindowRelative); 我们需要传递一个 CustomCaption 结构指针给指定的窗口,还有窗口句柄本身,目标按钮的索引以及一个RECT指针用于保存返回的按钮位置 。最后一个参数,fWindowRelative,用于控制返回坐标的相对位置:如果值为 TRUE 意味着返回坐标相对于窗口的左上角(注:也就是窗口DC坐标系),否则为屏幕坐标系。函数 GetButtonRect 在计算出按钮位置之前需要知道如下信息:Titlebar button size (in pixels). 我们可以通过调用 GetSystemMetrics API 获得,但首先我们必须测试窗口是一个标准窗口还是一个tool-window( with a narrow titlebar). Current caption buttons. 窗口一般包含四个按钮:Close [x], Minimize [-], Maximize [+] 和 Context Help [?] 按钮。 如果我们要保持这些按钮不变,必须注意我们插入的按钮不能覆盖这些按钮。GetButtonRect 函数通过测试window styles 来决定那些系统按钮已经存在。 Window border size. 窗口的边框有许多不同的风格,主要依赖于该窗口是固定的( fixed )还是可调节的( resizing ), 还与当前的系统设置有关。 GetButtonRect 充分考虑并计算当前的边框尺寸。 Previously inserted buttons. 当然,最后还有考虑按钮的数目、边距和尺寸(基于系统设置) 现在我们已经可以正确的计算出每个按钮的位置,可以进入下一步:绘制按钮:) 绘制按钮(Drawing the inserted buttons) 由于按钮位于窗口的非客户区,所以我们需要处理 WM_NCPAINT消息。在标题栏上绘制按钮并不很容易,最好是我们实际绘制的操作越少越好! 如果可能,我们希望在轮到我们动手之前让窗口自身的过程处理尽可能多的绘制操作。问题在于我们必须在保留原有标题栏的同时在其之上绘制按钮,并且需要防止闪烁和绘画失灵。基本思路是让窗口原先过程( the original window procedure )绘制除了按钮区域之外的整个窗口框架和标题栏,一旦窗口绘制完之后,剩下的工作就是在按钮区域中依次绘制按钮。 will break the process down into steps so that you can see how this is achieved.首先调用CreateRectRgn API为整个窗口创建一个region,然后通过指定RGN_XOR 标志调用 CombineRgn API 减去其中按钮所占的区域。这里需要提及的很重要的一点是所有的regions和rectangles都必须采用屏幕坐标(screen coordinates)。具体WM_NCPAINT的实现如下:HRGN hrgn, temprgn;// Get the SCREEN coordinates of this window. GetWindowRect(hwnd, &rect); // Create a region which covers the whole window. if(wParam == 1) hrgn = CreateRectRgnIndirect(&rect); else hrgn = (HRGN)wParam;// Clip our custom buttons out of the way... for(i = 0; i < ctp->nNumButtons; i++) { //Get button rectangle in screen coords GetButtonRect(ctp, hwnd, i, &rect, FALSE); temprgn = CreateRectRgnIndirect(&rect); //Cut out a button-shaped hole CombineRgn(hrgn, hrgn, temprgn, RGN_XOR); DeleteObject(temprgn); }
接下来的任务是让窗口原来过程绘制整个窗口框架和标题栏。消息WM_NCPAINT的wParam 参数是需要更新的region,我们用新创建的region取代原先的wparam调用原先的窗口过程,从而避免绘制按钮区域。CallWindowProc(ctp->wpOldProc, hwnd, WM_NCPAINT, (WPARAM)hrgn, 0);最后我们开始绘制按钮!绘制时采用窗口的设备上下文句柄 ,因此,另一点很重要的是所有的坐标必须为窗口设备坐标系。HDC hdc = GetWindowDC(hwnd);// Draw buttons in a loop for(i = 0; i < ctp->nNumButtons; i++) { //Get Button rect in window coords GetButtonRect(ctp, hwnd, i, &rect1, TRUE);
if(ctp->buttons[i].fPressed) DrawFrameControl(hdc, &rect1, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED); else DrawFrameControl(hdc, &rect1, DFC_BUTTON, DFCS_BUTTONPUSH); //Draw the bitmap on top... }ReleaseDC(hwnd, hdc);if(wParam == 1) DeleteObject(hrgn);上面的代码仅仅绘制处于按下或正常状态的空按钮。 如果原先wParam为一个有效的region,那么我们不必释放它,Windows自己会处理。如果wParam是由我们分配的区域句柄( update region)则必须记住释放。 让按钮工作(Making the buttons work) 现在按钮只是一个空壳,我们必须让它工作。Mouse button down (WM_NCLBUTTONDOWN)当鼠标左键在按钮上按下时,我们必须重画按钮处于按下状态。 同时需要捕获光标以便在按钮保持按下时能够收到其它光标消息,比如当我们离开窗口时仍然能够收到鼠标消息。int i; RECT rect; POINT pt;
// retrieve the mouse coordinates pt.x = (short)LOWORD(lParam); pt.y = (short)HIWORD(lParam);// Loop over all buttons to see if one has been clicked for(i = 0; i < ctp->nNumButtons; i++) { //get screen coordinates of each button GetButtonRect(ctp, hwnd, i, &rect, FALSE); InflateRect(&rect, 0, 2);
//if clicked in a custom button if(PtInRect(&rect, pt)) { ctp->iActiveButton = i; ctp->buttons[i].fPressed = TRUE; ctp->fMouseDown = TRUE;
谢谢............
http://www.vbaccelerator.com/home/VB/Code/Controls/Skins/article.asp
但对你的话我有另名一种理解的意思,,是不是获取标题栏的句柄,然后根据这个句柄,自己添加几个Picture控件(用API指定新交,调整位置,大小)就行了啊
在线等待
不过抓句柄到并不难
标题栏上的按钮并不是标准的按钮子窗口,按钮的按下与弹起效果完全就是两幅位图模拟出来的。为此,我们需要处理WM_NCPAINT和WM_ACTIVATE消息。首先计算出按钮的位置,利用GetWindowRect得到窗口的大小位置,再调用函数GetSystemMetrics得到标题栏的高度等系统参数。当需要查询什么时就传递相应的参数给它,下面是我们要用到的参数:SM_CXFRAME窗口边框宽度
SM_CYFRAME窗口边框高度
SM_CXSIZE标题栏上的按钮宽度
SM_CYSIZE标题栏上的按钮高度接着就可以计算出按钮的位置,然后我们就可以根据按钮的状态(按下、弹起)画上相应的位图。好,到这我们已经把按钮显示出来啦。按钮显示出来后还要能响应鼠标消息才能真正的起到按钮的作用,也就是说我们需要在子类中处理鼠标按下的消息,也就是WM_NCLBUTTONDOWN,还有WM_NCLBUTTONUP等。具体的你看看我介绍你看的网页中提供的代码吧
http://www.vbaccelerator.com/home/VB/Code/Controls/Skins/VB6_NeoCaption_Full_Source.asp
主要原理是:拦截WM_NcPaint消息,自己用GDI函数绘制整个非客户区
www.vbaccelerator.com的那个代码很经典的
想搞界面编程一定要记住这句话:界面是画出来的
定制窗口标题栏 作者:james翻译:ddn78, 2003/01/06翻译说明:通过这篇文章,可以了解:替换窗口过程的操作 定制标题栏时需要注意那些问题和需要关注那些消息 如何防止闪烁 与非客户区相关的鼠标消息 如何编写纯Win32 SDK程序
简介
这篇文章将教你如何在标题栏中插入一个或多个按钮。源代码中包含一个完整的库,能够定制各种类型的窗口,包含标准的frame windows和tool windows(注:即WM_EX_TOOLWINDOW)。
子类化窗口( subclass the window ) 就像大多数的定制窗口一样,我们需要声明一个结构用于保存每个按钮的属性:typedef struct
{
UINT uCmd; //Command to send when clicked (WM_COMMAND)
int nRightBorder; //Pixels between this button and buttons to the right
HBITMAP hBmp; //Bitmap to display BOOL fPressed; //Is the button pressed in or out?.
} CaptionButton;我们还需要知道被插入按钮的索引,按钮数组和保存被替换原先窗口进程:typedef struct
{
CaptionButton buttons[MAX_TITLE_BUTTONS];
int nNumButtons;
BOOL fMouseDown; // is the mouse button being clicked?
WNDPROC wpOldProc; // old window procedure int iActiveButton; // the button index being clicked.} CustomCaption;
现在我们声明一个 InsertButton 函数。该函数执行如下一系列步骤在窗口的标题栏中插入一个按钮:.首个按钮被插入时在内存中分配一个 CustomCaption 结构对象 将该结构关联到指定的窗口中 (using SetProp API, if necessary) 用我们自定义的窗口过程替换到窗口原来的过程(WNDPROC) 插入按钮到按钮数组中 注意:我们插入一个按钮时不需要任何物理(physical)上的改变(注:我想所谓物理上的改变即不需要手动调整非客户区)。不像在有些定制的编辑控件中加入按钮那样需要在WM_NCCALCSIZE消息中为按钮分配足够的空间,在我们这里不需要。这是因为标题栏已经存在,我们只是在上面重画我们的按钮,仅此而已。 函数原型如下:BOOL Caption_InsertButton(HWND hwnd, UINT uCmd, int nBorder, HBITMAP hBmp);计算按钮位置(Calculating the button position)
我们定制的按钮没有固定的尺寸和位置,它的位置和尺寸完全依赖于当前窗口的位置和尺寸以及当前系统中设置的标题栏的高度。因此有必要写一个函数来计算在指定时间下每个按钮的位置和尺寸,当我们需要重画按钮,或者当我们准备处理鼠标事件时需要调用该函数。好吧,让我们看看该函数的样子:void GetButtonRect(CustomCaption *ctp, HWND hwnd, int idx, RECT *rect, BOOL fWindowRelative);
我们需要传递一个 CustomCaption 结构指针给指定的窗口,还有窗口句柄本身,目标按钮的索引以及一个RECT指针用于保存返回的按钮位置 。最后一个参数,fWindowRelative,用于控制返回坐标的相对位置:如果值为 TRUE 意味着返回坐标相对于窗口的左上角(注:也就是窗口DC坐标系),否则为屏幕坐标系。函数 GetButtonRect 在计算出按钮位置之前需要知道如下信息:Titlebar button size (in pixels).
我们可以通过调用 GetSystemMetrics API 获得,但首先我们必须测试窗口是一个标准窗口还是一个tool-window( with a narrow titlebar).
Current caption buttons.
窗口一般包含四个按钮:Close [x], Minimize [-], Maximize [+] 和 Context Help [?] 按钮。 如果我们要保持这些按钮不变,必须注意我们插入的按钮不能覆盖这些按钮。GetButtonRect 函数通过测试window styles 来决定那些系统按钮已经存在。 Window border size.
窗口的边框有许多不同的风格,主要依赖于该窗口是固定的( fixed )还是可调节的( resizing ), 还与当前的系统设置有关。 GetButtonRect 充分考虑并计算当前的边框尺寸。 Previously inserted buttons.
当然,最后还有考虑按钮的数目、边距和尺寸(基于系统设置) 现在我们已经可以正确的计算出每个按钮的位置,可以进入下一步:绘制按钮:)
绘制按钮(Drawing the inserted buttons)
由于按钮位于窗口的非客户区,所以我们需要处理 WM_NCPAINT消息。在标题栏上绘制按钮并不很容易,最好是我们实际绘制的操作越少越好! 如果可能,我们希望在轮到我们动手之前让窗口自身的过程处理尽可能多的绘制操作。问题在于我们必须在保留原有标题栏的同时在其之上绘制按钮,并且需要防止闪烁和绘画失灵。基本思路是让窗口原先过程( the original window procedure )绘制除了按钮区域之外的整个窗口框架和标题栏,一旦窗口绘制完之后,剩下的工作就是在按钮区域中依次绘制按钮。 will break the process down into steps so that you can see how this is achieved.首先调用CreateRectRgn API为整个窗口创建一个region,然后通过指定RGN_XOR 标志调用 CombineRgn API 减去其中按钮所占的区域。这里需要提及的很重要的一点是所有的regions和rectangles都必须采用屏幕坐标(screen coordinates)。具体WM_NCPAINT的实现如下:HRGN hrgn, temprgn;// Get the SCREEN coordinates of this window.
GetWindowRect(hwnd, &rect); // Create a region which covers the whole window.
if(wParam == 1)
hrgn = CreateRectRgnIndirect(&rect);
else
hrgn = (HRGN)wParam;// Clip our custom buttons out of the way...
for(i = 0; i < ctp->nNumButtons; i++)
{
//Get button rectangle in screen coords
GetButtonRect(ctp, hwnd, i, &rect, FALSE); temprgn = CreateRectRgnIndirect(&rect); //Cut out a button-shaped hole
CombineRgn(hrgn, hrgn, temprgn, RGN_XOR); DeleteObject(temprgn);
}
接下来的任务是让窗口原来过程绘制整个窗口框架和标题栏。消息WM_NCPAINT的wParam 参数是需要更新的region,我们用新创建的region取代原先的wparam调用原先的窗口过程,从而避免绘制按钮区域。CallWindowProc(ctp->wpOldProc, hwnd, WM_NCPAINT, (WPARAM)hrgn, 0);最后我们开始绘制按钮!绘制时采用窗口的设备上下文句柄 ,因此,另一点很重要的是所有的坐标必须为窗口设备坐标系。HDC hdc = GetWindowDC(hwnd);// Draw buttons in a loop
for(i = 0; i < ctp->nNumButtons; i++)
{
//Get Button rect in window coords
GetButtonRect(ctp, hwnd, i, &rect1, TRUE);
if(ctp->buttons[i].fPressed)
DrawFrameControl(hdc, &rect1, DFC_BUTTON, DFCS_BUTTONPUSH | DFCS_PUSHED);
else
DrawFrameControl(hdc, &rect1, DFC_BUTTON, DFCS_BUTTONPUSH); //Draw the bitmap on top...
}ReleaseDC(hwnd, hdc);if(wParam == 1)
DeleteObject(hrgn);上面的代码仅仅绘制处于按下或正常状态的空按钮。 如果原先wParam为一个有效的region,那么我们不必释放它,Windows自己会处理。如果wParam是由我们分配的区域句柄( update region)则必须记住释放。
让按钮工作(Making the buttons work)
现在按钮只是一个空壳,我们必须让它工作。Mouse button down (WM_NCLBUTTONDOWN)当鼠标左键在按钮上按下时,我们必须重画按钮处于按下状态。 同时需要捕获光标以便在按钮保持按下时能够收到其它光标消息,比如当我们离开窗口时仍然能够收到鼠标消息。int i;
RECT rect;
POINT pt;
// retrieve the mouse coordinates
pt.x = (short)LOWORD(lParam);
pt.y = (short)HIWORD(lParam);// Loop over all buttons to see if one has been clicked
for(i = 0; i < ctp->nNumButtons; i++)
{
//get screen coordinates of each button
GetButtonRect(ctp, hwnd, i, &rect, FALSE);
InflateRect(&rect, 0, 2);
//if clicked in a custom button
if(PtInRect(&rect, pt))
{
ctp->iActiveButton = i;
ctp->buttons[i].fPressed = TRUE;
ctp->fMouseDown = TRUE;
SetCapture(hwnd);
RedrawCaption(hwnd);
return 0;
}
}return CallWindowProc(ctp->wpOldProc, hwnd, msg, wParam, lParam);
Mouse Movement (WM_MOUSEMOVE)如果光标位于按钮内,则按钮被绘制成凹陷状态,否则正常状态。GetButtonRect(ctp, hwnd, ctp->iActiveButton, &rect, FALSE);if(PtInRect(&rect, pt))
ctp->buttons[ctp->iActiveButton].fPressed = TRUE;
else
ctp->buttons[ctp->iActiveButton].fPressed = FALSE;
Mouse button up (WM_LBUTTONUP)当鼠标释放时,我们在WM_LBUTTONUP消息中为指定的按钮发送WM_COMMAND消息,以便处理不同按钮的动作。GetButtonRect(ctp, hwnd, ctp->iActiveButton, &rect, FALSE);if(PtInRect(&rect, pt))
SendMessage(hwnd, WM_COMMAND, ctp->buttons[ctp->iActiveButton].uCmd, 0);
Hit-testing (WM_NCHITTEST)默认情况下,如果鼠标移动到我们插入的按钮上,系统将返回HTCAPTION,我们必须重载该消息处理函数,以便在这种情况下返回HTBORDER,防止系统误处理HTCAPTION动作。for(i = 0; i < ctp->nNumButtons; i++)
{
GetButtonRect(ctp, hwnd, i, &rect, FALSE); if(PtInRect(&rect, pt))
return HTBORDER;
}return CallWindowProc(ctp->wpOldProc, hwnd, msg, wParam, lParam);
最后一步(One last step)
最后还有两个消息需要处理: WM_NCACTIVATE 和 WM_SETTEXT。这些消息绘导致窗口重画,从而覆盖我们插入的按钮。不幸的是这些消息并不通过 WM_NCPAINT 重画!我想这是Windows的一个设计缺陷。解决的办法是在窗口过程处理这些消息之前先把窗口的WS_VISIBLE风格关掉,这样窗口就不绘制重画,在处理完具体的事情之后再打开并由我们自己重画窗口标题栏:取消窗口 WS_VISIBLE 风格,这将组织窗口重画。 调用旧的窗口过程处理消息。 打开窗口的WS_VISIBLE风格。 调用我们自己的函数绘制标题栏。 代码如下所示:LRESULT foobar(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
DWORD ret, dwStyle; dwStyle = GetWindowLong(hwnd, GWL_STYLE); // turn OFF WS_VISIBLE
SetWindowLong(hwnd, GWL_STYLE, dwStyle & ~WS_VISIBLE); // perform the default action, minus painting
ret = CallWindowProc(ctp->wpOldProc, hwnd, msg, wParam, lParam); // turn ON WS_VISIBLE
SetWindowLong(hwnd, GWL_STYLE, dwStyle); // perform custom painting
Caption_NCPaint(hwnd, (HRGN)1); return ret;
}
上面这段代码可以在 WM_SETTEXT 和WM_NCACTIVATE中使用。
结论(Conclusion)
还有另外一种方法可以定制标题栏按钮,你可以简单地用WS_POPUP风格调用CreateWindow函数创建一个按钮(button control)。然后将其放置在窗口标题栏上,就像正常的窗口按钮一样。当然它也能正常工作,但是这种方法并不是最好的。最主要的问题是窗口的焦点,剪切和绘制问题。(完)
在窗体的任意位置绘图。这段代码演示了如何在窗口的标题栏和边框上绘图。 2003-06-07 397
http://www.vb99.com/loaddown.asp?tid=1&pathid=5&Filenames=98
这段代码演示了怎样在窗体的标题栏上添加一个按钮。