2.实验室打卡精灵2.0-单实例化、开机启动、托盘操作、程序启动时隐藏主窗口

来源:互联网 发布:文件存入阿里云oss 编辑:程序博客网 时间:2024/05/14 12:53

老规矩,上一张图片


最近对实验室打卡精灵做了一些优化,基本上达到了最初设想的功能。

现在的功能包括:

1.自定义早上、中午、下午、晚上的打卡时间

2.软件单实例化,即如果已经运行了一次再次运行会弹出“应用程序已经在运行”的提示

3.软件可选择开机自动启动

4.软件开始的时候提醒打卡,如果选择了开机自启动可以实现开机时候提示打卡

5.关机的时候提示打卡


在这里记下一些主要和通用功能的实现

1.程序单实例化

所谓程序单实例化就是说一个程序如果已经打开了,那么再次打开时会提醒该程序已经打开或显示已经打开的程序窗口

要实现这样的功能,我们自然想到给程序设置一个标记,第一次打开时设置状态打开,再次打开检测这个标志状态就可以判断当前是否有这个程序的实例在运行。但是这样有两个问题是需要我们解决的:

1.不同的程序的这个标记不能混乱

2.相同程序进程间是共享这个标记的

对于第一点,在VC6中我们采用微软提供的GUIDGEN.EXE工具(在VC6安装目录Microsoft Visual Studio\Common\Tools中)来生成全局唯一的标志,在VS2008中也可采用CRegKey::QueryGUIDValue函数来生成唯一的标志

对于第二点,我们可以使用全局内核对象或全局原子表(Atom)或进程共享单元(Shared SECTION)来实现

这里在VC6 MFC中演示使用全局内核对象,使用GUIDGEN.EXE工具来产生全局标志

代码如下

//定义全局标识号#define GUID_FLAG TEXT("0x70d56d85, 0xbb97, 0x435d, 0x9d, 0x51, 0x85, 0x89, 0x5e, 0x51, 0x3d, 0x1a")CMy20131221_VC6_MFC_App::CMy20131221_VC6_MFC_App(){//初始化内核对象指针m_OneHandle = NULL;}CMy20131221_VC6_MFC_App theApp;BOOL CMy20131221_VC6_MFC_App::InitInstance(){//创建内核对象,标记程序已经打开m_OneHandle = ::CreateMutex(NULL, FALSE, GUID_FLAG);if (ERROR_ALREADY_EXISTS == GetLastError()){::MessageBox(NULL, TEXT("应用程序已经在运行"), TEXT("警告"), MB_OK | MB_ICONWARNING);return FALSE;}#ifdef _AFXDLLEnable3dControls();#elseEnable3dControlsStatic();#endifCMy20131221_VC6_MFC_Dlg dlg;m_pMainWnd = &dlg;int nResponse = dlg.DoModal();if (nResponse == IDOK){}else if (nResponse == IDCANCEL){}return FALSE;}int CMy20131221_VC6_MFC_App::ExitInstance() {//释放内核对象if (NULL != m_OneHandle){CloseHandle(m_OneHandle);}return CWinApp::ExitInstance();}
可以看到单实例化流程为:定义一个全局标志字符串GUID_FLAG,在初始化实例时创建和判断全局内核对象,如果全局对象存在则提示应用程序已经打开,记得在程序退出实例时释放内核对象。
这里在判断程序实例已经打开后弹出一个提示对话框,如果我们想实现判断程序实例已经打开后显示原来窗口怎么办呢。首先我们得想办法在获得判断出实例已经打开后要显示的窗口的句柄,显然以上介绍的几种方法都做不到这一点。以上的方法都是在窗口外设置一个全区标志,这些标志和窗口扯不上关系,当然我们也别想通过他们获得窗口句柄。

下面,我们换一种办法,以窗口自身作为全局标志,这个可能相对上面几种方法难理解一些,代码如下:

1.在Dlg的OnInitDialog函数中加入

SetProp(m_hWnd, g_key, g_value);
2.在App中代码如下

//定义全局对象CString g_key = TEXT("0x70d56d85, 0xbb97, 0x435d, 0x9d, 0x51, 0x85, 0x89, 0x5e, 0x51, 0x3d, 0x1a") ;HANDLE g_value = HANDLE(1);CMy20131221_VC6_MFC_App::CMy20131221_VC6_MFC_App(){}CMy20131221_VC6_MFC_App theApp;BOOL CMy20131221_VC6_MFC_App::InitInstance(){//遍历各种窗口HWND oldHwnd=NULL;EnumWindows(EnumWindowsProc, (LPARAM)&oldHwnd);if (NULL != oldHwnd){AfxMessageBox("程序已经在运行,将显示其主窗口");::ShowWindow(oldHwnd, SW_NORMAL);::SetForegroundWindow(oldHwnd);return FALSE;}#ifdef _AFXDLLEnable3dControls();#elseEnable3dControlsStatic();#endifCMy20131221_VC6_MFC_Dlg dlg;m_pMainWnd = &dlg;int nResponse = dlg.DoModal();if (nResponse == IDOK){}else if (nResponse == IDCANCEL){}return FALSE;}int CMy20131221_VC6_MFC_App::ExitInstance() {::RemoveProp(m_pMainWnd->GetSafeHwnd(), g_key);return CWinApp::ExitInstance();}BOOL CALLBACK EnumWindowsProc(  HWND hwnd,   LPARAM lParam ){if (GetProp(hwnd, g_key) == g_value){*((HWND *)lParam) =hwnd;return FALSE;}else{return TRUE;}}
可以看到这种方法在已经打开的窗口的属性列表中设置一个唯一属性(SetProp),在打开实例时通过遍历当前所有窗口,直到找到刚刚设置了唯一属性的那个窗口就证明已经打开了程序,一旦找到已经打开了的窗口将他的句柄通过lParam传回并显示。

测试发现按主窗口上的隐藏窗口按钮后再次打开程序会显示刚才隐藏的窗口。
至于全局原子表和进程共享单元的方法大家可以自行百度吧。

完整源代码下载地址

2.开机自动启动

常见的开机自动启动是通过注册表完成的。
一般来说是将要开机自动启动的软件可执行文件路径写到如下图的路径处即可

代码如下
void CMyDlg::OnAutorun() {// TODO: Add your control notification handler code hereif(ERROR_SUCCESS == m_regKey.Open(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"))){TCHAR szBuffer[MAX_PATH];GetModuleFileName(AfxGetInstanceHandle(), szBuffer, MAX_PATH);if (ERROR_SUCCESS == m_regKey.SetValue(szBuffer, TEXT("Jimwen-Autorun"))){AfxMessageBox(TEXT("设置开机自动启动成功"));}m_regKey.Close();}}void CMyDlg::OnCancelautorun() {// TODO: Add your control notification handler code hereif(ERROR_SUCCESS == m_regKey.Open(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Run"))){if (ERROR_SUCCESS == m_regKey.DeleteValue(TEXT("Jimwen-Autorun"))){AfxMessageBox(TEXT("取消设置开机自动启动成功"));}m_regKey.Close();}}
完整源代码下载链接

3.程序托盘化及托盘消息

很多程序我们在选择关闭的时候不是关闭了窗口,而只是将窗口隐藏并显示成托盘。当双击托盘会显示窗口,右键会弹出对应菜单,调用相应函数即可,值得注意的是,如下代码中部分注释调的内容在VS2008中有效,在VC6中无效,代码如下
void CMyDlg::OnTotray() {// TODO: Add your control notification handler code here//设置托盘设置结构体参数m_nid.cbSize = sizeof(NOTIFYICONDATA);//托盘设置结构体大小m_nid.hWnd = GetSafeHwnd();//设置图盘图标对应的窗口句柄m_nid.uID = IDR_MAINFRAME;//托盘图标IDm_nid.hIcon = LoadIcon(AfxGetInstanceHandle(),   MAKEINTRESOURCE(IDR_MAINFRAME));//托盘图标句柄m_nid.uFlags = /*NIF_INFO|*/NIF_ICON|NIF_TIP|NIF_MESSAGE;//托盘显示气泡,图标,鼠标悬停提示,接受托盘消息m_nid.uCallbackMessage = WM_TRAYMESSAGE;//设置操作托盘图标时的消息// m_nid.uTimeout= 500;//设置托盘化时的提示信息(m_nid.szInfo)的显示时间// m_nid.dwInfoFlags = NIF_USER;//设置气泡框的图标lstrcpy(m_nid.szTip,   TEXT("托盘提示"));//设置鼠标悬停提示// lstrcpy(m_nid.szInfo,   TEXT("泡泡内容,VC6无效"));//设置托盘化时的提示信息(m_nid.szInfo)// lstrcpy(m_nid.szInfoTitle,   TEXT("泡泡提示,VC6无效"));//设置托盘化时的提示信息(m_nid.szInfo)的标题//在托盘区添加图标if(Shell_NotifyIcon(NIM_ADD, &m_nid)) {ShowWindow(SW_HIDE);//创建托盘图标成功则隐藏主窗口}else{MessageBox(TEXT("创建托盘图标失败"), TEXT("错误"), MB_OK) ;}}void CMyDlg::OnDeletetray() {// TODO: Add your control notification handler code here//删除托盘图标if(Shell_NotifyIcon(NIM_DELETE,&m_nid)) {}else{MessageBox(TEXT("删除托盘图标失败"), TEXT("错误"), MB_OK) ;}}LRESULT CMyDlg::OnTrayMessage( WPARAM wParam, LPARAM lParam ){switch(lParam)//根据lParam判断相对应的事件{case WM_LBUTTONDBLCLK://如果左键双击托盘图标,则显示设置窗体ShowWindow(SW_NORMAL);break;case WM_RBUTTONUP://如果右键菜单弹起,则弹出菜单CPoint pos;GetCursorPos(&pos);CMenu menu;menu.LoadMenu(IDR_MAINMENU);CMenu *m_pMenu = menu.GetSubMenu(0);if(m_pMenu != NULL){SetForegroundWindow();//加这句是为了鼠标点击其他地方时,弹出的菜单能够消失m_pMenu->TrackPopupMenu(TPM_RIGHTBUTTON|TPM_RIGHTALIGN, pos.x+1, pos.y+1, this);}break;}return 0;}void CMyDlg::OnShowmain() {// TODO: Add your command handler code hereShowWindow(SW_NORMAL);}void CMyDlg::OnExitapp() {// TODO: Add your command handler code herePostQuitMessage(0);}
这里的托盘消息WM_TRAYMESSAGE是自定义的。
完整源代码下载链接

4.程序启动时隐藏主窗口

有些程序需要实现这样的功能,当程序打开时默认主窗口是隐藏的。这个功能在主窗口为文档类窗口中很好实现,就是在窗口创建完成后ShowWindow的参数设为SW_HIDE即可,但是在主窗口为对话框的程序中这样是行不通的,因为它在windows的处理回调函数上封装了一层对话框管理器使其丧失了一定的灵活性,默认就显示了,但是我们可以拖过完美的Hack来实现这个效果。
对比在文档类中先创建窗口再显示,这里的对话框把这两个过程包含在一起了,那么我们就试图把这两个过程分离,这里采用MFC做演示。
首先把MFC默认的InitInstance()中模态对话框换成非模态对话框,因为模态对话框会挂起整个线程让我们无法对他做后续操作。
代码如下:
//采用非模态对话框便于程序一启动就隐藏对话框m_pMainWnd = new CMyDlg;((CMyDlg *)m_pMainWnd)->Create(IDD_MY_DIALOG, NULL);return TRUE;

这一步完成了创建,这时候编译发现默认会显示对话框,接下来就是隐藏显示了,自然我们想和文档类一样创建完后立即隐藏,可是如果我们真的这样做会发现窗口先显示后隐藏即有一个窗口一闪而过的画面,这完全不能忍受,这个原因刚才也说了对话框创建后即显示了,这是由对话框管理器决定的,我们不能更改。那么我们就想既然你显示了,那么必须PAINT整个界面,那么此时我在你Paint的时候隐藏掉整个界面不就得了,由于窗口绘制先调用WM_NCPAINT消息,所以我们在启动窗口时的WM_NCPAINT消息中隐藏窗口,代码如下:
void CMyDlg::OnNcPaint() {// TODO: Add your message handler code here//程序一启动就隐藏对话框static BOOL bISHideWindow = TRUE;if (TRUE == bISHideWindow){ShowWindow(SW_HIDE);bISHideWindow = FALSE;}}
测试效果是完美的。
这里需要指出的是,模态对话框变为非模态对话框后,我们按原来的确定和取消按钮只是隐藏窗口,要退出程序必须调用PostQuitMessage()等函数显式退出。
完整源代码下载链接

最新的实验室打卡精灵源代码和可执行文件链接

原创,转载请注明来自http://blog.csdn.net/wenzhou1219
0 0