超・工数計測ワークタイマー開発日記



2002年5月12日

統計情報ダイアログのサイズが変更されたときにはリストボックスのカラムのサイズも変更するようにした。ところがCSummaryDlgが初めて表示されるときはm_listがまだ初期化されていないにもかかわらずOnSizeハンドラが動作してしまい、所謂Nullポインタ例外が発生してしまっていた。そこでm_listが初期化されていないときはm_listに対する操作を行わないようにする必要がある。ヌルポインタであるかどうかの判断をするためにはm_listを(VOID*)でキャストしてやって比較する必要がある。これに結構気づかなくて難儀しました。とりあえず下のようにやって上手くいった。

void CSummaryDlg::OnSize(UINT nType int cx int cy)
{
  CResizableDialog::OnSize(nType cx cy);

  if((VOID*)this->m_list == NULL)
    return;
  // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
  CRect rect;
  m_list.GetClientRect(&rect);
  int newcx = rect.right - rect.left - (m_list.GetColumnWidth(0) + m_list.GetColumnWidth(1));
  this->m_list.SetColumnWidth(2newcx);
  return;
}



2002年5月7日

追加と編集のダイアログのサイズを可変にしたのだが、上下にはサイズを変更できないようにしている。ところがマスカーソルをダイアログの上や下に合わせるとカーソルの形がサイズ変更の形になってしまう。サイズ変更できないのにカーソルが変わってしまうのは嫌なのでOnNcHitTestを下のようにオーバーライドした。

UINT CEditDlg::OnNcHitTest(CPoint point)
{
    // TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください
    UINT ret = CResizableDialog::OnNcHitTest(point);
    switch(ret)
    {
      case HTBOTTOM:
        case HTTOP:
            return HTCLIENT;
            break;
        case HTTOPLEFT:
        case HTBOTTOMLEFT:
            return HTLEFT;
            break;
        case HTTOPRIGHT:
        case HTBOTTOMRIGHT:
            return HTRIGHT;
            break;
        default:
            return ret;
            break;
    }
}



2002年5月5日

http://www.codeproject.com/dialog/resizabledialog.asp
ここのライブラリを利用してサイズ可変なダイアログボックスを作成した。追加、編集、サマリの3つのダイアログをサイズ可変に変更。
知らないうちにファイルダイアログもサイズ可変になっていた。元からか?



2002年5月2日

指定日時超過および指定時間超過時には表示モードが「経過時間」「指定日時まで」「指定時間まで」の全モードでその作業項目が赤字になるようにした。カスタムドローを以下のように変更。

// 残り時間切れのものに対しては赤い文字で表示させるカスタムドロー
void CGenkaKanriView::OnCustomDraw(NMHDR* pNMHDR LRESULT* pResult)
{
NMLVCUSTOMDRAW* lpCustomDraw = (NMLVCUSTOMDRAW*)pNMHDR;
UINT listCnt = GetDocument()->m_myobArray.GetSize();
UINT index;
if( lpCustomDraw->nmcd.dwDrawStage == CDDS_PREPAINT )
{
*pResult = CDRF_NOTIFYITEMDRAW;
return;
}
if( lpCustomDraw->nmcd.dwDrawStage == CDDS_ITEMPREPAINT )
{
index = lpCustomDraw->nmcd.dwItemSpec % listCnt;

if(m_ColumnFlg == 1) //時間指定表示のとき
{
if(m_limitOver[index] == 1 || m_dayOver[index] == 1)
{
lpCustomDraw->clrText = RGB(25500);
}
}
else if(m_ColumnFlg == 2)//日付指定表示の時
{
if(m_dayOver[index] == 1)
{
lpCustomDraw->clrText = RGB(25500);
}
}
else if(m_ColumnFlg == 0)
{
if(m_limitOver[index] == 1 || m_dayOver[index] == 1)
{
lpCustomDraw->clrText = RGB(25500);
}
}

*pResult = 0;
return;
}
*pResult = 0;
}

どういうわけか作業項目追加時に落ちる。まぁ原因は大体予想がついてm_limitOverやm_dayOverのOutOfRangeである。挿入時には一時的にindexの大きさがこれらの配列のサイズを上回ってしまうのであった。アイテム追加時にもこのカスタムドローのメソッドが動くために発生したバグ。ということで追加時にm_listOverとm_dayOverのサイズを1つ分拡張してやる。
さらに黒字の作業項目と赤字の作業項目が上から並んで登録されている場合に黒字の作業項目を削除すると赤字だった作業項目が黒字になってしまうバグも発覚。作業項目を削除した瞬間に黒字・赤字判定用の配列を作り直す処理が抜けていた。

ニョキダイアログのサイズが変更できないようにした。WM_NCHITTESTメッセージを拾ったらば強制的にマウスカーソルを矢印にするようにした。これでサイズ変更カーソルにできないのでサイズ変更できなくなるという簡単な処置。



2002年4月29日

ドッキングウィンドウの初期設定の方法が分かった。それから状態保存の方法も。単にCMainFrame::DestoroyWindow()の実装をし忘れていただけであった。

BOOL CMainFrame::DestroyWindow()
{
// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
CString sProfile(_T("BarState"));
m_wndMyBar.SaveState(sProfile);
SaveBarState(sProfile);

return CFrameWnd::DestroyWindow();
}
とすれば勝手にレジストリに登録してくれる。ハッピー。

ツールバーのアイコンにチップが出るようにする。単にストリングテーブルをいじればいいだけだった。



2002年4月28日

リストビューのフォントサイズを変更するメニューを作成する。最大・大・中・小・最小の5つが選べるようになった。当初はフォントダイアログにしようと思っていたんだが、日本語表示できないフォントを選択されたり大きすぎるフォントサイズを選択されたりするのもイヤだったし(そうされないようにすることも可能ではある)、何よりそんなにフォントを変更するようなことも無いだろうと結論し5段階の選択だけ可能なようにした。ViewクラスにCFontをpublicで持たせてメニューで選ばれたらば
myFONT.CreatePointFont(90"MS UI Gothic");
とやってSetFont(&myFONT)で出来上がり。90が最小で最大130。

実は最初はLOGFONT構造体でどうにかしようと思っていたんだが、フォントサイズというのはとある計算式で算出しているということが判明。逆算してフォントサイズを割り出してみたのだが、いまいち結果が合わないので断念することにした。というよりもデバイスコンテキストより取得したLOGFONT.fontFaceが何故かSystemになっているのである。てゆーか
CClientDC* myDC(NULL);
myDC->GetCetCurrentFont()->GetLogFont(&lf);
で取得したLOGFONT構造体を使って

CFont myFONT;
myFONT.CreateFontIndirect(&lf);
SetFont(myFONT);

ってやったら全然違うフォントになっちゃったという謎な現象が発生したために断念することとなったのである。

プロパティーシートから適用ボタンとヘルプボタンを削除。
cps.m_psh.dwFlags |= PSH_NOAPPLYNOW;
cps.m_psh.dwFlags&=~PSH_HASHELP;
cps.DoModal()
とすべし。でもこれだと適用ボタンだけ消えるのでこの他に使っているプロパティーページのコンストラクタで
m_psp.dwFlags&=~PSP_HASHELP;
とすればよい。全部のプロパティーページで上の1行を書いてやればヘルプボタン自体が消滅する。

各種環境をレジストリに登録しているのだが、レジストリを消して起動してみたらビックリ。フォームのサイズがえらいことになってる。ということでデフォルトのサイズを変更。ドッキングウィンドウの初期サイズの設定ができん。どうすりゃいいんじゃ?



2002年4月26日

ドッキングウィンドウを追加した。アプリケーションの操作ログを表示する部分を今回追加したドッキングウィンドウに表示させようと思ったからだ。しかし、ドッキングウィンドウは別の使い方に利用することにする。どういった情報を表示させるかは現在考え中。いつものとおりサンプルはhttp://www.codeproject.com/docking/sizecbar.aspから入手した。

ログ保存ディレクトリの指定の実装が90%完了した。残り10%は決めの問題である。不正なディレクトリを指定されたときの動作をどうするかを色々なアプリなどを参考にして最終的に決定しようと思う。

タイマー起動時と停止時でアイコンの色を変更することにした。起動時は黄色と赤のアイコンで停止時は灰色のアイコンになる。ステータスバーとツールバーの表示/非表示を選択するメニューを追加。

その他今日の成果は論理ドライブのドライブレター一覧を取得する関数を得たこと。


2002年4月25日

ログ保存のディレクトリ指定のダイアログのイメージが決定した。WIN2Kのインターフェースを丸々真似する。参照ボタンを押した時の動きの大半を実装。これにより従来の参照ボタン押下時に表示されるフォルダ選択ダイアログはコメント化。以下ソース。
void CPPage2::OnRefer()
{
// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
/*ディレクトリ指定だったがファイル名指定に変更したためにコメント化
char szFolder[MAX_PATH];
if( GetOpenFolderName( m_hWnd "c:\\" szFolder MAX_PATH ) == IDOK )
{
CEdit* pDirectory = (CEdit*)GetDlgItem(IDC_SAVEDIRECTORY);
TCHAR szDirectory[MAX_PATH];
memset(szDirectory'\0'sizeof(szDirectory));
strcpy(szDirectoryszFolder);
pDirectory->SetWindowText(szDirectory);
m_saveDirectory = szDirectory;
}
*/
CFileDialogST dlg(TRUE "TXT" NULL OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT _T("すべてのファイル(*.*)\0") this);
dlg.m_ofn.lpstrTitle = "保存先ディレクトリとファイル名を選択してください";
int retCd = dlg.DoModal();
if (retCd == IDOK)//場所・ファイル名・例を更新する
{
CEdit* pSaveDir = (CEdit*)GetDlgItem(IDC_SAVEDIRECTORY);
CEdit* pSaveNm = (CEdit*)GetDlgItem(IDC_SAVEFILENAME);
pSaveDir->SetWindowText(dlg.GetFileDir().Left(dlg.GetFileDir().GetLength() -1));
CString strFileNm = dlg.GetFileName();
int pilIndex = strFileNm.Find('.');
pSaveNm->SetWindowText(dlg.GetFileName().Left(pilIndex));

//例を作りましょう
}
}

それからアプリケーション起動終了ログを追跡するつもりで作成したリストビューには自アプリケーションのオペレーションを表示するようにした。結局色々調べてみたがいい方法が思い浮かばなかったためにそうなった。以下ソース。

void CItemDetail::ItemAdd(CString strItemNm UINT opeTyp)
{
CString strItem;

strItem = CTime::GetCurrentTime().Format("%Y/%m/%d %H:%M:%S ");
strItem = strItem + strItemNm;
switch(opeTyp)
{
case TYP_ADD:
strItem = strItem + " を追加しました";
break;
case TYP_DELETE:
strItem = strItem + " を削除しました";
break;
case TYP_START:
strItem = strItem + " の計測を開始しました";
break;
case TYP_STOP:
strItem = strItem + " の計測を停止しました";
break;
case TYP_EDIT:
strItem = strItem + " の編集をしました";
break;
case TYP_SAM:
strItem = strItem + " の統計情報を表示しました";
break;
case TYP_RESET:
strItem = strItem + " をリセットしました";
break;
default:
break;
}
LV_ITEM item;
item.mask = LVSIL_SMALL | LVIF_IMAGE | LVIF_TEXT;
item.cchTextMax = 256;

item.pszText = strItem.GetBuffer(sizeof(strItem));

item.iItem = m_itemNo++;
item.iSubItem = 0;
GetListCtrl().InsertItem(&item);
}




2002年4月10日

 システムシェルフックを使ってアプリケーションの起動ログを取得しようと思っていたのだが、色々と難しかったので、タイマーをぐるぐる回してウインドウの存在チェックなどを行うことにした。ウインドウハンドルからアプリケーション名およびexe名からWindowTextからアイコンから色々なものが取得できるようになったのであとはこれらを合成してリストビューに表示してやるだけだ。結局システムシェルフック用に作ったDLLはまたもや意味がなくなってしまった。

 水平スクロールバーを表示させないようにする方法がわからない。Win32APIのリファレンス本は買っておいたほうがいいかもしれない。読んでみると結構使えそうなAPIが書いてあった。



2002年4月9日

 最小化して起動する方法がわかった。
BOOL CXXXApp::InitInstance()
{
・・・中略・・・

// DDE、file open など標準のシェル コマンドのコマンドラインを解析します。
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);

m_nCmdShow = SW_HIDE;//この1行を追加するだけ
// コマンドラインでディスパッチ コマンドを指定します。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
・・・後略・・・

たったの1行を入れるだけでよかった。。。



2002年4月8日

シェルフックからウインドウハンドルを取得してそのウインドウハンドルを元にexeまでのパスを取得することは出来るようになった。その取得したパスをダイアログに表示させてみたのだが、1つのウインドウ起動に対してものすごく沢山のダイアログが表示されてしまう。メッセージフックの方法が間違っているのか、それともフックする対象が間違っているのかは分からないがとにかくこのままじゃ使い物にならないので要調査である。

ユーザーが起動したアプリケーションをログに保存するWC Watcherというソフトを発見した。しかも嬉しいことにソースまで公開されていた!!と思ったらばHSPで作られたアプリケーションだったりする・・・。HSPはよく分からん。ソースコードを読んでみてもコメント皆無で一体どこがどの処理をしているのか皆目見当がつかない。

メインフレームを不可視にして起動する方法もよくわからなかったし今日はあまり作業が進まなかった。



2002年4月7日

 DLL化した追加ダイアログと編集ダイアログを元のEXE側に戻す。あんまりDLL化したメリットがなかったのと、追加機能と編集機能の仕様変更が発生しそうなのでいちいちアクティブプロジェクトを変更したりリンクしたりするのが面倒だったから。ということでaddDlgParam.cpp addDlgParam.h editDlgParam.cpp editDlgParam.hの4つのファイルはVSSからも削除してしまった。それからダイアログ用に作成したwtdlg.dll用のプロジェクトファイルも削除。
 メッセージフックのDLL化に成功。http://www.kab-studio.com/Programing/Codian/DLL_Hook_SClass/08.htmlを参考にしたらすんなりできた。が、システムフックの利用は非常に危険でまだその動作が完全に把握しきれていない。システムに深刻な障害を起こす危険性もあるので今のところは暫く何もさせないことにした。
 最小化ボタンを押したときにタスクトレイに帰っていくようにアニメーションするように出来た。MinimizeToTray.cpp MinimizeToTray.hを追加。右クリックしたときには元の位置にアニメーションして戻る。さらにメインフレームが表示されているときにはタスクトレイ上にはアイコンは表示されないようにした。スタートアップなどに登録しておくと起動時にいきなりメインフレームが表示されてしまうので起動時にはSW_HIDEにして起動させる必要がある。そうしないとメインフレームが表示されているのにタスクトレイにアイコンが表示されているという現象が発生してしまうからだ。もともとはそういう仕様だったのでそれで問題が起きる訳ではないがどうせなら統一したい。
 アイコンのデザインを変更した。センスのカケラも感じられないアイコンだがMFCのデフォルトのアイコンよりはマシだろう。32*32以外にも16*16のアイコンを作らないと縮小表示がちゃんとされないのだということらしい。タスクトレイ用のアイコンを別途作成しないと32*32のアイコンが縮小表示されて文字が潰れてしまうのだと。



2002年4月6日

 メインのフレームをスプリッタウインドウにした。上部にはタイマーが下部には何をしていたかをフックしてその内容を表示するようにするためだ。今回新たに追加したItemDetailクラスにそのフック関数を実装していたのだが、タイマー起動中に何をしていたかをフックするためには、DLLを作成してそこにフック関数を記述しないといけないらしい。システムフックを行うにはそうする必要がある。スレッドフックだけだったら今回追加したクラス内でもよかったんだが。以下は本日実装したコードの一部。

int CItemDetail::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CEditView::OnCreate(lpCreateStruct) == -1)
return -1;

// TODO: この位置に固有の作成用コードを追加してください
EnableWindow(FALSE);
this->SetWindowText("超・工数計測ワークタイマー Ver 0.01 Copyright(C) 2002 WayouProject");
HINSTANCE hInst;
hInst = (HINSTANCE)GetWindowLong(m_hWnd GWL_HINSTANCE);
hMyHook = SetWindowsHookEx(WH_SHELL //フック関数のタイプ
(HOOKPROC)ShellProc //フックプロシージャのアドレス
hInst //フックプロシージャが入っているインスタンスハンドル
0); //フックされるスレッド 0ならすべてのスレッド


return 0;
}

LRESULT CALLBACK ShellProc(int nCode WPARAM wp LPARAM lp)
{
if (nCode < 0)
return CallNextHookEx(hMyHook nCode wp lp);
if (nCode == HSHELL_WINDOWACTIVATED || HSHELL_WINDOWCREATED)
//AfxMessageBox("起動したよ");
return 0;
}



2002年4月3日

 昨日の追加機能のバグの原因がわかった。CStringに長い文字列(といっても20文字〜30文字程度だが)を入れるとどうやらメモリ領域を破壊するらしいということがわかった。
下のソースのような使い方をするとtmpCommentの領域が破壊されるみたいだ。
TCHAR tmpComment[sizeof(dayobj->m_comment.GetLength()) +1];
memset(tmpComment'\0'sizeof(tmpComment));
_tcscpy(tmpCommentdayobj->m_comment);

ということでこちらのように修正してみた。
TCHAR* tmpComment = dayobj->m_comment.GetBuffer(sizeof(dayobj->m_comment.GetLength()));
どうやらこれだと上手くいくらしい。何故領域が破壊されるのだろうか?考えられることは修正前ソースの1行目で必要な領域が確保できていないと考えるのが自然だろう。しかしGetLength()でその必要な分は取得できるはずなのだが・・・。
このバグが原因でタスクトレイ上のアイコンをクリックした場合に表示されるはずの作業項目も表示されずに落ちてしまうという潜在バグを解消することができた。よかった。

追加ダイアログと編集ダイアログをDLL化した。そのメリットとは・・・一体何なんだろう?必要に応じてメモリにロードされるので多少環境に優しくはなっているハズだ。しかしDLL化しなくても100K程度なのでやっぱりあんまりメリットはないかも。しかもDLLが存在しなくてもダイアログが出てこなくなるだけで平気で動いちゃうし。アプリ起動時にDLL存在チェックをしないとダメかもしれない。


2002年4月2日

 追加ダイアログをDLL化してみた。とりあえず成功したが、exeのサイズは小さくならなかった。あまりDLL化した意味がないかもしれない。追加機能で一言コメントを入力した場合に正常に追加できなくなる場合があるというバグが出現した。今のところ原因調査中。DLL化したのが悪かったのかと思い、dll化をやめてみたがやはり同じだった。どうもシリアル化が上手く行ってないのではないか?それともメモリ領域確保の仕方の問題なんだろうか?デバッガでトレースしている分には特に問題なさそうなのだが。itemobjとdayobjがかち合っているような雰囲気でもある。毎回この現象になる訳ではなく連続して追加機能を呼び出すとそうなるみたいだ。特に何回目になるということもなくいきなりバグが出ることもあるし、出ないこともある。
 DLL化のやりかたは「猫でもわかるプログラミング」のSDK122章に書いてある。またはVCテクニックのDLLの作成(3)にもある。とありあえずDLL化のソースは残しておくようにする。追加ダイアログのバグが直ったらば再度DLL化にすること。



2002年3月30日

 新しいファイルダイアログの実装が完了。英語サイトから拾ってきたものだからもしかしたら日本語が表示できないのではとちょっと心配していたが、普通に日本語でも問題なかった。今の開発環境がWindows2000なのでもしこれがNTだったり98だったりしたらどうなるのかは試してみる必要があるだろう。
ニョキダイアログの実装が出来た。表示されるフォントの指定が出来ない。日本語が表示できなかったりもした。LOGFONT構造体のlfFaceNameメンバに指定したいフォント名を入れてやればいいはずなのだが、日本語名のフォントだとどうしても化ける。デスクトップのデバイスコンテキストからシステムフォント名を取得してそれを設定してやることでどうにか日本語の表示は出来るようになったのだが、ボールドにしてやることができない。ちょっと困った。マルチスレッドでなくしたのでタイマーカウントが止まってしまうかと心配したが、どうやらそんなことはないらしい。とりあえずその部分については安心。課題としてはフォントをボールドにしたり文字色を変更したりすることと、背景色の設定をしてやることくらいだろう。



2002年3月29日

 ニョキダイアログのサンプルソースを発見した。http://www.codeproject.com/にあった。それからCFileDialogだとどうしてもWindows2000スタイルにすることが出来なかったが、ここのサイトでCFileDialogSTというのが公開されていたのでこれもいただくことにした。今日は実装は殆どせずに調査ばっかりであった。
 それからライセンス登録されたときに自exeに対してライセンス済または未済情報を保存させる方法を教えてもらった。つまりexeまたはオブジェクトモジュールにスタティックな文字列領域を持たせるとエディターなどで開いたときに設定した値がそのまま保存されるのだそうだ。あとはその領域のRead/Writeする処理を埋め込めばいいらしい。
static const char* szStr = "ライセンス情報";
なんてしておけばよい。まあ実際には初期値はchar[20] = "License "なんてやっておいて空白領域にしておくのが普通だそうだ。で書き込みするときにLicenseでサーチして見つかったらば後ろの空白領域を書き込みすればよい。



2002年3月28日

指定時間が近づいたときに出現するダイアログをモーダレスにする方法がわかった。海外のサイトに書いてあった。英語サイトだが頑張って読むとかなり有用な情報が沢山書いてある。日本のサイトではどうしても見つからなかったときにもここならどうにかなる。http://codeguru.earthweb.com

出現するダイアログの背景色も白くした。
HBRUSH CAlartDlg::OnCtlColor(CDC* pDC CWnd* pWnd UINT nCtlColor)
{
HBRUSH hbr = CDialog::OnCtlColor(pDC pWnd nCtlColor);

// TODO: この位置で DC のアトリビュートを変更してください
if (pWnd == this)
hbr = (HBRUSH)GetStockObject(WHITE_BRUSH);

// TODO: デフォルトのブラシが望みのものでない場合には、違うブラシを返してください
return hbr;
}
こうするといいらしい。で、出現したダイアログに文字を表示させようとしたんだが、うまく出てきてくれない。CStaticで表示領域を指定しておいて、必要に応じて文字列の変更をしようと考えているのだが、別のウインドウに消された場合の再描画で表示されてくれない。それから文字列長がどれだけ長くなるか分からないのでダイアログのサイズを固定にしておくのはよくないと思った。文字列のサイズによって動的に変更するようにする必要がある。



2002年3月26日

 残り時間が近づいたり指定日時に近づいた場合にユーザに知らせるためのダイアログ表示を作ってみた。MSNMessengerみたいに、タスクバーからニョキッと出てくるようなのを作りたいのだが、なかなか上手くいかない。ニョキッと出てきてニョキッと消えるダイアログの作成までは上手く行ったんだが、そのダイアログがどうしてもモードレスになってしまいダイアログが出ている間メインウインドウの操作が出来なくなる。マルチスレッドにしたら解決するかと思ったがどうもそれでもダメだった。マルチスレッドにした効果はダイアログが表示されている間でもタイマーのカウントを続けることが出来ているくらい。モーダルダイアログの出し方とかいろいろ調べているのだが思うような動作をしてくれない。
 課題としてはモーダルダイアログの実装とそれからダイアログを表示する位置決め。ダイアログを2つ以上表示する必要が出た場合にその表示位置をどう制御するかなどなど。



2002年3月20日

 前面に表示されるリストビューに一言コメントを表示するように変更した。どうもUIがヘボい。ログを自動保存するディレクトリを指定できるようにしているのだが、実在しないパスを入力されたときの動作をどうするかに非常に悩む。他のソフトの同様の操作を参考にするしかない。



2002年3月8日

 メモリリーク問題がついに解決した。例えばLPSTR str = new TCHAR[8]とやっているところを最初からTCHAR str[8]としてやればよかったわけで、これならdeleteとかしてやる必要もなく解決できるのでありました。なんでこんなことに今まで気づかなかったのか。ちなみに改善前は30bite*項目数*起動時間の分だけメモリリークを起こしていた。ためしに項目数30でビュー更新間隔0.1秒で動かしたらばあっという間に数十MB単位でメモリリークを起こしてました。う〜ん怖い怖い。



2002年3月7日

 控除時間帯問題はついに解決。つまりはitemobj->totalTimeを毎秒更新していけば問題ないのだということに気づく。そんでもって控除時間帯だけは更新しない仕組にしてやればいいのだ。ということでその実装が完了。ところが今度はサマリーが上手く合わない。これも同様に毎秒更新するロジックで落ち着いた。ところでCTimeSpanで+=のオペレータはおかしな動きをする。少なくともintとかとは全然違う動きをする。とにかく今回の元凶はこの妙などうさにあるのだ!!



2002年3月6日

 控除時間帯の問題はとりあえずあとにして、メモリリーク問題に取り組む。つまりはnewしているところは全部deleteしてやらないといけないってことだ。調子に乗って沢山delete文を書いていると、いきなりメモリ例外で落ちる。どうやらnewした文字領域をリストビューのitemのテキストに割り当てたあとにdeleteすると落ちるらしい。しかしここは1秒に1回かならず通る部分だし、つまりは1秒ごとに無駄な領域を確保していくことになる。どうしたもんか。



2002年3月5日

 できたと思っていた控除時間帯の考慮問題だが、思いっきり見当違いだったことが発覚。しかもかなりの見当違い。控除時間帯にカウントアップしない仕様は一旦諦めて、控除時間帯になると自動的にタイマーが停止するようにするほうが楽そうだ。結局振り出しに戻る。



2002年3月4日

 昨日の課題はすべて対応が終了した。控除時間帯プロパティーシートによる控除時間帯はカウントしない実装も出来た。経過時間をカウントするために利用する変数を変えた。あまりその部分には手を入れたくなかったが仕方が無い。あとは控除時間FromとToのコントロールの初期値が現在時刻になっているのを変えるだけだ。
 プログラムを終了したときにメモリを開放していない。あまりこの辺はシビアに考えなかったが、実際のところ考慮しないとダメなのかもしれない。



2002年3月3日

 プロパティーシート内のリストビューから値を取得できない。どうやらそういう仕様らしい。コントロール系はもって来れないらしきことが英語のサイトに書いてあった。仕方が無いのでリストビューに追加されたレコードをすべてCString型に押し込めて、あとはそれをトークンをつかって抽出するという対策をとった。とりあえずプロパティーシート→ビューのやりとりが実装できた。次にビュー→プロパティーシートを実装する必要がある。それからリストビューから削除されたときのことも考えないといけない。



2002年3月2日

 動作設定のプロパティーシートを作成。「起動終了時設定」と「保存設定」と「控除時間設定」の3つ。とりあえず実装しているのは「控除時間設定」。リストビューにコンボボックスをくっつけるサンプルがあったおかげで、控除時間の有効/無効フラグを設定させることができるようになった。



2002年3月1日

 サマリーダイアログに日付抽出機能を追加した。そもそも履歴が全部出てしまうのは実用的ではない。それから「名前を付けて保存」で項目名と合計時間の一覧を表示するようにするための最初の段階、選択されたメニューのメッセージを拾うところまでが出来た。出力するファイルのフォーマットがまだ未定なので、それは今後ゆっくり考えることにする。要望のメールを頂いたことで、今まで殆ど手をつけられなかった動作設定機能の案が出てきた。ありがたや。



2002年2月11日

 従来のワークタイマーの左クリックでタイマーが選択できる機能を追加することにした。よく考えたらその機能なくしてワークタイマーとは言えないだろう。従来のワークタイマーのソースを手本にして追加。登録されているタイマーをメニューに出すことはできたのだが、何故か選択不可になっている。メニューIDの割り当てやら、選択されたタイマーにチェックを付ける必要もあるしさらにはメニューで選択された時と開始ボタンが押されたときとを同じ処理をするように実装しないといけない。
実装完了。動作としてもほぼ問題なし。従来のワークタイマーと同じような動きにさせることができた。メニューのメニューIDが小さいと何故か選択不可になる問題は結局解決できなかった。とりあえず動いているので結果オーライとした。



2002年1月20日

 クリップボードへのコピーはリストビューの並びから取得することにした。非常に簡単であった。ついでに作業項目名と新規作成日、最終起動日も一緒にクリップボードへ送ることにした。
 基準日と日付が違う時にタイマーを動作させても1回目は古い基準日の方に計上されてしまうバグを発見。どうやらタイマー停止時にだけしか基準日の洗替の処理を入れていなかったのが原因みたいだ。タイマー起動時にも基準日の洗替の処理を追加することでバグを修正した。



2002年1月19日

 リストビューのアイテムをソートをすることでどうにか「表示上」は正常に表示されるようになった。が、今度はクリップボードの対応をしないといけない。結局ソートするアルゴリズムを導入するか、ソートされたリストビューから項目を取得していくかのどちらかで対処したい。多分後者になりそうだが。



2002年1月7日

 基準日変更対応の仕様変更。アプリ起動中に日付が変わった場合は強制的に基準日を変更する。ただし動いているタイマーがあった場合はそのタイマー停止後に基準日を変更するようにした。MCFのCMapクラスの仕様をちゃんと把握していなかったのが原因だが、CMapでは新規要素は常に先頭に挿入されるっぽい。通常のMapの動きも果たしてそうだっただろうか?てっきり最後尾に追加されるとばかり思っていた。つまり新しいものが先頭に入れられてしまうと、上から時系列にサマリーを表示するということが出来なくなる。Mapなのでインデックスを持たないからお尻から先頭まで舐めて行くというやりかたも出来ないし・・・。一旦別の配列かリストのようなコレクションクラスに格納して、表示するときはそれらの入れ物から取り出すようにしないとダメかもしれん。てゆーかそもそもハッシュテーブルだから取り出し順序が保存されると考える方が甘かったのかもしれん。色々考えたが表示は運良くリストボックスを使っているので、リストボックスの持っているSortItemメソッドを利用しようと思う。



2002年1月6日

 経過時間とラップタイムの合計に差が生じてしまう問題を解決。結局1秒未満の時間は切り捨てることにした。サマリーダイアログに項目名、作成日時と最終起動日時を出すようにした。レジストリを利用してウインドウの終了時の位置とサイズを保存し、起動時にはそれを読込むようにした。アプリ起動中に日付が変わった場合に基準日を変更するかどうかのダイアログを出すようにした。ただし問題は日付変更時に動作しているタイマーの扱いをどうするかだ。タイマーストップされるまでは変更前の基準日の方に計上する方が自然のような気もする。
 サマリーをクリップボードにコピーするようにした。CSVファイル出力させようかどうか悩んだのだが、勤務表のフォーマットなんかは色々あるだろうからファイル出力させるようなことはしないことにした。



2002年1月5日

 基準日のm_baseDtをコンストラクタで初期化する。これに伴い不要な日付取得処理を削除。タイマー起動中は項目の編集ができないようにする。残り時間切れの作業に対してはカスタムドローを利用して文字色を赤色にするようにした。WM_NOTIFYに対してOnCustomDraw(NMHDR* pNMHDR LRESULT* pResult)マクロを追加。残念ながらリストビューのカラムごとのカスタムドローはできないらしい。でもNMLVCUSTOMDRAW構造体にはちゃんとiSubItemっていうメンバがあるんだけどもなぁ。。。使い方が間違っているのだろうか?
 時間計算の処理方法を大幅に変更。不要なメンバ変数を削除。CMyObjectクラスよりsplitTimeおよびstartTime変数を削除。代わりにCGenkaKanriViewに対してm_startTime変数を追加。タイマ起動時のシステム時間をこの変数にセットするようにした。そもそもタイマーオブジェクトにはそんな変数を持たせる必要などなかったのだよ。
 タイマ停止時にミリ秒単位で狂う。仕方が無いのでビューに表示されている経過時間をオブジェクトにセットするようにした。つまり1秒未満の時間は切り捨て。しかしサマリーでの経過時間との間にやはりミリ秒単位で差が生じることが発覚。これがつもりつもると意外と大きな差になってしまう。



2002年1月3日

 先日の課題であった作業項目ごとのサマリーを日付をキーとしてコメントと一日の作業時間のMAPを作ることにした。検証してみたところMAP内に格納されたオブジェクトはちゃんと保存はされるらしい。作業時間を累計するアルゴリズムを考えるのが難しい。さらに日付をまたがって同じ作業を継続した場合に作業基準日という概念を導入するかどうかを考える。
作業時間集計のアルゴリズムは結局メンバ変数を追加することで出来るだけ簡略化することにした。その代わり見れたもんじゃない最悪なロジック。計測時間のトータルを1秒ごとに更新するロジックとの兼ね合いが難しい。同じような意味の変数を作ってしまったし、しかも変数名と違う役割を持ち始めているし・・・。センスのかけらもない。
 今後の課題。オプション設定機能の実装。とりあえずユーザインターフェースの微調整をしてみる。細部のテスト。日付をまたがる場合のロジックを考える。



2002年1月2日

 ソースの修正や追加が頻繁になってきたので。VSSを利用することにした。個人開発でまさかVSSを使うことになるとは思わなかった。でもこれでデグレや誤って削除したときなどの保険になった。
 ヘルプ作成をするためにプロジェクトの作り直しをする。マージがうまくいかずかなり焦る。VSS導入決断のきっかけである。
 保存方法としてのシリアル化は意外と上手くいった。残る課題は自動保存と自動読込みだ。(1/3 0:58 自動保存と自動読込み実装完了。ログオフなどので発生するメッセージハンドルを追加する必要あり。1/3 2:00 WM_QUERYENDSESSIONのハンドルで処理を実装)
 アプリ終了の際に各オブジェクトの起動フラグをすべてオフにするようにした。そうしないと次回起動時に起動したまま終了されたオブジェクトに関しての計測時間の計算が上手くいかないからだ。起動フラグ自体の削除も考えたが、例えば次回起動時のデフォルト起動などを行いたい場合などを考慮して残すことにする。
 制限機能の実装完了。ファイル入出力の実装完了。ファイル名をソースに直書きしない工夫をする必要あり。今後の残作業は、オプション設定を一元管理するクラスを作成することとオプション設定のダイアログ作成。作業項目コメントの参照ダイアログの作成。その他調査事項、リストビューの文字色の変更が出来るかどうか。自動保存のSetTimer()をどこに作るか。iniファイルでアプリ環境の保存またはレジストリ利用方法の調査。Socket利用のAPIの研究およびネットワークアーキテクチャーを考える。アドミン機能要件を考える。