※ 修正版をこちらのページにアップしています。将来的にはこのページは削除される可能性があります。
さて、今回までにドライブの表示はできましたが、ファイルの表示ができていません。
ファイルの表示を行うために、リストビュウ を使うことにします。
リストビュウは、コモンコントロール のコントロールです。つまり標準ではないコントロールになります。
コモンコントロールが標準コントロールと違うところは、標準では提供されないということです。
名前からも当たり前のようですが、CommCtrl.h をインクルードしなくてはいけません。
また、使用ライブラリに ComCtl32.Lib を追加してください。
※ ライブラリの追加は以下のようにやりましょう。


作り方は、コモンコントロールのときとまったく同じですが、使用する前におまじないが必要になります。
C言語には、この手のおまじないがよく出てきます。
InitCommonControlsEx()
で使用するコントロールの種類をします。
※ これを行わなくても正常に動きますが、一応おまじないとしてやっておいたほうが無難です。
ダイアログ等のリソースでコモンコントロールを使う場合にのみ、必要だと思うのですが裏が取れませんでした。
BOOL InitCommonControlsEx( LPINITCOMMONCONTROLSEX lpInitCtrls // 登録対象のクラスの情報 );
この引数には、INITCOMMONCONTROLSEX構造体を指定します。
この構造体の第1メンバーには、INITCOMMONCONTROLSEX のサイズ自身を、第2メンバーには、使用しようとするクラス名を指定します。
今回は、リストビュウを使いますから、ICC_LISTVIEW_CLASSES を指定します。
作成するには、標準コントロールと同じように、CreateWindow() で作成することができます。
システムクラスには、WC_LISTVIEW を指定します。
コンボボックスの時には、"COMBOBOX" を指定しましたね。
※ WC_LISTVIEW は、文字列定数です。表示させてみるとその答えは、"SysListView32" でした。
これは、"CommCtrl.h" を見るとわかりますね。
"COMBOBOX" に対応する文字列定数はないようです。
また、ウィンドウスタイルに WS_CHILD を指定するのを忘れないでくださいね。
ウインドウプロシージャを以下のように修正します。
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
/* 変数宣言部 */
HINSTANCE hInstance;
HWND hCtlCombo;
HWND hCtlListView;
char szCurDir[MAX_PATH + 1];
char szDrives[128];
int i;
INITCOMMONCONTROLSEX ICC;
/* 関数本体部 */
switch (uMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDM_EXIT:
SendMessage(hWnd, WM_CLOSE, 0, 0L);
break
default:
return (DefWindowProc(hWnd, uMsg, wParam, lParam));
}
break;
case WM_CREATE:
hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
// コンボボックス作成
hCtlCombo = CreateWindow("COMBOBOX",
NULL,
WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL,
10,
5,
380,
100,
hWnd,
0,
hInstance,
NULL);
SendMessage(hCtlCombo, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true , 0));
GetCurrentDirectory(MAX_PATH + 1, szCurDir);
ComboBox_SetText(hCtlCombo, szCurDir);
ComboBox_AddString(hCtlCombo, szCurDir);
GetLogicalDriveStrings(sizeof(szDrives), szDrives);
i = 0;
while (szDrives[i] != '\0')
{
ComboBox_AddString(hCtlCombo, &szDrives[i]);
// 次のドライブ名へ
while (szDrives[i] != '\0')
{
i++;
}
i++;
}
// コモンコントロール初期化
ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
ICC.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&ICC);
// リストビュウ作成
hCtlListView = CreateWindow(WC_LISTVIEW,
NULL,
WS_CHILD | WS_VISIBLE | LVS_REPORT,
10,
30,
380,
320,
hWnd,
0,
hInstance,
NULL);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return (DefWindowProc(hWnd, uMsg, wParam, lParam));
}
return 0L;
}
そうして、実行してみてください。
コンボボックスの下になにやらよくわからない物体が表示されました。

これは、何でしょうか?どうやらリストビュウの上の部分だけが表示されているようです。
これでは何も表示できません。表示するには表示する項目の数だけカラム(列)のヘッダーを作る必要があります。
カラム(列)のヘッダーを作るには、リストビュウに LVM_INSERTCOLUMN メッセージを送る必要があります。
送出するには、 やっぱり SendMessage() を使います。
LVM_INSERTCOLUMN wParam = (WPARAM) (int) iCol // カラムを挿入する位置 lParam = (LPARAM) (const LV_COLUMN FAR *) pCol // カラムの情報を格納した LVCOLUMN 構造体のポインタ
なんかややこしいことに、LVCOLUMN 構造体 を第2引数に指定しないといけないようです。
LVCOLUMN 構造体 はどのようになっているかというと、
typedef struct _LVCOLUMN {
UINT mask; // どのメンバが有効かを指定
int fmt; // カラムの配置(左端のカラムは左詰め固定)
int cx; // カラムの幅
LPTSTR pszText; // カラムのヘッダーの文字列
int cchTextMax; // pszTextのサイズ
int iSubItem; // カラムに関連付けられるインデックス
#if (_WIN32_IE >= 0x0300) // IE3.0 (comctl32.dll Version4.70) 以降の機能
int iImage; // イメージリストのインデックス
int iOrder; // カラムを挿入した位置
#endif
#if (_WIN32_WINNT >= 0x0600) // Windows Vista 以降の機能
int cxMin;
int cxDefault;
int cxIdeal;
#endif
} LVCOLUMN, *LPLVCOLUMN;
いやーなんとも難しそうですが、今回使うのは、mask、fmt、cx、pszText、iSubItem の5つです。
mask には今から使用するメンバのフラグを指定します。
メンバとは、構造体の構成要素です。
ここでいうと、fmt、cx、pszText、iSubItem の4つを指定する必要がありますので、LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM を指定します。
fmt には、カラム(列)のヘッダーの配置を指定することができます。LVCFMT_LEFT, LVCFMT_RIGHT, LVCFMT_CENTER のいずれかを指定します。
今回は、3つのカラム(列)のヘッダー(「名前」、「サイズ」、「更新日時」)を作成しようと考えています。
このため、それぞれ LVCFMT_LEFT、LVCFMT_RIGHT、LVCFMT_LEFT を指定します。
cx は、それぞれのカラム(列)のヘッダーの長さを指定します。
今回は適当に、それぞれ 150、75、100 を指定します。
pszText は、それぞれのカラム(列)のヘッダーのタイトルを指定します。
もちろん、「名前」、「サイズ」、「更新日時」を指定します。
iSubItem は、カラムに関連付けられるインデックスです。
挿入する順番とは関係なく採番することができますが、余計わかりにくくなるので、0 から順につけていくのが無難だといえます。
ウインドウプロシージャを以下のように修正します。
※ 長くなってきたので、必要部分のみ抜粋します。
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
/* 変数宣言部 */
HINSTANCE hInstance;
HWND hCtlListView;
LV_COLUMN LVColumn[] =
{
{LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 150, "名前", 0, 0},
{LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT, 75, "サイズ", 0, 1},
{LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 110, "更新日時", 0, 2}
};
int i;
INITCOMMONCONTROLSEX ICC;
/* 関数本体部 */
switch (uMsg)
{
case WM_CREATE:
hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE);
// コモンコントロール初期化
ICC.dwSize = sizeof(INITCOMMONCONTROLSEX);
ICC.dwICC = ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&ICC);
// リストビュウ作成
hCtlListView = CreateWindowEx(WS_EX_CLIENTEDGE,
WC_LISTVIEW,
NULL,
WS_CHILD | WS_VISIBLE | LVS_REPORT,
10,
30,
380,
320,
hWnd,
0,
hInstance,
NULL);
for (i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++)
{
ListView_InsertColumn(hCtlListView, i, &LVColumn[i]);
}
break;
}
return 0L;
}
リストビュウに外枠をつけるために ウィンドウの作成方法を CreateWindow() から
CreateWindowEx()
に変更しています。
第1引数の拡張ウインドウスタイルには、WS_EX_CLIENTEDGE を指定します。
また、LVM_INSERTCOLUMN を送出するには、SendMessage()を使って、SendMessage(hCtlListView, LVM_INSERTCOLUMN, 0, (LPARAM) &LVColumn[i]) と書きますが、今回もマクロ ListView_InsertColumn を使用して記述しています。
構造体配列の変数 LVColumn[ ] は宣言時に、初期化を行っています。
ですが、メンバーの数は足りません。
このように、構造体の要素よりも初期値が少ない場合は、仕様として許されていますが、初期値が指定されていない残りのメンバーは 0 に初期化されます。
また、構造体の各メンバーの後についているカンマですが、最後のメンバーに付けても付けなくてもエラーにはなりません。
同様に配列の最後の要素のカンマも付けても付けなくてもいいようです。
なんとなく、それらしい感じになってきていませんか?

最後に、解説しておきたいところがあります
for (i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++) { ListView_InsertColumn(hCtlListView, i, &LVColumn[i]); }
これは、Loop 文でカラムを追加しているのですが、その回数の指定方法がちょっと特殊です。
今回追加したいカラムは、3 個("名前"、"サイズ"、"更新日時")ですので、
for for (i = 0; i < 3; i++)
{
ListView_InsertColumn(hCtlListView, i, &LVColumn[i]);
}
と書いてもまったく問題ないわけですが、プログラムの世界では、この 3 をプログラムのコードに直接書くことを嫌う傾向があります。
なぜかというと、今は、3 個ですが、将来カラムの数を増やしたときに 3 を直すのを忘れてバグを誘発することになるからです。
※こういった、定数直書きをマジックナンバーといったりします。
このため、将来カラムの個数を増やしたときも安全なコードを書いておいたのが、先ほどのコードなのです。
何をやっているかと言うと、まず sizeof を使って、LV_COLUMN の配列全体のバイト数を取得します。
それとは別に、sizeof を使って、LV_COLUMN の型自体のバイト数を取得します。
LV_COLUMN の配列全体のバイト数 を LV_COLUMN の型自体のバイト数 で割るということで、3 を導き出しています。
いつものごとく、実際に表示させて見ましょう。

構造体のメンバーは、増えることがよくありますし、OS のバージョンなどによっても変わります。
さらに、構造体のメンバーの型のバイト数を単純に足したものとも異なることがあります。
これは、OS が勝手に扱いやすい形に整形(アラインメント)したりするからです。
int や char の型のバイト数は、わざわざ sizeof しなくてもわかりますので、通常 sizeof
は、配列の全体のバイト数に使われますが、構造体の場合は、その型が示すバイト数の取得にも、sizeof が活躍します。
※ 前回 sizeof の例外にちらと触れましたが、早速出てきましたね。
最後に今回の「WinMain.cpp」の全文を載せておきます。
/* ========================================================================== */ /* FILE WinMain.cpp */ /* CREATE 2006/10/01 */ /* MODIFY 2006/12/21 */ /* REMARK */ /* COPYRIGHT 2000-2007, Latin. All Rights Reserved */ /* ========================================================================== */ #include <Windows.h> #include <WindowsX.h> #include <CommCtrl.h> #include "Resource.h" /* -------------------------------------------------------------------------- */ /* CALLBACK WndProc */ /* PARAM HWND hWnd : ウィンドウハンドル */ /* UINT uMsg : メッセージ */ /* WPARAM wParam : メッセージの追加情報 */ /* LPARAM lParam : メッセージの追加情報 */ /* RETURN LRESULT */ /* メッセージ処理の結果 */ /* REMARK */ /* -------------------------------------------------------------------------- */ LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { /* 変数宣言部 */ HINSTANCE hInstance; HWND hCtlCombo; HWND hCtlListView; LV_COLUMN LVColumn[] = { {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 150, "名前", 0, 0}, {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_RIGHT, 75, "サイズ", 0, 1}, {LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM, LVCFMT_LEFT, 110, "更新日時", 0, 2} }; char szCurDir[MAX_PATH + 1]; char szDrives[128]; int i; INITCOMMONCONTROLSEX ICC; /* 関数本体部 */ switch (uMsg) { case WM_COMMAND: switch (LOWORD(wParam)) { case IDM_EXIT: SendMessage(hWnd, WM_CLOSE, 0, 0L); break; default: return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } break; case WM_CREATE: hInstance = (HINSTANCE) GetWindowLong(hWnd, GWL_HINSTANCE); hCtlCombo = CreateWindow("COMBOBOX", NULL, WS_CHILD | WS_VISIBLE | CBS_SORT | CBS_DROPDOWN | CBS_AUTOHSCROLL, 10, 5, 450, 100, hWnd, 0, hInstance, NULL); SendMessage(hCtlCombo, WM_SETFONT, (WPARAM) GetStockObject(DEFAULT_GUI_FONT), MAKELPARAM(true , 0)); GetCurrentDirectory(MAX_PATH + 1, szCurDir); ComboBox_SetText(hCtlCombo, szCurDir); ComboBox_AddString(hCtlCombo, szCurDir); GetLogicalDriveStrings(sizeof(szDrives), szDrives); i = 0; while (szDrives[i] != '\0') { ComboBox_AddString(hCtlCombo, &szDrives[i]); // 次のドライブ名へ while (szDrives[i] != '\0') { i++; } i++; } // コモンコントロール初期化 ICC.dwSize = sizeof(INITCOMMONCONTROLSEX); ICC.dwICC = ICC_LISTVIEW_CLASSES; InitCommonControlsEx(&ICC); // リストビュウ作成 hCtlListView = CreateWindowEx(WS_EX_CLIENTEDGE, WC_LISTVIEW, NULL, WS_CHILD | WS_VISIBLE | LVS_REPORT, 10, 30, 380, 320, hWnd, 0, hInstance, NULL); for (i = 0; i < (sizeof(LVColumn) / sizeof(LV_COLUMN)); i++) { ListView_InsertColumn(hCtlListView, i, &LVColumn[i]); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, uMsg, wParam, lParam)); } return 0L; } /* -------------------------------------------------------------------------- */ /* WINAPI WinMain */ /* PARAM HINSTANCE hInstance : 現在のインスタンス */ /* HINSTANCE hPrevInst : 直前のインスタンス */ /* LPSTR lpCmdLine : コマンドライン引数 */ /* int nCmdShow : ウインドウの表示形式 */ /* RETURN int : */ /* メッセージの wParam パラメータ */ /* REMARK Win32 アプリケーションの初期エントリポイント */ /* Windows システムが呼び出す。 */ /* -------------------------------------------------------------------------- */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { /* 変数宣言部 */ HWND hWnd; WNDCLASSEX WndClass; MSG Msg; /* 関数本体部 */ // ウインドウクラス作成 WndClass.cbSize = sizeof(WndClass); WndClass.style = CS_HREDRAW | CS_VREDRAW; WndClass.lpfnWndProc = WndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = hInstance; WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); WndClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); WndClass.hCursor = LoadCursor(NULL, IDC_ARROW); WndClass.hbrBackground = (HBRUSH) GetStockObject(WHITE_BRUSH); WndClass.lpszMenuName = MAKEINTRESOURCE(IDM_MENUMAIN); WndClass.lpszClassName = "FTPManager"; // ウィンドウクラス登録 if (! RegisterClassEx(&WndClass)) { return FALSE; } // ウィンドウ作成 hWnd = CreateWindow(WndClass.lpszClassName, // 登録されているクラス名 "FTPManager", // ウィンドウ名 WS_OVERLAPPEDWINDOW, // ウィンドウスタイル CW_USEDEFAULT, // ウィンドウの横方向の位置 CW_USEDEFAULT, // ウィンドウの縦方向の位置 CW_USEDEFAULT, // ウィンドウの幅 CW_USEDEFAULT, // ウィンドウの高さ NULL, // 親ウィンドウまたはオーナーウィンドウのハンドル NULL, // メニューハンドルまたは子ウィンドウ ID hInstance, // アプリケーションインスタンスのハンドル NULL); // ウィンドウ作成データ if (! hWnd) { return FALSE; } // ウィンドウの表示 ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); // メッセージループ while (GetMessage(&Msg, NULL, 0, 0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return (int) Msg.wParam; }
本日のソース: FTPManager105(VC8).zip FTPManager105(BCC).zip
皆さんは、変数の付け方にはどのようなこだわりがありますか?私は、ハンガリー記法を採用しています。
ハンガリー記法とは、本来の名前の前に小文字で、変数の型をシンボル化した文字を付加する記法です。
この特徴は、変数を見ただけで変数の型がわかりやすいということにあります。
構造体については、大文字の型に対して、大文字小文字混在の変数宣言をするようにしています。
また、「HWND hWnd;」 の様に決まった変数名があり、これはこのまま使っています。
が、最近はハンガリー記法はあまり推奨されなくなってきています。
しかも、今まで推奨してきたマイクロソフトがやめるべきだといい始めました。
一時期、変数名は省略形で何でも表記しているのがもてはやされる時代がありましたが、わかりにくいという理由で今度は、長い変数名が主流になってきています。
これまた、省略形は使わないといった理由で CurrentDirectory という長い変数名なっています。
結論から言えば、わかりやすければよいということになります。
見た目にわかりやすいというのが重要でしょう。
CurrentDirectory がわかりやすいか szCurDir がわかりやすいか、皆さん自身で判断してください。