由于平時(shí)很少鍛煉脖子和肩部的肌肉,上周積累的疲勞度爆發(fā)了,右鍵和右背疼的厲害。后來(lái)去醫(yī)院治療了一星期,花了幾百大洋才好。所以深刻體會(huì)到程序員的生 活即使在苦逼,也應(yīng)該注意保持適當(dāng)?shù)倪\(yùn)動(dòng)。身體恢復(fù)后,我就想找一款軟件,類似與鬧鐘的功能。但能以比較強(qiáng)烈的形式提醒自己,it's time to exercise!!!后來(lái),在小眾軟件發(fā)現(xiàn)了FadeTop,能夠每隔一段時(shí)間已覆蓋桌面的形式提醒你休息時(shí)間到了。后來(lái)想想自己,雖然寫了幾年的代碼 了,但真的沒有寫出什么實(shí)用的工具來(lái),好好的鄙視了自己一番。于是一時(shí)沖動(dòng),決定從模仿FadeTop開始,于是就有了這篇文章。
后面附有源碼,沒耐心的朋友可以到最后直接下載 言歸正傳,我想實(shí)現(xiàn)的功能有: 1.每隔一段時(shí)間提醒你該休息了 2.提醒的方式為支持透明度的窗口,暫且成為遮罩層吧,遮罩層需要覆蓋當(dāng)前除任務(wù)欄之外的區(qū)域,并顯示提醒的文字 3.遮罩層顯示一定的時(shí)間,具有一定的透明度,可以看到下面的程序。遮罩層從無(wú)到有,再到無(wú)即可 4.相關(guān)的參數(shù):提醒間隔時(shí)間,遮罩層顯示時(shí)間可以自定義 5.程序不需要在任務(wù)欄顯示圖標(biāo),遮罩層上也沒有任務(wù)操作,程序的圖標(biāo)在托盤中,類似QQ小圖標(biāo)。 想要實(shí)現(xiàn)以上的功能,需要解決的問(wèn)題主要有: 1.如何將窗口大小設(shè)置為鋪滿整個(gè)屏幕,并去除任務(wù)欄圖標(biāo) 2.程序如何以托盤的形式顯示,托盤上的操作 3.如何間隔一段時(shí)間提醒 4.遮罩層顯示的時(shí)候,如何實(shí)現(xiàn)從無(wú)到有,再逐漸消失 下面逐一分析解決以上問(wèn)題。 首先,新建一個(gè)基于對(duì)話框的應(yīng)用程序,將dialog屬性設(shè)置為無(wú)標(biāo)題欄。 如何將窗口大小設(shè)置為鋪滿整個(gè)屏幕,并去除任務(wù)欄圖標(biāo) 在OnInitDialog函數(shù)中,加入如下語(yǔ)句 1 //獲得桌面大小,不包含任務(wù)欄等
上面的注釋雖然簡(jiǎn)單,但還算比較清晰,其中特別說(shuō)明的有下面幾點(diǎn):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 //設(shè)置透明屬性 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 //獲得設(shè)置透明度的函數(shù)指針 12 HINSTANCE hInst = LoadLibrary("User32.DLL"); 13 if(hInst) 14 { 15 //取得SetLayeredWindowAttributes函數(shù)指針 16 SetLayerOpacity=(MYFUNC)GetProcAddress(hInst, "SetLayeredWindowAttributes"); 17 FreeLibrary(hInst); 18 } 19 20 //最小化到系統(tǒng)托盤 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("啟動(dòng)失敗"); 32 CDialog::OnCancel(); 33 } 34 35 //啟動(dòng)定時(shí)器 36 SetTimer(1,m_timespan,NULL); 1.其中SetWindowLong函數(shù)完成了兩件事情: a.去除了任務(wù)欄的圖標(biāo),通過(guò)去除屬性WS_EX_APPWINDOW,添加屬性WS_EX_TOOLWINDOW實(shí)現(xiàn);需要特別注意的是,在這里用 ShowWindow(SW_HIDE)并不能隱藏對(duì)話框,具體原因自己百度下,我采用重載OnNcPaint的方式,在初始化時(shí)隱藏對(duì)話框(為什么是兩 次,本人至今還不是很明白,希望知道的朋友msg我) void CReminderDlg::OnNcPaint()
b.將對(duì)話框設(shè)置為遮罩層,這樣可以設(shè)置透明度,通過(guò)添加屬性0x80000實(shí)現(xiàn),在vs中,0x80000對(duì)應(yīng)的屬性是WS_EX_LAYED,但在vc中此變量似乎沒有定義。{ static int i=2; if(i>0) { i--; ShowWindow(SW_HIDE); } } 2.SetLayerOpacity是自定義函數(shù)指針,其定義為 typedef BOOL (WINAPI *MYFUNC)(HWND,COLORREF,BYTE,DWORD);
用來(lái)獲取SetLayeredWindowAttributes函數(shù)的指針,此函數(shù)用于設(shè)置遮罩層的透明度,先保存起來(lái),以后使用MYFUNC SetLayerOpacity = NULL; 3.Shell_NotifyIcon函數(shù)用于將自定義的圖標(biāo)加入到右下角的托盤中。 4.SetTimer(1,m_timespan,NULL)是每隔m_timespan時(shí)間提醒一次的定時(shí)器 托盤和程序的通信 在這里,我們主要通過(guò)托盤實(shí)現(xiàn)程序的配置和退出。具體為:在托盤圖標(biāo)上點(diǎn)擊右鍵,出現(xiàn)彈出菜單,包含設(shè)定、退出等選項(xiàng)。點(diǎn)擊設(shè)定,彈出設(shè)定對(duì)話框,包含對(duì)與提醒間隔和遮罩層顯示時(shí)間,以及遮罩層顏色的設(shè)定,保存后立即生效。點(diǎn)擊退出菜單,則退出程序。 托盤的操作采用自定義消息: 在類定義中加入消息:afx_msg LRESULT OnTrayNotify(WPARAM wParm, LPARAM lParm); 在類實(shí)現(xiàn)頭部加入:#define UM_TRAYNOTIFICATION (WM_USER+100) 在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之間加入消息映射:ON_MESSAGE(UM_TRAYNOTIFICATION,OnTrayNotify) 然后實(shí)現(xiàn)托盤對(duì)于郵件的響應(yīng)函數(shù): LRESULT CReminderDlg::OnTrayNotify(WPARAM wParm, LPARAM lParm)
IDR_OPTION是自定義的菜單資源,其中包含了設(shè)定、退出等菜單。其對(duì)應(yīng)的消息處理函數(shù)是:{ 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; } //設(shè)置默認(rèn)菜單項(xiàng) ::SetMenuDefaultItem(pSubMenu->m_hMenu, 0, TRUE); //獲取鼠標(biāo)位置 CPoint mouse; GetCursorPos(&mouse); //設(shè)置快捷菜單 //::SetForegroundWindow(m_tray.hWnd); ::TrackPopupMenu(pSubMenu->m_hMenu, 0, mouse.x, mouse.y, 0, m_tray.hWnd, NULL); return 0; } void CReminderDlg::OnQuit()
其中需要說(shuō)明的是:在保存的時(shí)候,需要?jiǎng)h除舊的計(jì)時(shí)器,啟動(dòng)新的計(jì)時(shí)器。{ // 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); } } 間隔一段時(shí)間提醒 從上面的代碼可以看出,是通過(guò)計(jì)時(shí)器1來(lái)實(shí)現(xiàn)的,這個(gè)的代碼和第四個(gè)問(wèn)題的在一起,所以一會(huì)上 遮罩層的顯示,從無(wú)到右再消失 先看代碼 //背景刷新的時(shí)間間隔
如上所示,當(dāng)計(jì)時(shí)器1時(shí)間到的時(shí)候,用SetWindowPos顯示遮罩層,并取消定時(shí)器1,設(shè)置計(jì)時(shí)器2,計(jì)時(shí)器2是用來(lái)顯示遮罩層的,遮罩層2顯示的時(shí)間到達(dá)設(shè)定的顯示時(shí)間,則重新隱藏窗口,啟動(dòng)定時(shí)器1。其中兩點(diǎn)說(shuō)明:#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) { //時(shí)間到,提醒 ::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) { //顯示提醒層,從無(wú)到逐漸清晰再到無(wú) m_timepass += TIME_REFRESH; if(m_timepass==m_timeshow) { //顯示時(shí)間到,隱藏窗口,再次啟動(dòng)計(jì)時(shí) 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) { //提示層從無(wú)到清晰的過(guò)程 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.透明度是從0-200再到0的,當(dāng)為0的時(shí)候,意味著完全透明。 2.SetLayerOpacity是函數(shù)指針,指向函數(shù)SetLayeredWindowAttributes,用來(lái)設(shè)置透明度。 最后,在OnPaint()函數(shù)中完成遮罩層的繪制: 1 void CReminderDlg::OnPaint()
需要注意的是,在將提醒文字繪制在遮罩層上時(shí),使用的是相反的顏色,這樣有最大的對(duì)比度。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 //設(shè)置字體 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 } 另外,我將配置信息保存在注冊(cè)表中,因?yàn)橹挥?個(gè)數(shù)據(jù),啟動(dòng)時(shí)讀注冊(cè)表,函數(shù)如下所示 //從注冊(cè)表讀出配置信息
當(dāng)然,此程序你也可以通過(guò)注冊(cè)表,設(shè)置為開機(jī)自動(dòng)啟動(dòng)。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) { //說(shuō)明數(shù)據(jù)項(xiàng)不存在,創(chuàng)建 m_layerColor = RGB(0,0,255); m_timespan = 3600000; m_timeshow = 10000; SetEnvData(); return; } //數(shù)據(jù)項(xiàng)存在,則讀出值 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); } //配置信息寫入注冊(cè)表 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; } //寫入提醒時(shí)間間隔 iRet = RegSetValueEx(hkey,"timespan",NULL,REG_DWORD,(BYTE*)&m_timespan,sizeof(DWORD)); if(ERROR_SUCCESS != iRet) { RegCloseKey(hkey); return; } //寫入提示信息顯示秒數(shù) iRet = RegSetValueEx(hkey,"timeshow",NULL,REG_DWORD,(BYTE*)&m_timeshow,sizeof(DWORD)); if(ERROR_SUCCESS != iRet) { RegCloseKey(hkey); return; } RegCloseKey(hkey); return; } 以上是一個(gè)很粗糙的程序,大家可以在此基礎(chǔ)上加上自己喜歡的功能,下面附上源碼: 定時(shí)提醒工具 運(yùn)行截圖: 最后與大家共勉:無(wú)論多忙,都記得關(guān)愛自己的身體,做適當(dāng)?shù)倪\(yùn)動(dòng)。 |
|