MSDN マガジン > Home > 発行物 > 2007 > August >  Windows と C++: Windows Vista コントロールの拡張
Windows と C++
Windows Vista コントロールの拡張
Kenny Kerr

コードのダウンロード : : WindowsWithC++2007_08.exe (165 KB)
Browse the Code Online
「Windows と C++」の初回コラムへようこそ。ここでは、Windows Vista™ および Windows Server® 2008 における、ネイティブ C++ を使用した開発について説明します。今回のコラムでは、Windows® の最新バージョンで導入されたすばらしい新機能を紹介します。
このコラムの連載開始には、2 つの主な理由があります。第 1 に、Microsoft® .NET Framework の驚くべき成功にもかかわらず、ネイティブ C++ を使用する方が望ましいシナリオも多数存在します。.NET の使用はさらに拡大すると思われますが、C++ も消えることはありません。第 2 に、新しいオペレーティング システムのメジャー リリース時には、短期間のうちに、MFC、Active Template Library (ATL)、.NET などのすべてのアプリケーション フレームワークですべての新機能や機能拡張を使用できるようになるとは限りません。Windows SDK のあらゆる機能に自由にアクセスできる言語は C++ だけであり、Windows Vista 以降、そうした新機能はさらに多くなります。今後の .NET の詳細については、「.NET について」のリンクを参照してください。
このシリーズの初回では、コントロールについて説明します。Windows Vista で新しく導入されたコントロールは多くありません (ただし、IPv4 名、IPv6 名、および DNS 名の入力と検証を行うネットワーク アドレス コントロールが新しく導入されました) が、既存の標準的なコモン コントロールの多くに、多数の機能拡張と新機能が提供されています。これはある意味で朗報です。ほんの少しの労力で、これら多数の新機能をアプリケーションに活用できるのですから。このコラムでは、最もよく使用されるコントロールのいくつかを駆け足で紹介し、Windows Vista 以降で提供されるさまざまなすばらしい新機能について説明します。MSDN® Magazine Web サイトから入手できるこのコラムのダウンロードには、ここで説明するコントロール機能の多くに関する例が収録されています。

ボタン コントロール
新しい外観を持つ Windows Vista のコントロールのいくつかは、ボタン コントロールです。新しいスタイルのフラグによって、従来とは外観がかなり異なるボタンが導入されました。図 1 に新しいボタン スタイルをいくつか示します。
図 1 Windows Vista の新しいボタン スタイル 
BS_SPLITBUTTON スタイルでは、分割ボタンが作成されます。ボタンの親ウィンドウは、ユーザーがボタンのドロップダウン矢印をクリックしたことを示す BCN_DROPDOWN 通知メッセージを受け取ります。開発者の役目は、ショートカット メニューが表示されるようにすることです。ドロップダウン ハンドラを、たとえば次のように実装します。
CRect rectangle;
VERIFY(m_splitButton.GetWindowRect(
    &rectangle));

TPMPARAMS params = { sizeof(TPMPARAMS) };
params.rcExclude = rectangle;

CMenuHandle menu = m_menu.GetSubMenu(0);

VERIFY(menu.TrackPopupMenuEx(TPM_LEFTBUTTON,
    rectangle.left, rectangle.bottom,
    m_hWnd, &params));
このコードでは、まず、ボタンのバインドを計算しています。次に、TPMPARAMS 構造体を設定することにより、要求された場所にメニューを表示できない場合、ボタンに重ならないよう、使用可能な次の端に表示することを TrackPopupMenuEx に指示しています。BCM_GETSPLITINFO メッセージや BCM_SETSPLITINFO メッセージを使用して、ドロップダウン矢印の外観を照会したり調整したりすることもできますが、多くの場合、既定値のままで問題ありません。
BS_COMMANDLINK スタイルでは、コマンド リンクが作成されます。コマンド リンクでは、ボタンのキャプションの隣に矢印が表示されるほか、小さい文字のメモがオプションで表示されます。ボタンのキャプションは通常どおり SetWindowText 関数で設定しますが、新しい BCM_SETNOTE メッセージでは、キャプションの下に表示されるメモを設定します。
最後に、LPARAM に TRUE (アイコンを含める場合) または FALSE (アイコンを含めない場合) を設定した BCM_SETSHIELD メッセージを送信すると、今や有名な "昇格が必要" シールドをボタンに表示するよう Windows に指示できます。次に、簡単な例を示します。
button.SendMessage(BCM_SETSHIELD, 0, required);

編集コントロールとコンボ ボックス コントロール
Windows XP では、EM_SETCUEBANNER メッセージを使用して編集コントロールにテキスト キューを表示できるようになりました。このキューは、コントロールがフォーカスを取得した際に非表示になった情報を、ユーザーに知らせます。Windows Vista ではこれに軽微ながらも有用な拡張が加えられ、コントロールにフォーカスがある場合にもキュー テキストを表示できるようになりました。この一見小さな機能強化によって、多くの場合に静的コントロールが不要になります。以前は使用されていなかった WPARAM を使用して TRUE に設定するだけで、この機能を利用できます。簡素化のため、Edit_SetCueBannerTextFocused マクロが提供されています。
VERIFY(Edit_SetCueBannerTextFocused(m_edit, L”Cue text”, TRUE));
Windows Vista で CB_SETCUEBANNER メッセージを使用すると、コンボ ボックスでもこの機能をほぼ同様に利用できます。ただし、コントロールにフォーカスがある場合にキュー テキストを表示することはできず、"ドロップ リスト" スタイルのコンボ ボックスに対してだけ、いずれかを選択するまで、キュー テキストが表示されます (図 2 を参照)。
図 2 コンボ ボックスの例 
簡素化と利便性のため、ComboBox_SetCueBannerText マクロが提供されています。
VERIFY(ComboBox_SetCueBannerText(m_comboBox, L”Cue text”));

ツリー ビュー コントロール
Windows 開発者はついに、ツリー ビュー コントロールのスタイル拡張を実現しました。図 3 に、新しいツリー ビュー コントロール スタイルとテーマの使用例をいくつか示します。
図 3 ツリー ビュー 
拡張スタイルのビットマスクを取得したり設定したりするため、TVM_GETEXTENDEDSTYLE メッセージおよび TVM_SETEXTENDEDSTYLE メッセージが提供されています。TreeView_GetExtendedStyle マクロは TVM_GETEXTENDEDSTYLE メッセージをラップし、次のように使用できます。
DWORD style = TreeView_GetExtendedStyle(m_treeView);
ただし、通常は、拡張スタイルを取得する必要は生じません。TVM_SETEXTENDEDSTYLE メッセージを使用すると、ビットマスクを使用してスタイル フラグのサブセットを更新できるためです。たとえば次のように、TVS_EX_AUTOHSCROLL 拡張スタイルを追加すると同時に、TVM_SETEXTENDEDSTYLE メッセージをラップする TreeView_SetExtendedStyle マクロを使用することにより、TVS_EX_MULTISELECT 拡張スタイルを削除することができます。
TreeView_SetExtendedStyle(m_treeView,
                          TVS_EX_AUTOHSCROLL,
                          TVS_EX_AUTOHSCROLL | TVS_EX_MULTISELECT);
ツリー ビュー コントロールに対する追加のうち最もすばらしいのは、複数選択がサポートされたことです。ついに開発者が、複数選択を模倣するためのコードをすべて捨て去る日が来ました。TVS_EX_MULTISELECT 拡張スタイルを使用するだけで、後はシステムに任せることができます。もちろん、選択した項目を列挙することが望ましい場合もあります。これは、TVM_GETNEXTITEM メッセージの新しいフラグを使用することにより可能です。従来どおり TVGN_CARET フラグを使用し、現在選択されている項目を取得した後、TVGN_NEXTSELECTED フラグを使用し、選択されている残りの項目を取得します。次に、Windows Template Library の CTreeViewCtrlEx クラスと CTreeItem クラスを使用した簡単な例を示します。
 for (CTreeItem item = 
      m_treeView.GetSelectedItem();
      0 != item;
      item = item.GetNext(TVGN_NEXTSELECTED))
      {
      CString text;
      item.GetText(text);
      TRACE(L”%s\n”, text);
      }
CTreeViewCtrlEx の GetSelectedItem メソッドが TVM_GETNEXTITEM メッセージを TVGN_CARET フラグと共に送信して、選択された最初の項目を取得し、CTreeItem の GetNext メソッドが再度 TVM_GETNEXTITEM メッセージを指定したフラグとツリー項目のハンドルと共に送信して、メッセージ コンテキストを提供しています。
知る必要があるのが選択された項目の数だけであれば、TVM_GETSELECTEDCOUNT メッセージが役立ちます。関連付けられている TreeView_GetSelectedCount マクロはこのメッセージをラップし、次のように使用されます。
DWORD count = TreeView_GetSelectedCount(m_treeView);
拡張スタイルには、検討に値するものがさらにいくつかあります。TVS_EX_DOUBLEBUFFER 拡張スタイルは、ダブルバッファリングを使用してコントロールが描画されるようにします。これにより、コントロールのサイズ変更時のちらつきがなくなります。TVS_EX_AUTOHSCROLL 拡張スタイルを使用すると、コントロールで、選択されたツリー項目がビューに自動的にスクロールされます。これは、よく、水平スクロール バーを非表示にする TVS_NOHSCROLL スタイルと組み合わせて使用されます。次に、簡単な例を示します。
DWORD treeViewStyle = m_treeView.GetStyle();
treeViewStyle |= TVS_NOHSCROLL;
m_treeView.SetWindowLong(GWL_STYLE, treeViewStyle);

TreeView_SetExtendedStyle(m_treeView,
                          TVS_EX_AUTOHSCROLL,
                          TVS_EX_AUTOHSCROLL);
通常は既定の動作で十分ですが、TVM_SETAUTOSCROLLINFO メッセージを使用して自動スクロール アニメーション特性を制御することもできます。
お気付きかもしれませんが、Windows Vista のエクスプローラではフェード効果を持つ "expando" ボタンが新しく採用され、以前のプラス/マイナス ボックスの代わりに新しい矢印のビットマップが表示されています。矢印に切り替えるには、SetWindowTheme 関数を次のように使用して、指定したコントロールでエクスプローラと同じビジュアル スタイルを使用するよう Windows に指示する必要があります。
COM_VERIFY(::SetWindowTheme(m_treeView, L”explorer”, 0));
フェード効果を要求するには、TVS_EX_FADEINOUTEXPANDOS 拡張スタイルを使用します。

ヘッダー コントロールとリスト ビュー コントロール
ヘッダー コントロールを直接使用する開発者が比較的少ないのに対し、リスト ビュー コントロールは Windows 開発者のほぼ全員が日常的に使用しています。このリスト ビュー コントロールの機能の多くは、ヘッダー コントロールに大きく依存しているのです。ヘッダー コントロールもリスト ビュー コントロールも、Windows Vista で大幅に拡張されました。ここでは、多くの開発者がよく使用しているリスト ビュー コントロールの観点から、それらの機能を説明します。ただし、それらの機能の多くはリスト ビュー コントロールと無関係に使用することもでき、一部の機能ではヘッダー コントロールを直接操作する必要が生じます。
Windows Vista ではリスト ビュー コントロールが大幅にアップグレードされ、多くの機能、スタイル、および改善点が新しく追加されました。ツリー ビュー コントロールとは異なり、リスト ビュー コントロールでは以前から拡張スタイルが使用可能でした。しかし、Windows Vista では、はるかに豊富なユーザー操作を実現する拡張スタイルが、新たにいくつか定義されています。
LVS_EX_AUTOSIZECOLUMNS 拡張スタイルは、ビューの列サイズを自動的に変更し、さまざまな列がビュー内にうまく収まるようにします。この機能はユーザーに役立つうえ、非常に簡単に追加できます。
LVS_EX_AUTOCHECKSELECT スタイルでは、Web アプリケーションの表形式コントロールでよく使用される "すべて選択" に似たチェック ボックスが作成されます。これは、新しい HDS_CHECKBOXES ヘッダー コントロール スタイルに対応します。チェック ボックスは、項目チェック ボックスの上の列ヘッダー内に配置されます。このチェック ボックスをクリックすると、リスト ビューの項目チェック ボックスすべてを一度に選択したり選択解除したりできます。図 4 に、この外観の例を示します。
図 4 リスト ビューのすべて選択コントロール 
ツリー ビュー コントロールの場合と同様、リスト ビューにエクスプローラのテーマを適用するには、SetWindowTheme 関数を呼び出して、エクスプローラと同じビジュアル スタイルを使用するよう Windows に指示するだけです。
LVS_EX_COLUMNSNAPPOINTS 拡張スタイルは LVCOLUMN 構造体の追加機能と連動し、ユーザーによって列サイズが変更された際に、列の位置を最小幅に合わせます。LVS_EX_AUTOSIZECOLUMNS 拡張スタイルを使用しても、これと同じ動作が得られます。そのためには、どちらかの拡張スタイルを追加し、LVCOLUMN 構造体を適切に準備します。たとえば、次の例では、初期幅 200 ピクセル、最小幅 100 ピクセルの列を挿入しています。
LVCOLUMN column = { 0 };
column.mask = LVCF_TEXT | LVCF_WIDTH | LVCF_MINWIDTH;
column.pszText = L”Name”;
column.cx = 200; // initial width
column.cxMin = 100; // minimum width
m_listView.InsertColumn(0, &column);
列の最小幅を表す cxMin フィールドを設定することが、新しい LVCF_MINWIDTH マスクによって示されています。
これもお気付きかもしれませんが、Windows Vista のエクスプローラには、詳細表示モードだけでなくすべての表示モードで列ヘッダーを表示する機能があります。既定では、ヘッダー コントロールは詳細表示モードでのみ表示されます。LVS_EX_HEADERINALLVIEWS 拡張スタイルを使用するだけで、列ヘッダーがすべての表示モードで表示されるようになります。ユーザー側でこの機能に慣れる必要がありますが、さまざまな表示モードでユーザーによる表示の並べ替えが可能になり、非常に便利です。
さらにお気付きかもしれませんが、並べて表示などの表示モードでは、エクスプローラの列サイズを変更できず、オーバーフローした列ヘッダーをスクロール表示するための水平スクロール バーも使用されません。代わりに、オーバーフロー ボタンがヘッダー コントロールに表示されます。このボタンをクリックすると、ポップアップ メニューに、表示されずにオーバーフローしたヘッダー列の一覧が表示されます。この動作を再現するには、いくつかの手順が必要です。
まず、詳細以外の表示モードに設定する必要があります。また、LVS_EX_HEADERINALLVIEWS 拡張スタイルを使用し、列ヘッダーを表示する必要があります。次に、リスト ビュー コントロールのヘッダー コントロールに、次のように HDS_NOSIZING スタイルと HDS_OVERFLOW スタイルを追加する必要があります。
CHeaderCtrl header = m_listView.GetHeader();
DWORD headerStyle = header.GetStyle();
headerStyle |= HDS_NOSIZING | HDS_OVERFLOW;
header.SetWindowLong(GWL_STYLE, headerStyle);
HDS_NOSIZING スタイルによってすべての列のサイズ変更が無効化され、HDS_OVERFLOW スタイルによってオーバーフロー ボタンが必要に応じて表示されます。最後に、HDN_OVERFLOWCLICK ヘッダー コントロールの通知メッセージを処理する必要があります。ここで、オーバーフローした列をユーザーに示すため、何らかのポップアップ ウィンドウやメニューを表示できます。そのためには TrackPopupMenu 関数を使用できます。通知メッセージの LPARAM によって提供される NMHEADER 構造体の iItem メンバから、オーバーフローした最初の列のインデックスを取得できます。
ヘッダー コントロールの新機能としては、分割ボタンも挙げられます。列ヘッダーは、ボタン コントロールに関するセクションで説明した分割ボタンによく似ており、列ヘッダー上にマウスを置くとドロップダウン ボタンが表示されます。LVCOLUMN 構造体を新しい列用に準備する際には、LVCFMT_SPLITBUTTON 書式設定フラグを使用します。これは、ヘッダー コントロールの直接操作時に HDITEM 構造体に対して使用される HDF_SPLITBUTTON 書式設定フラグに相当します。ドロップダウン ボタンを押すと、リスト ビュー コントロールから LVN_COLUMNDROPDOWN 通知メッセージが送信されます。これも、ヘッダー コントロールを直接操作する場合の HDN_DROPDOWN 通知メッセージに相当します。LVN_COLUMNDROPDOWN メッセージの LPARAM は、NMLISTVIEW 構造体へのポインタとなり、iSubItem フィールドは、ドロップダウンがクリックされた列のインデックスを示します。
リスト ビュー コントロールに関して Windows Vista でアップグレードされた領域としては、グループ化機能も挙げられます。図 5 に、グループに適用できる新しいラベルをいくつか示します。リスト ビュー グループの定義に使用される LVGROUP 構造体のサイズが倍以上になりました。
図 5 リスト ビューのグループ化 
図 5 のグループは、次のようにして作成できます。
LVGROUP group = { sizeof(LVGROUP) };

group.mask = LVGF_GROUPID | LVGF_HEADER | LVGF_SUBTITLE | LVGF_TASK | 
             LVGF_FOOTER | LVGF_STATE | LVGF_ALIGN;

group.iGroupId = 1;
group.pszHeader = L”Header”;
group.pszSubtitle = L”Subtitle”;
group.pszTask = L”Task”;
group.pszFooter = L”Footer”;
group.state = LVGS_COLLAPSIBLE;
group.uAlign = LVGA_FOOTER_RIGHT | LVGA_HEADER_CENTER;

m_listView.InsertGroup(-1, &group);
.NET について
今後のバージョンの .NET Framework には、このコラムで説明した機能の多くが確実に搭載されます。とはいえ、Windows フォームには新機能の付加に必要なフックが用意されているケースが多いため、これらの機能は新バージョンのリリースを待たずに .NET Framework ベースのアプリケーションで利用することができます。
ここで説明した機能の多くは、コントロールへの特定のメッセージの送信を必要とします。メッセージは通常、user32.dll システム ライブラリからエクスポートされた SendMessage 関数を使用して送信され、次のようにマネージ アプリケーションにインポートすることができます。
[DllImport("user32.dll")]
static extern int SendMessage(IntPtr window, int message, 
                              IntPtr wParam, IntPtr lParam);
1 つの例として、このコラムにあるツリー ビュー コントロールに関するセクションでは、複数選択を可能にする方法を説明しました。次のコードを使用すると、この同じ処理を Windows フォームの TreeView クラスで実現することができます。
int TVM_SETEXTENDEDSTYLE = 4396;
int TVS_EX_MULTISELECT = 2;

SendMessage(m_treeView.Handle, TVM_SETEXTENDEDSTYLE,
            new IntPtr(TVS_EX_MULTISELECT),
            new IntPtr(TVS_EX_MULTISELECT));
次に、通知メッセージの処理方法を示す例をもう 1 つ紹介します。Windows フォームの Control クラスには、必要に応じて特定のメッセージを処理できるプロテクトされた WndProc メソッドが用意されています。コントロールの所有者にさまざまなイベントを知らせるためにコントロールから送信されるメッセージの多くは、LPARAM を NMHDR 構造体へのポインタとする WM_NOTIFY メッセージの形式で送信されます。送信される具体的な通知メッセージは、NMHDR 構造体の code メンバによって識別されます。通知メッセージをマネージ コードで処理する方法を次に示します。
struct NMHDR
{
    public IntPtr hwndFrom;
    public IntPtr idFrom;
    public int code;
}

const int WM_NOTIFY = 0x004E;

protected override void WndProc(ref Message message)
{
    bool handled = false;

    if (WM_NOTIFY == message.Msg)
    {
        NMHDR header = (NMHDR)Marshal.PtrToStructure(message.LParam,
                                                     typeof(NMHDR));
        if (<someCode> == header.code)
        {
            // TODO: handle notification here
            handled = true;
        }
    }

    if (!handled)
    {
        base.WndProc(ref message);
    }
}

LVGS_COLLAPSIBLE 状態フラグを使用すると、グループを折りたたんだり展開したりできるようになります。グループを折りたたむと、グループに属するリスト ビュー項目が非表示になります。プログラムで LVGS_COLLAPSED 状態フラグを使用すると、グループを折りたたむことができます。LVGA_ フラグを使用すると、グループ ラベルの既定の配置をオーバーライドできます。通常、ヘッダーとフッターのテキストは左揃えで表示されます。サブタイトルは常に見出しの下に表示されます。タスク リンクは常に右揃えで表示されます。ユーザーがタスク リンクをクリックすると、LVN_LINKCLICK 通知メッセージが送信されます。タスク リンクが属するグループは、メッセージの LPARAM として提供される NMLVLINK ポインタの iSubItem フィールドによって識別されます。
最後に、LVN_GETEMPTYMARKUP 通知メッセージへの応答として、リスト ビュー コントロールに項目がない場合に表示するテキストを設定することもできます。アンカー タグを使用すると、テキスト内にリンクを作成できます。次に、通知メッセージを処理する関数の例を示します。
LRESULT OnGetEmptyMarkup(LPNMHDR notifyData)
{
    NMLVEMPTYMARKUP* markupInfo = 
        reinterpret_cast<NMLVEMPTYMARKUP*>(notifyData);

    markupInfo->dwFlags = EMF_CENTERED;

    wcscpy_s(markupInfo->szMarkup,
             _countof(markupInfo->szMarkup),
             L”Link <A>one</A> and <A>two</A>.”);

    return TRUE; // set the markup
}
既定では、リスト ビューの左上にメッセージが表示されます。EMF_CENTERED フラグを使用すると、水平方向にも垂直方向にも中央揃えで表示されます。リスト ビューは、ユーザーによるリンクのクリック操作を、グループ タスク リンクと同様に報告します。ただし、LVN_LINKCLICK 通知メッセージ ハンドラは共有が可能です。マークアップ リンクが空の場合、NMLVLINK 構造体の iItem メンバと iSubItem メンバが -1 に設定され、EmptyMarkup に設定されたリンク識別子が、入れ子になった LITEM 構造体に設定され、その iLink メンバがテキスト内のリンクのインデックスに設定されます。

まとめ
Windows SDK を利用して Windows Vista コントロールを調べると、この他にも多くの小さな機能や機能拡張があることがわかります。以前はオーナー (カスタム) 描画を必要としていた多くのシナリオが、現在では標準で組み込まれています。また、強化された Windows Vista コントロールをさらに容易に活用できるよう、Windows Template Library と MFC の次期バージョンには多くの改善が加えられます。詳細については、「Vista コモン コントロールのための MFC の更新」を参照してください。

ご意見やご感想は

Kenny mmwincpp@microsoft.com.

まで英語でお送りください。
Kenny Kerr は、Windows のソフトウェア開発を専門にしているソフトウェア設計者です。彼はプログラミングおよびソフトウェア設計に関して執筆を行い、開発者を指導しています。連絡先は weblogs.asp.net/kennykerr です。

Page view tracker