プロパティシート・ダイアログを作ってみる

SPECIAL

設定用のダイアログを作っていたら、コントロールの多さに挫折。

ということで、プロパティシートに挑戦です。


プロパティシート・ダイアログ

プロパティシート・ダイアログとは、プロパティーシートの載ったダイアログボックス/そのまんま。

Windows で何かの設定をするとき、たとえば画面のプロパティの設定で、「テーマ」 とか 「デザイン」 とか、設定をグループごとに分類することで、ユーザが直感的に操作できるのが魅力でしょうか。

 

また、プログラマにも利点があります。

「テーマ」 とか 「デザイン」 とか、グループのそれぞれの設定内容がひとつの プロパティ・ページ としてまとめられます。そしてそれらを配置するのが プロパティ・シート。

で、それぞれのプロパティページを、Visual C++ では独立したページとして作成することができるのです。

その場合、それぞれのページを少ないコントロール数で作成することができるので、設定項目が多い場合でも見た目がすっきりするし、第一、ダイアログの狭い領域に四苦八苦しないですみます。

 

というわけで、魅力いっぱいのプロパティシートダイアログを試しに作ってみることにしました。

 

プロパティシート・ダイアログの作成実験

開発環境

まずは、どんな開発環境にてプログラミングをしているかというお話です。

といってもとりあえず細かい処理速度とかは必要ないと思いますので、必要そうな部分だけを大雑把に書いておくことにします。

 

OS Windows XP Professional
CPU PentiumIII Dual Processors
開発環境 Microsoft Visual Studio 6.0 Enterprise Edition
 + Service Pack 5

ざっと書きすぎですけどこんな感じです。

 

プロジェクトの作成

とりあえず MFC がサポートされていることと、動かして実験できるという理由で、ダイアログベースの MFC AppWizard (.exe) を使うことにしました。

 

プロジェクトの種類 MFC AppWizard (.exe)
プロジェクト名 PropTest
アプリケーションの種類 ダイアログベース

上のような感じでプロジェクトを作成しておくことにします。これ以外の設定項目は特にいじりませんでした。

 

プロパティページの作成

とりあえず、プロパティページを2つほど作成してみることにします。

非常に無意味な設計になってしまいますが、それぞれ名前と年齢を入力するためだけのページにしてみることにします。

 

名前入力用のプロパティページ作成 (リソース)

まず Dialog リソースをひとつ、普通に追加します。そして出来上がった IDD_DIALOG1 の設定を次のように調整します。

 

一般:キャプション 氏名
スタイル:スタイル チャイルド
スタイル:境界線 細枠
スタイル:タイトルバー ON
スタイル:システムメニュー OFF
その他のスタイル:無効 ON

そして、名前入力用のエディットボックスを、IDC_EDIT1 として貼り付けておきます。ついでに、OK ボタンとキャンセルボタンを削除しておきます。

 

または、ダイアログリソースを作成する際に、PROPPAGE_LARGE とか、それを選ぶことで、上記のようにオプションを変更済みのダイアログを作成することができるようです。

 

年齢入力用のプロパティページ作成 (リソース)

これも上で書いた名前入力用のダイアログボックスと基本的に一緒です。

キャプションを 「年齢」 に変えて、エディットボックス IDC_EDIT1 を貼り付けたダイアログリソースを新しく用意します。

 

それぞれを CPropertyPage クラスから派生させる

リソースにクラスを割り当てます。

名前入力用のダイアログリソースを選択して、「表示」 メニューからクラスウィザードを起動させます。この操作を始めて行ったときに、どのようにクラスを追加するかを尋ねられますので、「新規クラスの作成」 を選択します。

するとクラスの詳細を設定するダイアログが現れますので、とりあえず、クラス名は CPage1 とでもしておきましょう。そして今回は、基本クラスも変更します。

基本クラスには CPropertyPage を指定します。

 

同様にして、IDD_DIALOG2 をつかって、CPage2 を作成しておきます。

 

プロパティシート・ダイアログの呼び出し

さて、続いてプロパティシートを作成します。これは特にリソースを作って組み立てるという感じではないようです。

まず、今回はダイアログベースのアプリケーションなので、はじめから作成されていたダイアログリソース (IDD_PROPTEST_DIALOG) に、ボタンコントロール IDC_BUTTON1 をひとつ貼り付けましょう。

このボタンから、プロパティシートを呼び出すことにします。

 

貼り付けたボタンをダブルクリックして、OnButton1 というメソッドを作成します。

 

そして実際にプロパティページを表示させる場合、次のようなコードを追加することになります。Page1.h と Page2.h のヘッダの読み込み位置は、適当なところで行いましょう。

#include "Page1.h"

#include "Page2.h"

 

void CPropTestDlg::OnButton1()

{

// TODO: この位置にコントロール通知ハンドラ用のコードを追加してください

// まずはそれぞれのプロパティページを生成します。

CPage1 PropPage1;

CPage2 PropPage2;

 

// プロパティシートを作成し、プロパティページを配置します。

CPropertySheet PropSheet(_T("作成されたプロパティページ");

 

PropSheet.AddPage(&PropPage1);

PropSheet.AddPage(&PropPage2);

 

// プロパティシートダイアログを表示します。

if (PropSheet.DoModal() == IDOK)

{

// ここで正常終了時の処理を行います。

}

else

{

// ここでキャンセル時の処理を行います。

}

}

CPropertySheet のコンストラクタで、プロパティシートダイアログのキャプションを指定しています。ほかにも SetCaption() メソッドでキャプションをつけることもできるようです。

その後、Page1 と Page2 を追加して、ダイアログを表示する、といった流れになっています。

 

これを実行したとき、下のようなプロパティシートダイアログが表示できます。

 

ただ 「適用」 はまだしも、「ヘルプ」 が不要な人もいるはず…。「適用」 また 「ヘルプ」 を表示させたくない場合は次のようなコードを追加して、表示を調整することができます。

// 「ヘルプ」 を表示させたくない場合

PropSheet.m_psh.dwFlags &= ~PSH_HASHELP;
PropPage1.m_psp.dwFlags &= ~PSP_HASHELP;
PropPage2.m_psp.dwFlags &= ~PSP_HASHELP;
 

// 「適用」 を表示させたくない場合

PropSheet.m_psh.dwFlags |= PSH_NOAPPLYNOW;

なお、「ヘルプ」 の調整で気をつけたほうがいいのは、プロパティシート自体か、使用しているプロパティページのどれかで 「ヘルプ」 を使用する設定になっていると、使用不可の状態で表示されます。逆にすべてで使用しない設定になっていると表示されなくなります。

使用不可の状態で画面に表示させたい場合は、プロパティシートだけ 「ヘルプ」 を使用する設定にすればよさそうです。

あと、プロパティシートでヘルプを調整する変数は PSH_HASHELP ですが、プロパティページのほうは PSP_HASHELP と、1文字違うので気をつけましょう/汗

 

ダイアログのタイトルバーにアイコンをつけたいばあには、次のような感じにします。

// タイトルバーにアイコンを表示させる

PropSheet.m_psh.hIcon = m_hIcon;

PropSheet.m_psh.dwFlags |= PSH_USEHICON;

指定するアイコンは、HICON である必要があるので、LoadIcon() API などを利用して取得します。上の例では、ダイアログにあるものを直接使ってみてます。

 

あとは普通のダイアログボックスプログラミングと同様、メンバ変数を作ったりして、「OK」 が押された後、呼び出し元で適切な処理をすれば OK です。

ただし、メンバ変数としてコントロールを登録している場合、そのコントロールを配置しているシートがめくられないと、そのメンバ変数に対して実際のコントロールが割り当てられないという状況になるそうです。

 

「適用」 ボタンを利用する

「適用」 ボタンは、ディフォルトでは使用不可の状態になっています。

これを使用できるようにするためには、プロパティページ内で、SetModified メソッドを呼び出します。これの引数を TRUE とすると 「適用」 ボタンは有効になり、逆に FALSE とすれば 「適用」 ボタンは無効になります。

存在するプロパティページのどれかで 「適用」 ボタンの状態が更新されると、その後、どのプロパティページへ移動しても、その状態が受け継がれます。

 

そして 「適用」 が押された場合、通常は OnOK() メソッドが呼び出されます。これは ClassWizard を使ってオーバーライドすることができます。

この OnOK() は、プロパティページに存在しています。では 「適用」 ボタンを押したときに、どの OnOK が呼び出されるのでしょう、ということで調べてみました。

すると、めくられたすべてのページのが呼び出されるようです。SetModified() メソッドが呼び出されているか否かにかかわらず、表示されたものはすべてです。ただし、表示されなかったページに関しては呼び出されませんでした。

なので、いつどこで適用されても、すべての OnOK が呼ばれるので安心です。

ただ、無駄をなくすためには m_Modified みたいなメンバ変数をそれぞれのページに用意して、SetModified といっしょにこの変数も設定して、OnOK の中で m_Modified がセットされていれば更新、見たいな処理をするのがよさそうです。

 

ということは、これを利用すれば、上記でいったようなコントロールの構築問題を回避できそうですね。

各プロパティページに m_config みたいなメンバー変数を持たせて、それを設定を記録する構造体とする。その構造体は親であるプロパティシート内で構築されて、そのポインタを渡す。で、各プロパティページは、OnOK の中で、m_config に、更新されたデータを書き込む。

こういう感じにすれば、円滑にプロパティシートが機能するのではないでしょうか。

 

実験終了

かなりはっきりと実現性が見えたところで、今回の実験はおしまいです。これでようやく URLParamFilter のコントロールパネルが完成しそうですね。