続・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の警告とおさらばできるぜひゃっほい。


今回も長くなったので、この辺で終わり。






おまけ



何を取得すんねん!!!?