続・PCの起動時間を記録してくれるソフトを作る。
何故か今日はVSのIntellisenseがうまく働いてないのか、
候補表示がされない。
おかげでとても不便(^o^;)
こういう時は ncb ファイルを削除すればうまく動くので消してみる。
→案の定うまくIntellisenseが動き始めました。よかったよかった。
・タスクトレイにアイコン表示
まず、適当にタスクトレイアイコンから受け取るメッセージを定義しておく。
#define WM_NOTIFYICON ( WM_USER + 100 )
そして、NOTIFYICONDATA の変数を作って、設定して、Shell_NotifyIcon。
g_Icon.cbSize = sizeof ( NOTIFYICONDATA ); g_Icon.uID = 1; g_Icon.hWnd = hWnd; g_Icon.uFlags = NIF_MESSAGE | NIF_ICON | NIF_TIP; g_Icon.hIcon = 0; // まだアイコンを用意してないんだもの g_Icon.uCallbackMessage = WM_NOTIFYICON; lstrcpy ( g_Icon.szTip, "Running Timer" ); Shell_NotifyIcon ( NIM_ADD, &g_Icon );
ただ、スタートアップ等に登録しておいた場合、Explorerのタスクトレイが作成される前に
アイコンを登録しようとしてしまうのかなんなのか、トレイアイコンに登録できない事があるらしい。
そこで、適当にループを回して処理する。
for ( ; ; ) { if ( Shell_NotifyIcon ( NIM_ADD, &g_Icon ) ) { break; } else { if ( GetLastError ( ) == ERROR_TIMEOUT ) { PostQuitMessage ( 0 ); } if ( Shell_NotifyIcon ( NIM_MODIFY, &g_Icon ) ) { break; } else { Sleep ( 1000 ); } } }
終了時には、アイコンを削除させる。
Shell_NotifyIcon ( NIM_DELETE, &g_Icon );
また、起動直後は非表示(タスクトレイに格納された状態)にしたいので
ShowWindow ( hWnd, SW_HIDE ) で隠しておく。
ただし、WM_INITDIALOG に書いても非表示にはならない。
これは、WM_INITDIALOG メッセージが「ダイアログが作成される直前」に送られてくるものだからである。
そこで、またまたタイマーを使う事にする。
使い捨てタイマーをセットしておき、そこで非表示にする。
これだけだと起動時に一瞬だけウィンドウが見えるけど、まあ仕様ということで・・・(^o^;)
更に、Explorerが死んだときに再びタスクアイコンを復活させるようにする。
起動時にTaskbarCreatedのメッセージIDを取得しておき、
後はそれを処理するだけ。
g_nTaskbarMessage = RegisterWindowMessage ( "TaskbarCreated" ); // 以下メッセージループにて default: if ( msg == g_nTaskbarMessage ) { AddTasktray ( ); }
・タスクトレイのアイコンからの復帰
先ほど定義した WM_NOTIFYICON を処理してやる。
lParam に動作メッセージが入っている。
case WM_NOTIFYICON: switch ( lParam ) { case WM_LBUTTONDBLCLK: ShowWindow ( hWnd, SW_NORMAL ); break;
更に、タスクトレイアイコンを右クリックするとメニューが表示されるようにする。
適当にリソースでメニューを作って、表示。
case WM_RBUTTONDOWN: GetCursorPos ( &pt ); hMenu = LoadMenu ( g_hInstance, (LPCSTR) IDR_POPUPMENU ); hSubMenu = GetSubMenu ( hMenu, 0 ); TrackPopupMenu ( hSubMenu, TPM_LEFTALIGN, pt.x, pt.y, 0, hWnd, NULL ); DestroyMenu ( hMenu ); break;
hMenu や hSubMenu は HMENU型。
・起動時間から文字列フォーマット化する関数を作る
Record構造体の lSpan から文字列を生成させる。
リストにレコードを追加する AddRecord関数 も一緒に書き換えちゃう。
更に、前回の lSpan から時間を算出する部分にバグがあったのでそれも直す。
(%d なのに ULONG64 を使っていたため、おかしな事になっていた。)
void SpanToString ( ULONG64 lSpan, char *pszBuf, int nSize ) { if ( lSpan / (1000*60*60*24) > 1 ) { sprintf_s ( pszBuf, nSize, "%d日 %2d時間%02d分%02d秒", (int)lSpan / (1000*60*60*24), (int)(lSpan / (1000*60*60)) % 24, (int)(lSpan / (1000*60)) % 60, (lSpan / 1000) % 60 ); } else { sprintf_s ( pszBuf, nSize, "%2d時間%02d分%02d秒", (int)lSpan / (1000*60*60), (int)(lSpan / (1000*60)) % 60, (int)((lSpan / 1000) % 60 )); } }
タスクトレイのアイコンのツールチップはこんな感じで更新。
これだとマウスカーソルを動かさなくても何故かリアルタイムで更新される(^o^;)
case WM_NOTIFYICON: switch ( lParam ) { case WM_MOUSEMOVE: SpanToString ( GetRunningTime ( ), g_Icon.szTip, sizeof ( g_Icon.szTip ) ); Shell_NotifyIcon ( NIM_MODIFY, &g_Icon ); break;
・ウィンドウ表示中は起動時間をリアルタイムに更新する
メインウィンドウが表示されている間は、
「今回の起動時間」欄にリアルタイムで情報を表示させる。
ここでまたタイマーを使う事にする。
1000ミリ秒間隔で、IsWindowVisible関数 でチェックを行い、更新。
更に、起動時にPCの起動時刻を取得しておく。
これは、現在時刻から起動時間を引けばOKである。
手動で計算するとめんどくさいので、time.h をインクルードして mktime を使う。
g_recNow.lSpan = GetRunningTime ( ); tNow = time ( NULL ); localtime_s ( &tDay, &tNow ); tDay.tm_mday -= (int) g_recNow.lSpan / 1000 / 60 / 60 / 24; tDay.tm_hour -= (int) (g_recNow.lSpan / 1000 / 60 / 60) % 24; tDay.tm_min -= (int) (g_recNow.lSpan / 1000 / 60 ) % 60; tDay.tm_sec -= (int) (g_recNow.lSpan / 1000 ) % 60; tStart = mktime ( &tDay ); localtime_s ( &tDay, &tStart ); g_recNow.dtStart.wYear = tDay.tm_year + 1900; g_recNow.dtStart.byMonth = tDay.tm_mon + 1; g_recNow.dtStart.byDay = tDay.tm_mday; g_recNow.dtStart.byHour = tDay.tm_hour; g_recNow.dtStart.byMinute = tDay.tm_min; g_recNow.dtStart.bySecond = tDay.tm_sec;
実際の表示処理は次のような感じで。
(ostringstream 使うと長くならうからあえて sprintf で・・・(^o^;))
if ( IsWindowVisible ( hWnd ) ) { SpanToString ( GetRunningTime ( ), szBuf, sizeof ( szBuf ) ); sprintf_s ( szBuf2, sizeof ( szBuf ), "起動時刻:%04d/%02d/%02d %02d:%02d:%02d 経過時間:%s", g_recNow.dtStart.wYear, g_recNow.dtStart.byMonth, g_recNow.dtStart.byDay, g_recNow.dtStart.byHour, g_recNow.dtStart.byMinute, g_recNow.dtStart.bySecond, szBuf ); SendMessage ( g_hInfoEdit, WM_SETTEXT, 0, (LPARAM) szBuf2 ); }
かなり雑な作りだけど、一応動く。
・レコードの保存処理
これは既に関数作ってあるから楽チンチン。
保存する時=今まで保存してきた中で最も終了時刻に近い時間なので
SaveRecord の最初に処理を付け加えて、dtEnd や lSpan を更新させる。
ここでまたおかしな挙動が発生。
ファイルが存在しないと、ios::out があっても open に失敗する。。
最終的に分かった事は、オープンに失敗したら clear ( ) を呼ばないと行けないという事。
あと、ios::in が含まれている場合、ios::out があっても新しくファイルを作ってくれない。
ヤヤコシイ・・・(^o^;)
・起動時間TOP10とかその辺の処理諸々
これも簡単で、既に作った関数から読み込んで、
その後リストに登録するだけ。
これは起動時に1回処理するだけでいい。
ここで気づいたが、今の状態だと起動時間に1秒〜2秒ぐらいの誤差が現れる事になる。
すると、別レコードとして保存されてしまい、結果として残念な事になってしまう。
そこで、0秒〜9秒は同じ起動時刻として判定に使わない事にした。
ソート用にオペレータを定義しておく。
bool operator<(const Record& left, const Record& right) { return left.lSpan < right.lSpan; } bool operator>(const Record& left, const Record& right) { return left.lSpan > right.lSpan; }
後は適当に。
vecRec = LoadRecord ( ); for ( int i = 0; i < vecRec.size() && i < 10; i++ ) { AddRecord ( IDC_LIST_RECENT, vecRec[i] ); } sort ( vecRec.begin(), vecRec.end() ); for ( int i = 0; i < vecRec.size() && i < 10; i++ ) { AddRecord ( IDC_LIST_TOP, vecRec[i] ); }
・設定ダイアログ周り
・メニュー周りの処理
特に特筆すべき点もないので省略。
こうしていろいろと後から変更とかあったけど、
一応の完成である。
後はしばらくテスト使用。これでAntiVirの警告とおさらばできるぜひゃっほい。
今回も長くなったので、この辺で終わり。
おまけ
何を取得すんねん!!!?