访MSN浮出式窗口(2)--窗口区域和自定义绘制菜单

简介: 在此前一篇文章中已经给出了一个访范例。这里在以前的基础上进行进一步的美化工作。(本范例是属于windows 应用程序范畴,即传统的桌面应用程序,开发环境是VC6.0 + Windows SDK)。

          在此前一篇文章中已经给出了一个访范例。这里在以前的基础上进行进一步的美化工作。(本范例是属于windows 应用程序范畴,即传统的桌面应用程序,开发环境是VC6.0 + Windows SDK)。

          (1)给自定义绘制的窗口增加圆角。

          系统中的任何窗口在系统的视角逻辑上都被认为是矩形,然而我们能看到很多应用程序会使用一些具有特殊边缘形状的各种窗口(例如qqmusic的浮出窗口)。在这里我是通过 SetWindowRgn 函数来给一个窗口设置区域。窗口的区域类似PS中的蒙版,位于区域外部的窗体部分将不可见。一个复杂区域实际上是一些互相没有重叠的矩形组成的集合。效果下图所示。

          对于复杂区域的设置我们可以通过一副Bitmap(这里使用的就是浮出窗口的背景图)来得到。即这里规定了洋红色(RGB255,0,255)为一个特殊颜色,因此我们就可以给一个窗口划定成圆角轮廓,使他更美观,这里边框一并也都在背景图来绘制完成了。

          使用 SetWindowRgn 函数时需要注意的是,传递给该函数的HRGN,一旦调用了函数以后,该RGN的维护权就从应用程序移交给了操作系统,也就是说应用程序不应该再对该RGN句柄做任何其他操作了(例如DeleteObject等)。这一点对于我们需要缓存一些复杂区域是非常关键的,例如如果一个窗口需要经常切换区域,而这个区域的生成成本又很高(要遍历一个位图的所有像素),这时我们很自然就会想把得到的区域缓存起来。这里务必注意的是,不能把缓存的RGN的句柄直接传递给 SetWindowRgn 函数,否则这个RGN缓存可能已经会系统回收导致此后将不再有效。正确的做法是,每次更新RGN时,创建一个缓存RGN的副本拷贝传递给该函数。

           

          (2)一个典型的提供三态鼠标反馈的Button。

          一个button对鼠标提供三种不同外观反馈,分别是,普通(鼠标在远处),悬停(鼠标悬浮在按钮上方),按下(鼠标在按钮上按下)。这里我引用了qqmusic中的资源图片,如图2所示。在这里要注意的是,这种反馈具有以下几个特点:

          a。鼠标在按钮上按下,保持按下状态,移动到远处抬起,按钮事件不会被触发。这时按钮对按下期间的鼠标移动提供“普通”和“按下”反馈。

          b。鼠标在远处按下,保持按下状态移动到按钮上方,按钮为悬停态。这时如果鼠标在按钮上方抬起,同样不会触发按钮事件。

          从上面两点非常细节化的设计上(我个人感觉这样的好处主要是为了给用户一种非常细微的可以“撤销”点击按钮的方式,当然这样也就会相应增加了实现的难度)可以看出,按钮事件被触发的条件是:鼠标的按下点和抬起点都位于按钮的“有效点击区域”内。鼠标的按下点和抬起点我们可以在收到WM_LBUTTONDOWNWM_LBUTTONUP消息时得到并更新,同时我们也必须记录鼠标按钮的当前状态(使用一个BOOL变量来记录鼠标是否处于按下状态)。

          还要注意的时,当有多个按钮时,鼠标在其中某个按钮上方按下以后,常规做法时,应用程序内部有一个“追踪对象”就会被设置为该按钮,此后鼠标在按下期间的移动,只有“被追踪”的对象对其提供反馈,其他按钮将不会做任何反应。这个方式非常类似应用程序之间的鼠标捕获。在这个小例子里由于只有一个关闭按钮,所以为了简便,就没有这样去做处理。

          另一点,对这种反馈我们还必须注意到,我们对按钮状态的切换主要是根据MoveMove消息来完成的,而鼠标事件的另外一个特点是:MouseMove消息是断续的,而不是我们感观感受到的“连续”状态。也就是说,当鼠标用非常快的速度移动时,我们可能会得到一些间距很大的离散点。而且我们没有捕获鼠标,而系统只会把消息传递给鼠标“下方”的窗口。因此这样我们上面的按钮三态反应将很可能因为鼠标快速的离开窗口,而使窗口不知道鼠标实际已经离开按钮(由于MouseMove的离散性),从而使按钮停留在一个“中间状态”,而无法复原到Normal状态。这时我们就必须要求系统通知我们一个特殊的鼠标消息,即WM_MOUSELEAVE。我想大概处于性能考虑,微软默认情况下是不给窗口发送MouseLeave通知的,所以我们如果要求系统通知我们WM_MOUSELEAVE消息,就必须调用 TrackMouseEvent 函数,要求系统为我们送达我们需要的消息。这个函数的使用时需要注意的是,Track的有效期是自调用函数开始到我们要求的消息到来之前的期间一直有效,而一旦我们要求的消息已经产生并送达,就算完成了一次Track过程(此后的这种消息,系统就不再发送通知)。因此我们应该把这个函数的调用放到MouseMove消息中进行,即一旦鼠标进入我们的窗口“领空”,我们就不厌其烦的请求系统发送WM_MOUSELEAVE消息。这样我们就能保证不管鼠标移动的多么快,我们都会正确得到通知,从而保证按钮状态可顺利恢复到Normal状态。

           

          (3)自定义绘制菜单。

          在这部分,我参考到了BCMenu的源代码(BC是作者姓名的首字母)。但BCMenu是用MFC类库的,而我的应用程序中是没有采用MFC类库的,所以我就自己写了不携带MFC的C语言版本的一个简要的自定义绘制菜单,即例子代码中Menu.h和Menu.C。

          如果应用程序想要自定义菜单的绘制,那么就相应的要为此必须承担一些责任,例如“测量”和“绘制”MenuItem。菜单的复杂情况决定这并不是一个简单轻松的任务。例如MenuItem有Checked,Unchecked, Grayed, Selected, 等不同状态。细节不再具体叙述了。在例子中的菜单一些配色参考了Ms office 2003和qqmusic。

           

          (4)其他附加功能:

          a。在右下角可显示一个URL,当鼠标点击时,会使用默认浏览器打开显示的网址。默认浏览器的路径是通过查询注册表信息获得的。

          b。对标题的自动截断,如果从某个汉字的两个bytes中间截断,将会造成显示“?”(因为系统不知道这里是什么字符)。这种情况一般是在对于char*这种ASCII码式的C字符串(即MultiByteString)才会出现,因为字符的字节数为1到2不等。如果是Unicode(即WideCharString),因为字符字节数固定,则不存在这种问题。

          因此,如果要做的更好,我们应该在截断时严格的从汉字边界处截断(而不能从某个汉字的中间切断)。对于char*这种ASCII码式的C字符串,汉字和ASCII字符的区别就是 判断起始字节是否大于等于0xa1 ,如果是,则这里是汉字,否则就是普通的ASCII字符(英文字符)。但如果是汉字,我们还是不知道这是属于某汉字的前半还是后半部分(尤其是当连续汉字时)。这时要获取汉字边界,我们必须从字符串开始位置向后一直扫描,直到扫描到这里我们才能准确的把汉字边界划出来。即遇到ascii码就向后移动一格,如果遇到汉字就向后移动两格继续判断。直到判断到和要截断位置距离小于2个bytes为止。这时只有两种可能,如果距离为0,则说明恰好位于字符边界处,可以直接截断字符串。若距离为1byte,则说明处于某个汉字中间,可以把阶段位置前移(切掉当前汉字),或后移(保留当前汉字)一个byte再做截断。

          另外必须注意的是,如果参数是char*,必须先把char强转为unsigned char类型才能和0xa1进行比较(否则汉字字符的byte可能被当作负数看待!)。

          下面是能够正确从汉字边界处把标题自动截断的相应代码:

img_1c53668bcee393edac0d7b3b3daff1ae.gif img_405b18b4b6584ae338e0f6ecaf736533.gif Code_标题自动截断
//设置NotifyWnd标题
void SetNotifyTitle(LPCTSTR title)
{
    unsigned 
int i;
    unsigned 
int maxlength = sizeof(m_NotifyTitle)-1;
    
//需要注意的是,用char*是无法和0xa1比较的,因为会被当作负数!!!
    unsigned char *s=(unsigned char*)title;
    
//置空(重要)
    memset(m_NotifyTitle, 0 ,maxlength+1);
    
//判断是否要截断标题,注意要判断汉字!
    if(strlen(title) > maxlength)
    {
        
//strncpy(m_NotifyTitle, title, maxlength - 5);
        
//为了保证不把汉字从中间阶段,所以我们一个一个字符复制过去
        for(i=0; i < maxlength - 3; i++)
        {
            
if(s[i]>= 0xa1)
            {
                
//是汉字,连续复制2个字节
                m_NotifyTitle[i]=title[i];
                i
++;
                m_NotifyTitle[i]
=title[i];
            }
            
else
            {
                
//是英文,复制一个字节即可
                m_NotifyTitle[i]=title[i];
            }
        }
        
//无需null结束字符(因为此前已经把m_NotifyTitle清零了。

        strcat(m_NotifyTitle, 
""); //追加省略号
    }
    
else
        strcpy(m_NotifyTitle,title);
}

 

 

          最后,是范例代码的下载链接:

          http://files.cnblogs.com/hoodlum1980/NotifyWndDemo2.rar 

          

目录
相关文章
如何创建一个用户看不到的窗口。
如何创建一个用户看不到的窗口。
|
7月前
|
Windows
排错实战——你知道拖动窗口时只显示虚框怎么设置吗?
你知道拖动窗口时只显示虚框怎么设置吗?
|
9月前
|
数据安全/隐私保护 iOS开发 芯片
将任意应用窗口置顶显示,这个工具太强了。
将任意应用窗口置顶显示,这个工具太强了。
|
C# Windows
推荐一个C#开发的窗口扩展菜单,支持系统所有窗口
一个C#开发的窗口扩展项目,采用.NET Framework 4.0开发,支持Windows Xp以及更高版本的系统,同时支持命令模式,可供代码调用。
98 0
推荐一个C#开发的窗口扩展菜单,支持系统所有窗口
|
定位技术 API 开发工具
百度地图开发-显示地图默认界面 03
百度地图开发-显示地图默认界面 03
228 0
百度地图开发-显示地图默认界面 03
QT软件开发: 窗口打开固定在屏幕中间或者右下角
QT软件开发: 窗口打开固定在屏幕中间或者右下角
320 0
PyQt5 技巧篇-窗口置顶设置,如何使窗口始终显示在最前面
PyQt5 技巧篇-窗口置顶设置,如何使窗口始终显示在最前面
1920 0
|
C# Windows
C#代码像QQ的右下角消息框一样,无论现在用户的焦点在哪个窗口,消息框弹出后都不影响焦点的变化,那么有两种方法
你QQ的右下角消息框一样,无论现在用户的焦点在哪个窗口,消息框弹出后都不影响焦点的变化,那么有两种方法:   要么重写需要弹出的窗体的事件: protected override CreateParams CreateParams     {     get     {         const ...
1185 0
|
C#
WPF自定义窗口最大化显示任务栏
原文:WPF自定义窗口最大化显示任务栏 当我们要自定义WPF窗口样式时,通常是采用设计窗口的属性 WindowStyle="None" ,然后为窗口自定义放大,缩小,关闭按钮的样式。 然而这样的话,当通过代码设置窗口(代码如下)放大时,窗口会把任务栏给遮档住。
1185 0
|
C#
[WPF疑难] 模式窗口被隐藏后重新显示时变成了非模式窗口
原文:[WPF疑难] 模式窗口被隐藏后重新显示时变成了非模式窗口                            [WPF疑难] 模式窗口被隐藏后重新显示时变成了非模式窗口                                              周银辉 现象: 大家可以...
1214 0