posts - 3,  comments - 4,  trackbacks - 0
由于平时很少锻炼脖子和肩部的肌肉,上周积累的疲劳度爆发了,右键和右背疼的厉害。后来去医院治疗了一星期,花了几百大洋才好。所以深刻体会到程序员的生 活即使在苦逼,也应该注意保持适当的运动。身体恢复后,我就想找一款软件,类似与闹钟的功能。但能以比较强烈的形式提醒自己,it's time to exercise!!!后来,在小众软件发现了FadeTop,能够每隔一段时间已覆盖桌面的形式提醒你休息时间到了。后来想想自己,虽然写了几年的代码 了,但真的没有写出什么实用的工具来,好好的鄙视了自己一番。于是一时冲动,决定从模仿FadeTop开始,于是就有了这篇文章。

后面附有源码,没耐心的朋友可以到最后直接下载

言归正传,我想实现的功能有:
1.每隔一段时间提醒你该休息了
2.提醒的方式为支持透明度的窗口,暂且成为遮罩层吧,遮罩层需要覆盖当前除任务栏之外的区域,并显示提醒的文字
3.遮罩层显示一定的时间,具有一定的透明度,可以看到下面的程序。遮罩层从无到有,再到无即可
4.相关的参数:提醒间隔时间,遮罩层显示时间可以自定义
5.程序不需要在任务栏显示图标,遮罩层上也没有任务操作,程序的图标在托盘中,类似QQ小图标。

想要实现以上的功能,需要解决的问题主要有:
1.如何将窗口大小设置为铺满整个屏幕,并去除任务栏图标
2.程序如何以托盘的形式显示,托盘上的操作
3.如何间隔一段时间提醒
4.遮罩层显示的时候,如何实现从无到有,再逐渐消失
下面逐一分析解决以上问题。

首先,新建一个基于对话框的应用程序,将dialog属性设置为无标题栏。

如何将窗口大小设置为铺满整个屏幕
,并去除任务栏图标
在OnInitDialog函数中,加入如下语句
 1 //获得桌面大小,不包含任务栏等
 2     CRect rc;
 3     ::SystemParametersInfo(SPI_GETWORKAREA,0,(PVOID)&rc,0);
 4     SetWindowPos(NULL,rc.left,rc.top,rc.Width(),rc.Height(),SWP_NOMOVE|SWP_NOREPOSITION);
 5     
 6     //设置透明属性
 7     LONG exstyle = GetWindowLong(m_hWnd,GWL_EXSTYLE);
 8     ::SetWindowLong(m_hWnd, GWL_EXSTYLE, exstyle|0x80000 | (~WS_EX_APPWINDOW) | WS_EX_TOOLWINDOW);
 9     
10     
11     //获得设置透明度的函数指针
12     HINSTANCE hInst = LoadLibrary("User32.DLL");
13     if(hInst)
14     {
15         //取得SetLayeredWindowAttributes函数指针
16         SetLayerOpacity=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes");
17         FreeLibrary(hInst);                
18     }
19     
20     //最小化到系统托盘
21     m_tray.cbSize = sizeof(NOTIFYICONDATA);
22     m_tray.hWnd = this->m_hWnd;
23     m_tray.uID = IDR_OPTION;
24     m_tray.uFlags = NIF_ICON|NIF_TIP|NIF_MESSAGE;
25     m_tray.hIcon = AfxGetApp()->LoadIcon(IDI_ICON_TIP);
26     strcpy (m_tray.szTip, "提醒闹铃");
27     m_tray.uCallbackMessage = UM_TRAYNOTIFICATION;
28     
29     if(!Shell_NotifyIcon(NIM_ADD, &m_tray))
30     {
31         MessageBox("启动失败");
32         CDialog::OnCancel();
33     }
34     
35     //启动定时器
36     SetTimer(1,m_timespan,NULL);
上面的注释虽然简单,但还算比较清晰,其中特别说明的有下面几点:
1.其中SetWindowLong函数完成了两件事情:
      a.去除了任务栏的图标,通过去除属性WS_EX_APPWINDOW,添加属性WS_EX_TOOLWINDOW实现;需要特别注意的是,在这里用 ShowWindow(SW_HIDE)并不能隐藏对话框,具体原因自己百度下,我采用重载OnNcPaint的方式,在初始化时隐藏对话框(为什么是两 次,本人至今还不是很明白,希望知道的朋友msg我)
     
void CReminderDlg::OnNcPaint()
{
    
static int i=2;
    
if(i>0)
    {
        i
--;
        ShowWindow(SW_HIDE);
    }
}
      b.将对话框设置为遮罩层,这样可以设置透明度,通过添加属性0x80000实现,在vs中,0x80000对应的属性是WS_EX_LAYED,但在vc中此变量似乎没有定义。
2.SetLayerOpacity是自定义函数指针,其定义为
typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
MYFUNC SetLayerOpacity 
= NULL;
用来获取SetLayeredWindowAttributes函数的指针,此函数用于设置遮罩层的透明度,先保存起来,以后使用
3.Shell_NotifyIcon函数用于将自定义的图标加入到右下角的托盘中。
4.SetTimer(1,m_timespan,NULL)是每隔m_timespan时间提醒一次的定时器

托盘和程序的通信
在这里,我们主要通过托盘实现程序的配置和退出。具体为:在托盘图标上点击右键,出现弹出菜单,包含设定、退出等选项。点击设定,弹出设定对话框,包含对与提醒间隔和遮罩层显示时间,以及遮罩层颜色的设定,保存后立即生效。点击退出菜单,则退出程序。
托盘的操作采用自定义消息:
在类定义中加入消息:afx_msg LRESULT OnTrayNotify(WPARAM wParm, LPARAM lParm);
在类实现头部加入:#define UM_TRAYNOTIFICATION (WM_USER+100)
在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间加入消息映射:ON_MESSAGE(UM_TRAYNOTIFICATION,OnTrayNotify)
然后实现托盘对于邮件的响应函数:
LRESULT CReminderDlg::OnTrayNotify(WPARAM wParm, LPARAM lParm)
{
    
if(wParm!=m_tray.uID || lParm!=WM_RBUTTONDOWN)
    {
        
return 0;
    }

    
//加载菜单
    CMenu mu;
    
if(!mu.LoadMenu(IDR_OPTION))
    {
        
return 0;
    }

    CMenu 
*pSubMenu = mu.GetSubMenu(0);
    
if(!pSubMenu)
    {
        
return 0;
    }

    
    
//设置默认菜单项
    ::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE);
    
    
//获取鼠标位置
    CPoint mouse;
    GetCursorPos(
&mouse);
    
    
//设置快捷菜单
    
//::SetForegroundWindow(m_tray.hWnd);
    ::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0, m_tray.hWnd, NULL);
    

    
return 0;
}
IDR_OPTION是自定义的菜单资源,其中包含了设定、退出等菜单。其对应的消息处理函数是:
void CReminderDlg::OnQuit()
{
    // TODO: Add your command handler code here
    Shell_NotifyIcon(NIM_DELETE, &m_tray);
    CDialog::OnCancel();
}

void CReminderDlg::OnSet()
{
    // TODO: Add your command handler code here
    
    CRSetting dlg;
    dlg.m_inteval = m_timespan/1000/60;
    dlg.m_show = m_timeshow/1000;
    dlg.m_color = m_layerColor;

    if(IDOK==dlg.DoModal())
    {
        KillTimer(1);
        m_timespan = dlg.m_inteval*60*1000;
        m_timeshow = dlg.m_show*1000;
        m_layerColor = dlg.m_color;
        SetEnvData();
        SetTimer(1,m_timespan,NULL);
    }    
}
其中需要说明的是:在保存的时候,需要删除旧的计时器,启动新的计时器。

间隔一段时间提醒
从上面的代码可以看出,是通过计时器1来实现的,这个的代码和第四个问题的在一起,所以一会上

遮罩层的显示,从无到右再消失
先看代码
//背景刷新的时间间隔
#define TIME_REFRESH    100
//最大的透明度
#define MAX_OPACITY        200

void CReminderDlg::OnTimer(UINT nIDEvent) 
{
    
// TODO: Add your message handler code here and/or call default
    if(nIDEvent==1)
    {
        
//时间到,提醒
        ::SetWindowPos(m_hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE|SWP_NOSIZE|SWP_SHOWWINDOW);
        SetLayerOpacity(m_hWnd,
0,0,2);
        KillTimer(
1);
        SetTimer(
2,TIME_REFRESH,NULL);
        m_timepass 
= 0;
    }
    
else if(nIDEvent==2)
    {
        
//显示提醒层,从无到逐渐清晰再到无
        m_timepass += TIME_REFRESH;
        
if(m_timepass==m_timeshow)
        {
            
//显示时间到,隐藏窗口,再次启动计时
            KillTimer(2);
            ShowWindow(SW_HIDE);
            SetTimer(
1,m_timespan,NULL);
        }
        
else if(SetLayerOpacity!=NULL)
        {
            
//更改透明度
            ULONG half = m_timeshow/2;
            BYTE op;
            
if(m_timepass<half)
            {
                
//提示层从无到清晰的过程
                op = (BYTE)(m_timepass*MAX_OPACITY / half);
            }
            
else
            {
                op 
= (BYTE)(MAX_OPACITY - (m_timepass-half)*MAX_OPACITY / half);
            }
        
            
if(!SetLayerOpacity(m_hWnd, 0, op,2))
            {
                
//MessageBox(str);
            }
        }
        
    }

    CDialog::OnTimer(nIDEvent);
}
如上所示,当计时器1时间到的时候,用SetWindowPos显示遮罩层,并取消定时器1,设置计时器2,计时器2是用来显示遮罩层的,遮罩层2显示的时间到达设定的显示时间,则重新隐藏窗口,启动定时器1。其中两点说明:
1.透明度是从0-200再到0的,当为0的时候,意味着完全透明。
2.SetLayerOpacity是函数指针,指向函数SetLayeredWindowAttributes,用来设置透明度。

最后,在OnPaint()函数中完成遮罩层的绘制:
 1 void CReminderDlg::OnPaint() 
 2 {
 3     if (IsIconic())
 4     {
 5         CPaintDC dc(this); // device context for painting
 6 
 7         SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);
 8 
 9         // Center icon in client rectangle
10         int cxIcon = GetSystemMetrics(SM_CXICON);
11         int cyIcon = GetSystemMetrics(SM_CYICON);
12         CRect rect;
13         GetClientRect(&rect);
14         int x = (rect.Width() - cxIcon + 1/ 2;
15         int y = (rect.Height() - cyIcon + 1/ 2;
16 
17         // Draw the icon
18         dc.DrawIcon(x, y, m_hIcon);
19     }
20     else
21     {
22         CPaintDC dc(this);
23         CRect rect;
24         GetClientRect(&rect);
25         dc.FillSolidRect(rect,m_layerColor);
26 
27         //设置字体
28         CFont font;
29         font.CreateFont(
30             48,                        // nHeight
31             0,                         // nWidth
32             0,                         // nEscapement
33             0,                         // nOrientation
34             FW_BOLD,                    // nWeight
35             FALSE,                     // bItalic
36             FALSE,                     // bUnderline
37             0,                         // cStrikeOut
38             GB2312_CHARSET,              // nCharSet
39             OUT_DEFAULT_PRECIS,        // nOutPrecision
40             CLIP_DEFAULT_PRECIS,       // nClipPrecision
41             DEFAULT_QUALITY,           // nQuality
42             DEFAULT_PITCH | FF_SWISS,  // nPitchAndFamily
43             "宋体");                 // lpszFacename
44 
45         CFont* def_font = dc.SelectObject(&font);
46         dc.SetTextColor(RGB(
47             abs(255-GetRValue(m_layerColor)),
48             abs(255-GetGValue(m_layerColor)),
49             abs(255-GetBValue(m_layerColor))
50             ));
51         
52         //得到字体尺寸
53         CSize sz = dc.GetTextExtent(m_tip); 
54         dc.TextOut(rect.left+(rect.Width()-sz.cx)/2, rect.top+(rect.Height()-sz.cy)/2, m_tip);
55         
56         dc.SelectObject(def_font);
57 
58         CDialog::OnPaint();
59     }
60 }
需要注意的是,在将提醒文字绘制在遮罩层上时,使用的是相反的颜色,这样有最大的对比度。

另外,我将配置信息保存在注册表中,因为只有3个数据,启动时读注册表,函数如下所示
//从注册表读出配置信息
void CReminderDlg::GetEnvData()
{
    HKEY hkey;
    LONG iRet;

    iRet 
= RegOpenKeyEx(HKEY_LOCAL_MACHINE, "software\\reminder"0, KEY_READ|KEY_QUERY_VALUE, &hkey);
    
if(ERROR_SUCCESS != iRet)
    {
        
//说明数据项不存在,创建
        m_layerColor = RGB(0,0,255);
        m_timespan 
= 3600000;
        m_timeshow 
= 10000;
        SetEnvData();
        
return;
    }
    
    
//数据项存在,则读出值
    DWORD len = sizeof(DWORD);
    iRet 
= RegQueryValueEx(hkey,"rcolor",NULL,NULL,(BYTE*)&m_layerColor,&len);
    
if(ERROR_SUCCESS != iRet)
    {
        m_layerColor 
= RGB(0,0,255);
    }

    iRet 
= RegQueryValueEx(hkey,"timespan",NULL,NULL,(BYTE*)&m_timespan,&len);
    
if(ERROR_SUCCESS != iRet)
    {
        m_timespan 
= 3600000;
    }

    iRet 
= RegQueryValueEx(hkey,"timeshow",NULL,NULL,(BYTE*)&m_timeshow,&len);
    
if(ERROR_SUCCESS != iRet)
    {
        m_timeshow 
= 10000;
    }

    RegCloseKey(hkey);
}

//配置信息写入注册表
void CReminderDlg::SetEnvData()
{
    HKEY hkey;
    LONG iRet;
    iRet 
= RegCreateKeyEx(HKEY_LOCAL_MACHINE,"software\\reminder",
        
0,NULL,0,KEY_ALL_ACCESS,NULL,&hkey,NULL);
    
if(ERROR_SUCCESS != iRet)
    {
        
return;
    }

    
//写入颜色信息
    iRet = RegSetValueEx(hkey,"rcolor",NULL,REG_DWORD,(BYTE*)&m_layerColor,sizeof(DWORD));
    
if(ERROR_SUCCESS != iRet)
    {
        RegCloseKey(hkey);
        
return;
    }

    
//写入提醒时间间隔
    iRet = RegSetValueEx(hkey,"timespan",NULL,REG_DWORD,(BYTE*)&m_timespan,sizeof(DWORD));
    
if(ERROR_SUCCESS != iRet)
    {
        RegCloseKey(hkey);
        
return;
    }

    
//写入提示信息显示秒数
    iRet = RegSetValueEx(hkey,"timeshow",NULL,REG_DWORD,(BYTE*)&m_timeshow,sizeof(DWORD));
    
if(ERROR_SUCCESS != iRet)
    {
        RegCloseKey(hkey);
        
return;
    }

    RegCloseKey(hkey);

    
return;
}
当然,此程序你也可以通过注册表,设置为开机自动启动。

以上是一个很粗糙的程序,大家可以在此基础上加上自己喜欢的功能,下面附上源码:
定时提醒工具

运行截图:



最后与大家共勉:无论多忙,都记得关爱自己的身体,做适当的运动。
posted on 2011-08-14 07:14 成成 阅读(3873) 评论(4)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
【推荐】超50万行VC++源码: 大型组态工控、电力仿真CAD与GIS源码库
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理