由于平時很少鍛煉脖子和肩部的肌肉,上周積累的疲勞度爆發了,右鍵和右背疼的厲害。后來去醫院治療了一星期,花了幾百大洋才好。所以深刻體會到程序員的生 活即使在苦逼,也應該注意保持適當的運動。身體恢復后,我就想找一款軟件,類似與鬧鐘的功能。但能以比較強烈的形式提醒自己,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
成成 閱讀(4217)
評論(4) 編輯 收藏 引用