EmptyPage.jp > WZ Resources > TX-Cでリストビューを使う
WZ EDITORのマクロ言語、TX-Cでは、宣言さえしてしまえばWindowsのあらゆるコモンコントロールを利用することができます。もちろんリストビューも使えます。ここではTX-Cでリストビューを使うことをいくらか楽にするための関数のサンプルとその使用法を紹介しています。
TX-Cの素敵な点のひとつが、用意されているテキスト操作用のAPIだけでなく、Windows APIや外部DLLの関数なども、マクロから呼び出して使えるところです。Windowsのバージョン・アップによって追加されたり、改善されたコモンコントロールも、WZ本体がAPIを用意して対応するというのを待つことなく、(適切な宣言さえしてしまえば)ほとんど制約なく実装することができます。
リストビューも、そうしたコモンコントロールのひとつです。WZにはダイアログで頻繁に使われるコントロール(リストボックス、エディットボックス、etc.)については、それらを簡単に利用できるようにするための専用の関数群が用意されていますが、リストビューについてはありません。Windows APIを駆使して実装することになるわけですが、実際にやってみるとこれはけっこう面倒な作業です(単に「利用できる」というのと、「簡単に利用できる」というのはやっぱり違う)。
いちばん面倒なのは、まずそのコントロールについてどのようなWindows APIが用意されていて、どういう手順で使うのかということを調べなければならないことです。専用の構造体やメッセージを把握する必要もあります。しかも往々にして、新しめのコモンコントロールについてはMSDNライブラリのドキュメントがまだ日本語になってなかったりします。
そこで、はじめてリストビューをTX-Cから利用しようという方々が、そのための予備知識を習得する手間を少しでも減らせるかと思い、簡単な解説と、いくつかの関数の例とを書いてみることにしました。参考になりましたら幸いです。
と、大見得を切ったものの、僕自身はプログラマでもなんでもないので、事実誤認や、効率の悪い使い方を提示していることがありえます。そんなときはメールやVisitor's Voiceのページなどから指摘していただけたらありがたいです。
リストビューを使ったコントロールでいちばんよく目にしているはずなのが、Windowsのデスクトップやエクスプローラの、ファイルやフォルダが並んでいる領域です。エクスプローラでは、[大きいアイコン]、[小さいアイコン]、[一覧]、[詳細]などから表示スタイルを切り替えることができますが、これらの表示スタイルすべてが、リストビューのウィンドウスタイルを利用して実現されています。また、コントロールパネルやアプリケーションに用意されている、各種設定を行うためのダイアログにも、リストビューが利用されているものが見つかります。リストビューは、表現能力が多彩で、多くの情報を整理して表示できます。
設定用のダイアログでは、リストビューのなかでも[詳細]表示(report view)がよく使われます。アイテムと、それに付随する情報を表形式で表示できるので便利です。また、アイテムの先頭にチェックボックスをつけることもできます。こうした機能の使いかたしだいで、一覧性とわかりやすさを両立させた、優れたユーザ・インタフェースを提供することもできるわけです。
TX-Cのマクロで使われるシチュエーションを考慮して、また、アイコンを利用しようとするとそれはそれでまた説明が必要なこと、さらにドラッグ&ドロップの処理についてページ作者自身がまだうまく理解できていないこと(すんません)を考慮して、ここでは、アイコンを使わない[詳細]表示(report view)のリストビューのダイアログへの実装を扱います。
というわけで、これから実際にリストビューをTX-Cから利用してみます。リストビューでの基本的な処理を扱ったサンプルマクロでの実装をもとに話を進めています。
サンプルマクロでは、リストビューへのデータの表示、アイテムの移動、追加と削除、そしてリストビューからのデータの読み込みといった処理をおこなっています。
以下の説明で出てくるコードはこのサンプルマクロからのものです。
まず、なにはともあれダイアログを作成し、これにリストビュー・コントロールを追加します。TX-C APIで用意されていないコントロールを追加するには、__dialogAddItem関数を使用します。この関数ではコントロールを追加したあとに、続いて追加する次のコントロールの位置をあわせたりする必要があるので、サンプルマクロではそこまでの処理を、dialogAddListViewという関数でまとめてあります。
int dialogAddListView(HDIALOG hd, int id, mchar *szcaption, int width, int height, long style);
ダイアログにリストビューを追加します。
この関数をダイアログの作成過程(main関数内)で呼び出します。
void main(TX *text) { (… 省略 …) HDIALOG hd = dialog("リストビュー サンプル"); (… 省略 …) long style = LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS; dialogAddListView(hd, IDD_LVSAMPLE, "リスト(&L)", 40, 10, style); (… 省略 …) }
ここで、変数styleには一般的なリストビューのスタイルを指定します。リストビューのスタイルには以下のようなものがあります。
| スタイル | 説明 |
|---|---|
| LVS_ICON | [大きなアイコン]表示(Icon view)にする。 |
| LVS_LIST | [一覧]表示(list view)にする。 |
| LVS_NOCOLUMNHEADER | [詳細]表示(report view)でカラムヘッダを表示しない。デフォルトでは表示される。 |
| LVS_NOSORTHEADER | カラムヘッダにボタンのような動作をさせない。[詳細]表示(report view)でカラムをクリックしても、ソートなどの動作をしないときに使用します。 |
| LVS_REPORT | [詳細]表示(report view)にする。 |
| LVS_SHOWSELALWAYS | コントロールがフォーカスを失っても選択したアイテムが強調表示されるようにする。 |
| LVS_SINGLESEL | いちどにひとつのアイテムしか選択できないようにする。デフォルトでは複数のアイテムを選択できる。 |
| LVS_SMALLICON | [小さなアイコン]表示(Small icon view)にする。 |
| LVS_SORTASCENDING | アイテムの文字列で昇順にソート。 |
| LVS_SORTDESCENDING | アイテムの文字列で降順にソート。 |
当然ながら、LVS_ICON、LVS_LIST、LVS_REPORT、LVS_SMALLICONは同時には指定しません。4つの表示スタイルのうちどれかひとつを指定します。
リストビューには、この他に拡張スタイル(extended style)というものがあります。アイテムにチェックボックスを付けたり、アイテムの選択状態を行全体の強調表示で表したい場合は、拡張スタイルを使って指定します。拡張スタイルの指定は、ダイアログプロシージャ内のWM_INITDIALOGメッセージが来たところで実行しています。
拡張スタイルの指定ばかりでなく、リストビューではユーザのいろいろな操作に反応する必要もあるので、dialogSetDlgprocTxpcode関数でダイアログプロシージャを設定します。
サンプルマクロでは、main関数内で、この他にリストビューで表示するもとのデータとなるテキスト(タブ区切りの文字列)を用意したり、リスト操作用のボタンを付けたりしています。
このままではリストビューにはなにもアイテムがない状態なので、ダイアログの初期化の段階(ダイアログプロシージャ内でWM_INITDIALOGが来たとき)でリストビューの内容も初期化します。もととなるデータは、(TX-Cらしく)タブ区切りのテキストとして用意しています。
[詳細]表示(report view)のリストビューでは、まずカラムヘッダを用意し、それからアイテムとそれに続くサブアイテム(2桁目以降)を追加していくという作業を、リストビュー用のWindows APIを使用して進めていきますが、ここではテキストをもとに一連の作業を自動的に行う関数、listviewInitFromTextを作成し、それを呼び出しています。
void listviewInitFromText(HWND hwnd, TX *text);
タブ区切りテキストでリストビューを初期化します。カラムヘッダが表示されるリストビューでは、テキストの1行目はヘッダとして解釈されます。また、チェックボックス付きのリストビューでは、テキスト行末の桁情報はチェックボックスの真偽値として扱われます("1"ならば真、"0"ならば偽)。
BOOL CALLBACK lvsampleDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ダイアログプロシージャ
HDIALOG hd = dialogFromHwnd(hwnd);
static TX *textwork; // 作業用のテキスト
switch (message) {
(… 省略 …)
case WM_INITDIALOG: { // ダイアログ初期化
// カスタムデータの取得
TX *text = dialogGetCustdata(hd);
// リストビューのハンドル取得
HWND hwndLV = GetDlgItem(hwnd, IDD_LVSAMPLE);
#if 0
// リストビューにチェックボックスをつけてみる。
ListView_SetExtendedListViewStyle(hwndLV, LVS_EX_CHECKBOXES);
#endif
// リストビューの内容を初期化
ListView_SetText(hwndLV, text);
textwork = textnew(); // 作業用のテキストをオープン
}
break;
}
return FALSE;
}
#if 0と#endifで挟まれている部分は、このままではコンパイルされません。#if 0を#if 1と書き換えることでコンパイルされるようになります。この部分では、前述の拡張スタイルを指定して、リストビューにチェックボックスを追加しています。チェックボックスがある場合と、ない場合とでコンパイルして、listviewInitFromText関数がテキストの最後の桁の解釈を変えるのを確認してみてください。
なお、リストビューの拡張スタイルには他に以下のようなものがあります。
| スタイル | 説明 |
|---|---|
| LVS_EX_CHECKBOXES | リストビュー内のチェックボックスを有効にする。アイテムのチェックボックスの状態を知るにはListView_GetCheckStateを使用します。 |
| LVS_EX_FULLROWSELECT | アイテムが選択されたときに、アイテムとそのサブアイテム全体が強調表示されます。このスタイルは[詳細]表示(report view)でのみ有効です。 |
| LVS_EX_GRIDLINES | アイテムとサブアイテムの周囲にグリッド線を表示します。このスタイルは[詳細]表示(report view)でのみ有効です。 |
| LVS_EX_HEADERDRAGDROP | ドラッグ&ドロップでカラムヘッダのカラムの順番を入れ替えることができるようにします。このスタイルは[詳細]表示(report view)でのみ有効です。 |
| LVS_EX_ONECLICKACTIVATE | ユーザがアイテムをクリックしたときに、親ウィンドウにLVN_ITEMACTIVATEメッセージを送ります。また、このスタイルではリストビューのホット・トラッキングが有効になります。 |
| LVS_EX_TRACKSELECT | ホバー選択を有効にします。 |
| LVS_EX_TWOCLICKACTIVATE | ユーザがアイテムをダブルクリックしたときに、親ウィンドウにLVN_ITEMACTIVATEメッセージを送ります。また、このスタイルではリストビューのホット・トラッキングが有効になります。 |
ホット・トラッキング(hot tracking)というのは、カーソルがアイテムの上に来たときに、選択状態とは別に、アイテムを強調表示することをいいます。ホバー選択(hover selection)――トラック選択(track selection)ともいいます――というのは、カーソルがアイテム上で一定時間静止していたときに自動的にアイテムを選択状態にすることをいいます。
WM_INITDIALOGメッセージでは、この他に、ダイアログのカスタムデータの受け渡しや、作業用のテキストのオープンをしています。作業用のテキスト変数は、static変数にしなければいけないことに注意。このテキストはダイアログが閉じられるとき(WM_DESTROY)にいっしょにクローズしています。
サンプルマクロは、ボタンを押すことでリストビューに対して、簡単な操作ができるようになっています。アイテムの上下への移動は、いったんアイテムの内容を作業用のテキストに書き出してから、そのアイテムを削除し、リストのひとつ上(またはひとつ下)の位置にそのアイテムを挿入する、という処理で実現しています。
リストビューのアイテムの削除には、Windows APIのListView_DeleteItem関数をそのまま利用していますが、アイテムの内容の取得と挿入では、アイテムの内容をタブ区切りでテキストに書き出すlistviewOutputItemText関数と、タブ区切りのテキストの内容をアイテムに出力するlistviewSetItemFromText関数とを作成して使用しています(作業用のテキストを用意しておいたのはこのためです)。
BOOL ListView_DeleteItem(HWND hwnd, int iItem);
(Windows API)
リストビューからアイテムを削除します。成功したらTRUEを、失敗したらFALSEを返します。
void listviewOutputItemText(HWND hwnd, int index, TX *text);
リストビューのアイテムの内容をタブ区切りの文字列にしてテキストに挿入します。末尾で改行します。チェックボックスがついたリストビューでは、最後にチェックボックスの真偽値が出力されます。
int listviewSetItemFromText(HWND hwnd, int index, TX *text);
テキストのカーソル位置の段落をタブ区切りのデータとみなしてリストビューにアイテムを挿入します。挿入したカラム数を返す。チェックボックスがついたリストビューでは、最後のアイテムはチェックボックスの真偽値として扱われます。
選択したアイテムを上に移動する処理は次のようになります。
BOOL CALLBACK lvsampleDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ダイアログプロシージャ
(… 省略 …)
switch (message) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
case IDD_UP: { // 上へ
// リストビューのハンドル取得
HWND hwndLV = GetDlgItem(hwnd, IDD_LVSAMPLE);
// 選択されているアイテムを取得
int index = ListView_GetNextItem(hwndLV, -1, LVNI_SELECTED);
if (-1 == index || 0 == index) break; // 取得失敗またはいちばん上
// ワーク用テキストにアイテムを出力
txDeleteText(textwork);
listviewOutputItemText(hwndLV, index, textwork);
txJumpFileTop(textwork); //カーソル位置合わせ
ListView_DeleteItem(hwndLV, index); // リストビューのアイテム削除
index--; // 位置更新
// 新しい位置にワーク用テキストのアイテムを挿入
listviewSetItemFromText(hwndLV, index, textwork);
// 選択状態にする
ListView_SetItemState(hwndLV, index,
LVIS_FOCUSED | LVIS_SELECTED, LVIS_FOCUSED | LVIS_SELECTED);
}
break;
(… 省略 …)
}
break;
(… 省略 …)
}
return FALSE;
}
上記のコードでちょっと妙に見える個所があるとすれば、ListView_GetNextItemという関数で選択されているアイテムを取得
しているところと、アイテムを選択状態にするために呼び出しているListView_SetItemState関数の引数の後半でしょう。これはAPIの仕様がそうなっているので仕方ないのですが、どちらも使い方が直感的でないので、はじめは戸惑うところです。
int ListView_GetNextItem(HWND hwnd, int iStart, UINT flags);
(Windows API)
リストビューから特定の属性を持つアイテムを探します。アイテムが見つかったらその位置を、見つからなければ-1を返します。
| LVNI_FOCUSED | フォーカスを持っているアイテム |
|---|---|
| LVNI_SELECTED | 選択状態になっているアイテム |
BOOL WINAPI ListView_SetItemState(HWND hwnd, int i, UINT state, UINT mask);
(Windows API)
リストビューのアイテムの状態を変更します。
| LVIS_FOCUSED | アイテムがフォーカスを持ちます。複数のアイテムを選択できる場合でも、フォーカスを持つアイテムはひとつだけです。 |
|---|---|
| LVIS_SELECTED | アイテムが選択されます。 |
アイテムの追加、削除の処理もこれらの関数の組み合わせです。アイテムの追加の部分では、コードを簡潔にするために、決められたアイテムを決められた位置(リスト末尾)にしか追加できませんが、ダイアログを表示してアイテムの内容をユーザに入力させたり、現在選択されている位置に挿入するような動作に変更することもできるでしょう。
ユーザがいろいろとリストビューを操作して、最終的にダイアログの[OK]ボタンを押すと、リストビューの内容を、初期化の際に使ったテキストに書き出すようにします。ここでは、listviewOutputTextという関数を作成して呼び出しています。テキストに出力されたデータは、初期化のときと同様、タブ区切りになっています。あとは、TX-Cのテキスト操作用APIを使用してテキスト上のデータを処理していけばいいわけです(サンプルマクロでは、ただ単にそのテキストを表示するだけですが)。
void listviewOutputText(HWND hwnd, TX *text);
リストビューの内容をタブ区切りでテキストに出力します。カラムヘッダが表示されている場合は、テキストの1行目はヘッダ名になります。チェックボックス付きのリストビューの場合は、行の末尾にチェックボックスの真偽値が追加されます。
BOOL CALLBACK lvsampleDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ダイアログプロシージャ
(… 省略 …)
switch (message) {
case WM_COMMAND:
switch (LOWORD(wParam)) {
(… 省略 …)
case IDOK: { // [OK] ボタンが押された
// カスタムデータの取得
TX *text = dialogGetCustdata(hd);
// リストビューのハンドル取得
HWND hwndLV = GetDlgItem(hwnd, IDD_LVSAMPLE);
// リストビューの内容をテキストに出力
listviewOutputText(hwndLV, text);
}
break;
}
break;
(… 省略 …)
}
return FALSE;
}
ここまでで説明できなかった関数や、サンプルマクロに含まれていながらデモでは使われていない関数などについての解説です。リストビューを使うときには、ここで書いてあるような処理を行う必要があることも多いでしょう。
ListView_GetItemCount関数を使うと、リストビューにあるアイテムの総数を取得できます。
int ListView_GetItemCount(HWND hwnd);
(Windows API)
リストビューのアイテム数を取得します。
リストビューのアイテム数を返します。
これまで出てきた関数を使用すれば、作業用テキストにアイテムの内容を挿入することでアイテムの文字列を知ることはできますが、それでは非効率的ですし、面倒ですので、アイテムの文字列を取得するためのAPIを紹介しておきます。
void ListView_GetItemText(HWND hwnd, int iItem, int iSubItem, LPTSTR pszText, int cchTextMax);
(Windows API)
リストビューのアイテムまたはサブアイテムの文字列を取得します。
反対に、アイテムに文字列をセットするときには、ListView_SetItemText関数を使います。
void ListView_SetItemText(HWND hwnd, int iItem, int iSubItem, LPTSTR pszText);
(Windows API)
リストビューのアイテムまたはサブアイテムの文字列を変更します。
これまで出てきた関数を使用すれば、作業用テキストにアイテムの内容を挿入することでチェックボックスの状態を知ることもできますが、それでは非効率的ですし、面倒ですので、チェックボックスの状態を取得するためのAPIを紹介しておきます。
BOOL ListView_GetCheckState(HWND hwndLV, UINT iIndex);
(Windows API)
アイテムのチェックボックスの状態を取得します。LVS_EX_CHECKBOXES拡張スタイルが指定されているリストビューでのみ使用します。チェックが入っているかどうかを返します。
では逆に、アイテムのチェック状態を設定するにはどうするかというと、ListView_SetCheckState関数が用意されて……ないんです。SDKのドキュメントには、たしかにListView_SetCheckStateという名前は出てくるのですが、ただ単に「アイテムのチェックボックスの状態を設定するには、以下のようなマクロを使いなさい」と、そのマクロの定義が書かれているだけで、なぜかヘッダファイルには記述されていません。仕方ないので、SDKドキュメントに書かれているとおり、マクロをコードの先頭で定義します。
#include <windows.h> extern "commctrl.dll" { #pragma multidef+ #include <commctrl.h> #pragma multidef- #ifndef ListView_SetCheckState #define ListView_SetCheckState(hwndLV, i, fCheck) \ ListView_SetItemState(hwndLV, i, \ INDEXTOSTATEIMAGEMASK((fCheck)+1), LVIS_STATEIMAGEMASK) #endif }
ListView_SetCheckState(HWND hwndLV, int i, BOOL fCheck);
アイテムのチェックボックスの状態を設定します。
サンプルマクロでは、各カラムの幅は関数任せにしていましたが、じっさいには初期化時にカラムの幅を調節しておきたいというのが普通です。そのために、listviewSetColumnWidthという関数が、サンプルマクロに含まれています。この関数はじっさいには使われていませんが、WM_INITDIALOGメッセージのところでカラムの幅を調節するようにマクロを書き換えて、その動作を確認してみてください。
void listviewSetColumnWidth(HWND hwnd, int widthArr[]);
リストビューのカラムの幅を調節します。
たとえば、サンプルマクロで最初と2番目のカラムでリストビュー全体の半分を占めるようにしたい、といった場合、次のようなコードを追加することになります。
BOOL CALLBACK lvsampleDlgProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ダイアログプロシージャ
(… 省略 …)
switch (message) {
(… 省略 …)
case WM_INITDIALOG: { // ダイアログ初期化
(… 省略 …)
// リストビューの内容を初期化
listviewInitFromText(hwndLV, text);
int colwidth[] = {25, 25, 0}; // カラム幅情報
listviewSetColumnWidth(hwndLV, colwidth);
(… 省略 …)
}
break;
}
return FALSE;
}
サンプルマクロで使用している関数を再利用することで、簡単なリストビュー・コントロールを少ない手間で実装できるようになります。サンプルマクロの関数定義をコピーして、リストビューを付けようとしているマクロのソースファイルにペーストしてしまえば手軽です。「//## list view」と書かれている行から、「//## main」と書かれている行の直前までが、リストビューを使うために作った、ここで紹介している関数群の実体です。この部分は、そっくりあなたのマクロに貼り付けて利用してしまって構いません。関数の性能に不満を覚えたら、その関数のコードを変更して、自分のマクロに取り込めばよいでしょう。
リストビューは、ひじょうに柔軟性の高いコントロールなので、その表示能力をさらに生かしたいという場合にはリストビュー用に用意されているその他のWindows APIを駆使していくことになります。MSDNライブラリ(MSDN Library)などでリストビューについての情報が得られます。SDKレベルでのプログラミングについて解説しているWebサイトなども、ひじょうに参考になるでしょう。
長文で薄いうえに、対象の限られた情報ながら、このページがだれかのお役に立つことを願います。