静岡のSさんからの要望で、ディレクトリを選択することを目的とするダイアログの作成方法の解説です。 ここでは、Windows 95 のダイアログやコントロールについての基本的な知識(たとえば、 エディットコントロールの文字列を操作する方法)を持っている人を読者対象とします。そうでない人は、 基本的な解説書をよく読んで、もう一度いらしてください。
ディレクトリ選択ダイアログは以下の様な形状をしており、grep ダイアログ等に埋め込んで使用します。
┏━━━━━━━━━━━━━━━━━━━━━━┓ ┃ディレクトリ: ┃ ┃┌──────────────────┬─┐┃ ┃│C:\BIN │▼│┃←─ 現在選択されているディレクトリパス ┃└──────────────────┴─┘┃ ┃┌────────────────────┐┃ ┃│□ C:\ │┃ ┃│ □ BIN │┃←─ 現在のディレクトリを展開、 ┃│ ■ vivi │┃ 表示 ┃│ ■ vz │┃ ┃│ │┃ ┃│ │┃ ┃└────────────────────┘┃ ┃┌──────────────────┬─┐┃ ┃│ c:\ PCDOC │▼│┃←─ 現在のドライブ ┃└──────────────────┴─┘┃ ┗━━━━━━━━━━━━━━━━━━━━━━┛ |
ダイアログには3つのコントロールが使用されています。一番上と一番下はドロップダウンのコンボッボックスで、 中央はリストボックスです。一番上以外は、項目の先頭にアイコンを表示するためにオーナドローになっています。 オーナドローはアイテムをコントロールが描画してくれないので、描画するコードを記述しなくてはいけません。 でも、Windows 95 では CImageList が使えるので、それほど面倒ではありません。 オーナドローはとても重要な事項ですから、ヘルプやサンプル(CTRLTEST)も参照してください。
3つのコントロールのどれかが修正されると、それによって他のコントロールの内容も変化します。
ディレクトリコンボボックスの内容が変更されると、それに従って、リストボックスの内容が更新されます。
ドライブも変更された場合は、ドライブコンボボックスも更新します。
リストボックスで新しいディレクトリがダブルクリックされた場合、
そのディレクトリがカレントディレクトリになるので、ディレクトリコンボボックスが更新されます。
ドライブコンボボックスが修正されると、新しいドライブのカレントディレクトリをAPIにより取得し、
ディレクトリコンボボックス、リストボックスを更新します。
┏━━━━━━━━━━━━━━┓ ┃ ┃─────────────┐ ┌→┃ディレクトリ・コンボボックス┃────────────┐│ │ ┃ ┃←┐ ││ │ ┗━━━━━━━━━━━━━━┛ │カレントディレクトリ││ │ ┏━━━━━━━━━━━━━━┓ │が変更された場合 ││ │ ┃ ┃─┘ ││ ├→┃ ディレクトリ・リスト ┃←───────────┘│ ドライブが変更│ ┃ ┃ ディレクトリが変更 │ されると、ドラ│ ┗━━━━━━━━━━━━━━┛ された場合 │ イブのカレント│ ┏━━━━━━━━━━━━━━┓ │ ディレクトリに│ ┃ ┃ │ 設定する └─┃ ドライブ・コンボボックス ┃←────────────┘ ┃ ┃ ドライブが変更された場合 ┗━━━━━━━━━━━━━━┛ |
ユーザがパスを直接入力するためのコントロールです。VC++はエディットコントロールを使用していますが、
過去に検索したディレクトリを再び検索することはよくあるので、ViViではコンボボックスにし、
過去の履歴からディレクトリを選択出来るようにしました。
実装では、ディレクトリ・コンボボックスは変数ではなくコントロールにしました。
ダイアログの OnInitDialog で履歴を AddString で設定し、最新の履歴を SetWindowText
でエディットコントロールに設定します。
次に、エディットコントロールの文字列が修正された場合の処理ですが、現在のViViでは、
文字列が修正された場合に、ディレクトリリスト、ドライブコンボボックスを同期的に更新する処理は実装しませんでした。
代わりに、ドロップダウンからディレクトリが選択された場合は、その情報を他のコントロールに渡します。
他のコントロールはサブクラス化されていて、パスを設定するメソッドをもっているので、
新しいディレクトリを引数で指定して、それらのメソッドをコールします。
ディレクトリ・リストはディレクトリ(フォルダ)のアイコンを表示するために、オーナドローとします。 コントロールをオーナドローにする時は、コントロールのサブクラスを定義します。クラスでは、アイテムを描画する DrawItem メソッド、アイテムの高さを与える MeasureItem を最低限定義します。また、 このコントロールはディレクトリを選択するためのもので、アイテムがダブルクリックされたら、 そのディレクトリ直下のすべてのディレクトリを表示するので、OnLButtonDblClk メソッドを定義します。 さらに。1文字が入力されると、その文字で始まるアイテムを選択すると便利なので OnChar も定義しておきます。
各アイテムは、描画を行うためのユーザ定義のデータを持っています。ViViでは、 最初の1,2バイト目でアイテムのインデント、3バイト目で開いたフォルダか閉じたフォルダか、 それ以降にディレクトリ名、という構造としました。DrawItem では、その構造を参照し、描画を行います。
void CDirListBox::DrawItem(LPDRAWITEMSTRUCT lpDIS) { CString str = (LPCSTR)lpDIS->itemData; // アイテムに関するデータ CDC* pDC = CDC::FromHandle(lpDIS->hDC); // 描画用のCDC ..... // データの内容に従って描画する }
void CDirListBox::setPath(const char *fullPath) は、 リストボックスの表示するディレクトリリストを更新します。この関数は、フルパスをディレクトリごとに分解し、 カレントディレクトリにあるディレクトリを取得し、CDirListBox の形式に変換し、それを CStringList に設定する setDirString(const char *fullPath, CStringList &dirList) を下請けに使用します。
void CDirListBox::setPath(const char *fullPath) { g_fullPath = fullPath; int sel = setDirString(fullPath, g_dirList); // フルパスを分解し、内部形式の文字列に変換する ResetContent(); POSITION pos = g_dirList.GetHeadPosition(); while( pos ) { CString &str = g_dirList.GetNext(pos); AddString(str); // 各ディレクトリのアイテムをリストに加える } if( sel >= 0 ) SetCurSel(sel); // カレントディレクトリを選択する }
ViViで苦労したのがこのドライブ・コンボボックスです。 Tips1に書いている様に、全部のドライブの情報を取得しようとすると、 数秒を要するので、マルチスレッドを使ってみたりしてごまかそうとしましたが、うまくいきませんでした (もちろんちゃんと動作しましたが、待たされることには変わりがないのでボツになった)。 結局、時間のかかるドライブの情報は取得しない、というのが解でした。
と思っていたら、高津戸敦氏から、SHGetFileInfo を使うと良いというご教授をいただきました。詳しくは Tips1を参照してください。
ドライブ・コンボボックスの実装はそれほど難しくありません。最初に有効なドライブの一覧、そのボリューム名を取得し、
その情報を CStringList で管理します。各文字列はボリュームタイプを表す識別文字列とボリューム名です。
これを元に、DrawItem でドライブのアイコンとボリューム名を表示します。
ドロップリストから別のドライブが選択された場合、そのドライブのカレントディレクトリを GetCurrentDirectory
で取得し、ディレクトリ・コンボボックス、ディレクトリ・リストに設定します。
オーナドローを使うと、カラーの一覧のコンボボックスを作ったり、アイテムの先頭にアイコンを表示したりできます。
VC++でプログラムを作成する上で必須の知識でしょう。ここでは、オーナドローそのものについては、
あまり詳しく述べませんが、その手順だけ簡単に示しておきます。
リストボックスをオーナドローにする手順:
以下、具体的な手順。
#define DIR_ITEM_HEIGHT 16 void CDirListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) { lpMeasureItemStruct->itemHeight = DIR_ITEM_HEIGHT; // 定数を返すだけ }
DrawItem は引数で渡ってくるアイテムデータに従って描画を行います。 ViViではここを特定のフォーマットの文字列として、アイコンの描画やインデントを行うようにしました。
void CDirListBox::DrawItem(LPDRAWITEMSTRUCT lpDIS) { CString str = (LPCSTR)lpDIS->itemData; // アイテムのデータを取得 CDC* pDC = CDC::FromHandle(lpDIS->hDC); CImageList imgList; if( str[2] == 'o' ) { // フォルダがオープンかクローズかでアイコンを変える imgList.Create(IDB_FOLDER_OPEN, 16, 1, RGB(255, 0, 0)); } else { imgList.Create(IDB_FOLDER_CLOSE, 16, 1, RGB(255, 0, 0)); } int lvl = (str[0]-'0') * 10 + str[1] - '0'; // インデントのレヴェルを取得 int x = lpDIS->rcItem.left + lvl * 10; // インデントから表示位置を算出 int y = lpDIS->rcItem.top; if (lpDIS->itemAction & ODA_DRAWENTIRE) { pDC->SetTextColor(RGB(0, 0, 0)); pDC->TextOut(x+20, y, (LPCSTR)str + 3); } if ((lpDIS->itemState & ODS_SELECTED) && // 選択状態の時の描画 (lpDIS->itemAction & (ODA_SELECT | ODA_DRAWENTIRE))) { pDC->FillSolidRect(&lpDIS->rcItem, GetSysColor(COLOR_ACTIVECAPTION)); pDC->SetTextColor(RGB(255, 255, 255)); pDC->SetBkMode(TRANSPARENT); pDC->TextOut(x+20, y, (LPCSTR)str + 3); } if (!(lpDIS->itemState & ODS_SELECTED) && // 通常の状態の描画 (lpDIS->itemAction & ODA_SELECT)) { pDC->FillSolidRect(&lpDIS->rcItem, RGB(255, 255, 255)); pDC->SetTextColor(RGB(0, 0, 0)); pDC->TextOut(x+20, y, (LPCSTR)str + 3); } CPoint p(x, y+3); imgList.Draw(pDC, 0, p, ILD_TRANSPARENT); // アイコンの描画 }
BOOL CGrepDlg::OnInitDialog() { CDialog::OnInitDialog(); VERIFY(m_dirListBox.SubclassDlgItem(IDC_DIR_LIST, this)); ..... }
前のTips 次のTips 津田伸秀 のホームページに戻る。
Last Updated on 10-Oct-1996, Copyright (c) 1996 by Nobuhide Tsuda, All Right Reserved.
このホームページに関するご質問、ご要望、バグレポート等は
ntsuda@beam.or.jp
までメールをいただければ幸いです。