用vc.net实现记录开关机时间的程序

虽然现在电脑大幅降价,但是多人公用一台电脑的情况仍然不少,特别是学生一族。有时候自己由于某种需要要知道别人什么时候使用这台电脑,那该怎么办呢?去网上找一个?但是不一定符合自己的要求,不如发扬DIY精神,自己做一个。那么就用VC.net来DIY一个吧。
    编程思想:记录开机时间比较容易,只需要让程序随系统启动,启动时记录一个开机时间,关机时记录关机时间就可以了。
    在正式开始之前,首先得了解这个小程序的编写步骤,让我们一步一步来:
  1. 实现开机自运行
  2. 实现运行时自动隐藏
  3. 实现可以热键呼出
  4. 实现记录时间
  5. 实现关机时记录时间

1、实现开机自运行

    我在csdn网站上经常看到有人问这样的问题,其实要实现开机时就自动运行自己的程序并不难。在注册表的HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Run键下可以看到有一系列键值,它们都是开机自动运行的软件的路径。那么我们要做的就是编程实现将我们的程序的路径也添加到这个键值下,就搞定了。著名的“windows优化大师”也就是用去掉不必要的开机运行软件这个方法来实现开机速度优化的。
    既然要读写注册表,就要用到两个重要的操作注册表的函数:RegOpenKey()和RegSetValueEx()。前者用于打开注册表的键,后者则为打开的键设置键值,至于这两个API函数的具体参数,请参见MSDN。为了代码重用的要求,我为此封装了一个专门的函数,如下所示:

BOOL SetAutoRun(CString strPath)//开机自动运行
{
   CString str;
   HKEY hRegKey;
   BOOL bResult;
   str=_T("Software\\Microsoft\\Windows\\CurrentVersion\\Run");
   if(RegOpenKey(HKEY_LOCAL_MACHINE, str, &hRegKey) != ERROR_SUCCESS) 
       bResult=FALSE;
   else
   {
       _splitpath(strPath.GetBuffer(0),NULL,NULL,str.GetBufferSetLength(MAX_PATH+1),NULL);
       strPath.ReleaseBuffer();
       str.ReleaseBuffer();
       if(::RegSetValueEx( hRegKey,
                           str,
                           0,
                           REG_SZ,
                           (CONST BYTE *)strPath.GetBuffer(0),
                           strPath.GetLength() ) != ERROR_SUCCESS)
          bResult=FALSE;
       else
          bResult=TRUE;
       strPath.ReleaseBuffer();
   }
   return bResult;
}		

    其中strPath参数表示要设置为自运行的程序的绝对路径。当设置成功时返回true,否则返回false。
这里又带来一个问题:既然需要本程序的绝对路径,那么怎么得到它呢?总不能指定一个值吧,那么当本程序的路径改变时就又要修改程序,太麻烦了。可以用这个封装的函数来实现:
//得到程序文件本身的路径(包括文件名)

CString GetMyPath()
{
   CString strPath;
   GetModuleFileName(NULL,strPath.GetBufferSetLength(MAX_PATH+1),MAX_PATH);
   strPath.ReleaseBuffer();
   return strPath;
}

    其中GetModuleFileName()是一个得到路径的API函数。本函数将这个API函数封装在其中,为的是简化调用的目的。
    当执行这个函数时,返回本程序所在的绝对路径,包括本程序的文件名。
好了,得到本程序的路径,然后将它加载到注册表中,下次系统启动时,我们的程序就能自动随之启动了。

2、实现运行时自动隐藏

   这是一个很有趣很古老的话题,csdn上经常有人为此讨论不休,提出不少方案,比如在对话框的OnInitDialog()中添加一句:ShowWindow(SW_HIDE);,或者在对话框属性框中去掉对话框的Visible属性;或者将对话框移到桌面以外的地方去;或者首先将对话框最小化,然后实现最小化时隐藏……有趣的是这些很容易想到的常规方法都不能解决这个问题,或者说解决的不够好。我通过查找相关文章和多次修改代码,找出了真正解决这个问题的办法。
   之所以用ShowWindow()函数失效,我认为可能是对话框的DoModal()在作怪,这么一来就只能绕开DoModal(),那么就自然想到可以把对话框变成一个无模式对话框。无模式对话框平时我们用到的不多,它与模式对话框不同,是用Create方法Create出来的,而不是DoModal()创建的。
   假设建立一个VC.NET工程GetTime,首先在CGetTimeApp类中添加一个成员变量:CGetTimeDlg *dlg;然后在InitInstance()中将原来的:

    int nResponse = dlg.DoModal();
    if (nResponse == IDOK)
    {
       // TODO: Place code here to handle when the dialog is
       //  dismissed with OK
    }
    else if (nResponse == IDCANCEL)
    {
       // TODO: Place code here to handle when the dialog is
       //  dismissed with Cancel
    }
以及return FALSE;全部删除掉,改为:
    dlg=new CGetTimeDlg;
    m_pMainWnd = dlg;
    return dlg->Create(IDD_GETTIME_DIALOG);
   最后别忘了在ExitInstance()中加上一句:delete dlg;好了,这下把本程序的对话框变成了一个无模式对话框。不过既然是无模式对话框,就不能再用OnOK(),OnCancel()来退出了,要用DestroyWindow()。
   由于在上面的代码中没有将对话框设为可见,所以运行时就实现了隐藏,而且在Windows任务栏上也没有显示。至此,第二个问题得到完美解决。

3、实现可以热键呼出

    既然已经隐藏,别人看不到,但是程序的作者有必要和对话框发生交互,那么怎么在需要的时候让对话框出现呢?这个问题可以用热键呼出来实现。
    要实现热键呼出的功能,首先要注册一个热键。注册热键的API函数是RegisterHotKey(),具体参数见MSDN。注册热键可以在对话框的OnInitDialog()中进行:

m_strKey="J";
RegisterHotKey(this->m_hWnd,0xa001,MOD_CONTROL,(UINT)m_strKey[0]);

   其中m_strKey是改对话框类CGetTimeDlg的一个CString型成员变量。0xa001是热键ID,范围是从0x0000 到 0xBFFF,可以自己设定。

这两句的作用是注册一个Ctrl+J的热键。

再为该对话框类添加一个BOOL型成员变量:m_bShow;用来表示对话框当前是显示还是隐藏。当然这个变量
初始化为false,因为对话框开始时不可见。既然注册了热键,就要接收消息以判断用户是否按了热键,因此要重载对话框类的PreTranslateMessage(MSG* pMsg),从消息中截获热键消息:

if(pMsg->message==WM_HOTKEY && pMsg->wParam==0Xa001)
{
if(m_bShow==true)
ShowWindow(SW_HIDE);
else
ShowWindow(SW_SHOW);
m_bShow=!m_bShow;
}

再在对话框的OnClose()中加上两句 :

UnregisterHotKey(this->m_hWnd,0Xa001);
DestroyWindow();
好了,现在运行看看,是不是能响应热键了?


4、实现记录时间

    要记录时间,就要用到MFC的CTime类,这个类可以很方便地处理时间日期相关的问题。为了更高效地处理记录时间问题,我封装了一个函数:

void WriteTime(CString strNote)
{
    CString strTime,str,strDate,strFileName;
    int t;
    DWORD dwAttr;

    CTime time=CTime::GetCurrentTime();//得到当前系统时间
    t=time.GetHour();
    str.Format("%d",t);
    strTime=str;

    t=time.GetMinute();
    str.Format("%d",t);
    strTime=strTime+":"+str;

    t=time.GetSecond();
    str.Format("%d",t);
    strTime=strTime+":"+str;

    t=time.GetYear();
    str.Format("%d",t);
    strDate=str;

    t=time.GetMonth();
    str.Format("%d",t);
    strDate=strDate+"-"+str;

    t=time.GetDay();
    str.Format("%d",t);
    strDate=strDate+"-"+str;

    strFileName="time.txt";
    dwAttr=GetFileAttributes(strFileName);

    if(dwAttr==0xFFFFFFFF)//如果该文件不存在就创建一个
    {
       CStdioFile file;
       file.Open(strFileName,CFile::modeCreate|CFile::modeWrite);
       file.Close();
    }

    CStdioFile file;
    file.Open(strFileName,CFile::modeWrite);
    file.SeekToEnd();//这一句很重要,不然会将以前写的覆盖
    file.WriteString(strDate+" "+strTime+" "+strNote);
    file.WriteString("\r\n");
    file.Close();
}
其中strNote参数是一个备注,本函数的作用是将当前系统时间记录在一个time.txt文件中,比如:

2003-3-15 0:31:25 开机

开机是strNote(备注)。
因此,当要记录开机时间时可以调用WriteTime(“开机”);
要记录关机时间可以用WriteTime(“关机”);


5、实现关机时记录时间

    如何在系统关闭也就是关机时得到通知呢?当关机时,系统向还在运行的程序发送WM_QUERYENDSESSION消息,以询问该程序是否可以结束,因此可以对这个消息进行响应,源码如下:

BOOL CGetTimeDlg::OnQueryEndSession()
{
   if (!CDialog::OnQueryEndSession())
       return FALSE;
   WriteTime("关机");
   // TODO:  在此添加专用的查询结束会话代码

   return TRUE;
}
    这里要注意一点:这种记录关机时间的方法有一定的局限,比如用户注销,重启时都可能被记录为关机。而且当强制关机时,系统不再向程序发送WM_QUERYENDSESSION消息,这时这种方法不起作用。如果大家有什么更好的办法,希望你们能告诉我。
    自此这个小程序的全部代码编写完成,该程序在VC.net+win2000下调试通过。
字母检索 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z