プロパティーシート (Property Sheet) とは、 Windows 上の各種ソフトなどでオプション設定するときに出現する複数ページからなる ダイアログでタブ (tab) をクリックすることで頁を変更することができます。 Windows では至る所でプロパティーシートに遭遇しますが、 Visual C++ でプログラムを組んでいるときには コントロールなどのプロパティーの設定で最もよくお目にかかるものです。 厳密には、プロパティーシートは複数のプロパティーページの入れ物の ことで、プロパティーページの方がダイアログです。
ウィザード (Wizard) とは Windows ソフトをインストールするときなどによく登場します。 Wizard の各ページはダイアログで、『戻る』、『次へ』のボタンがあって、 最後のページには『終了』 (or『完了』) のボタンが付いています。 Wizard も色々なページのスタイルがありますが、 ウィザードも実はプロパティーシートです。
プロパティーシートもウィザードも Visual C++ で非常に簡単に プログラムを作成することができます。全部を一度に書き上げようとすると かなり混乱することになりますが、 基本は簡単ですから少しづつ作成すれば驚くほど簡単に作成することが できます。
次の条件でプロジェクトワークスペースを作成し、 「MFC アプリケーション ウィザード」では「アプリケーションの種類」 として「シングル ドキュメント」を選びます。(従って単に View クラスのプログラムです。)
| プロジェクトの種類 | Visual C++ プロジェクト |
|---|---|
| テンプレート | MFC アプリケーション |
| プロジェクト名 | PropSample |
「リーソース ビュー」の 「Menu」/「IDR_MAINFRAME」 を選び、メニューの項目を作成します。 どこに作っても構わないのですが、ここでは「ファイル」メニューの中に「プロパティー シート」という項目を作成します。 このメニュー項目を選択すると、プロパティーシートが表示されるようにすることが以下の目的です。
次に、プロパティーシートに挿入するプロパティーページを作成します。 全部で 3 ページ作成します。これはあとでプロパティーシートをウィザードに 変更したときに 3 ページないとウィザードらしくないからで、 面倒くさければ 2 ページ程度でも構いません。
リーソースに新規のダイアログ (IDD_DIALOG1) を挿入します。「OK」, 「キャンセル」 のボタンが付いていますが、これは必要ありませんから、マウスで指定して、 「DEL」キーで消去します。 このダイアログのプロパティーを次のように変更します。
| Caption | 1頁 |
|---|---|
| Style | 子 |
| Border | 細枠 |
| System Menu | False |
Caption の文字はプロパティー頁のタブ (tab) (= つまみの部分) に表示される文字で、プロパティー頁はプロパティーシートの子ウィンドウとして、 実現されるため、「子」の指定をつけます。
ダイアログ全体をマウスで指定して、右クリックして表示されるメニューから「クラスの追加」を選びます。 すると「MFC クラス ウィザード」が表示されます。そこで次のように設定して、「完了」します。
| クラス名 | CPage1 |
|---|---|
| 基本クラス | CPropertyPage |
IDD_DIALOG1 が消えてしまいますから、「リソース ビュー」で「IDD_DIALOG1」をダブルクリックして、
表示し、そこに、Edit Control を 1 つ配置します
(横に少し長く作ります)。
ID は IDC_EDIT1 の
まま放置します。
このエディットボックスに変数を割り当てるため、エディットボックスを
マウスで右クリックして表示されるメニューから「変数の追加」を選びます。
すると「メンバー変数追加ウィザード」が表示されますから、次のように設定して「完了」します。
| カテゴリ | Value |
|---|---|
| 変数の種類 | CString |
| 変数名 | m_edit |
1 ページ目と全く同様に同様に 2 頁、3 頁を作ります。クラス名は CPage2, CPage3 としますが、すべてに同じようにエディットボックスを配置して、 割り当てるメンバー変数名も全く同じ 「m_edit」 とします。 ちょっと雑かもしれませんがクラスが異なりますから、同じ変数名でも一向に構いません。
以上のように作成したプロパティーページを収納する プロパティーシートを作成します。 そのために「クラス ビュー」で、プロジェクト名「PropSample」を 右クリックすると表示されるメニューから 「追加」/「クラスの追加」を選びます。 表示される「テンプレート」からMFC クラス を選ぶと「MFC クラス ウィザード」が登場します。 そこで次のように設定して「完了」します。
| クラス名 | CMySheet |
|---|---|
| 基本クラス | CPropertySheet |
CMySheet から CPage1, CPage2, CPage3 にアクセスできるようにするために ヘッダーファイル MySheet.h の先頭に次のような書き込みをします。
#pragma once
#include "Page1.h" #include "Page2.h" #include "page3.h"
// CMySheet class CMySheet : public CPropertySheet { DECLARE_DYNAMIC(CMySheet) public: CMySheet(UINT nIDCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); CMySheet(LPCTSTR pszCaption, CWnd* pParentWnd = NULL, UINT iSelectPage = 0); virtual ~CMySheet(); protected: DECLARE_MESSAGE_MAP()
public:
CPage1 m_page1;
CPage2 m_page2;
CPage3 m_page3;
};
更に、プロパティーページをプロパティーシートに付け加えるために CMySheet の 2 つのコンストラクターに次のように書き込みをします。
CMySheet::CMySheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
}
CMySheet::CMySheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
}
PropSampleView.h でプロパティーページの各頁 (1 、2、3 頁) の
エディットボックスに表示される文字を保存する変数を宣言します。
// 属性 public: CPropSampleDoc* GetDocument() const;
CString m_edit_p1;
CString m_edit_p2;
CString m_edit_p3;
この変数をコンストラクターで初期化します。
CPropSampleView::CPropSampleView()
{
// TODO: 構築コードをここに追加します。
m_edit_p1 = "1頁の文字列";
m_edit_p2 = "2頁の文字列";
m_edit_p3 = "3頁の文字列";
}
更に文字列をグラフィック画面に表示するために PropSampleView.cpp の OnDraw 関数に 次の書き込みをします。(引数の pDC がコメントアウトされていますから、忘れずに復活させておきます。)
void CPropSampleView::OnDraw(CDC* pDC) { CPropSampleDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); if (!pDoc) return; // TODO: この場所にネイティブ データ用の描画コードを追加します。
pDC->TextOut(20, 40, m_edit_p1);
pDC->TextOut(20, 70, m_edit_p2);
pDC->TextOut(20, 100, m_edit_p3);
}
まだプロパティーシートを使えるようにはなっていませんが、 タイプミスがあるかどうかのチェックのためにビルドしてみましょう。
さて View クラスから CMySheet クラスを使用するために PropSampleView.cpp の先頭で ヘッダーファイルをインクルードします。
// PropSampleView.cpp : CPropSampleView クラスの実装 // #include "stdafx.h" #include "PropSample.h" #include "PropSampleDoc.h" #include "PropSampleView.h"
#include "MySheet.h"
次にメニューからプロパティーシート関数を実行するために、「リソース ビュー」 の「Menu」/「IDR_MAINFRAME」をダブルクリックして、表示し、メニューの項目に 「プロパティーシート」を作っていましたから、その上で右クリックして表示されるメニューから 「イベント ハンドラーの追加」を選びます。「イベント ハンドラ ウィザード」が 表示されます。そこで次のように設定して「追加して編集」を選びます。
| メッセージの種類 | COMMAND |
|---|---|
| クラスの一覧 | CPropSampleView |
「イベント ハンドラ」が作成されますから、次のように書き込みをします。
void CPropSampleView::On32771() { // TODO : ここにコマンド ハンドラ コードを追加します。
CMySheet mysheet("プロパティーシート", this, 0);
mysheet.m_page1.m_edit = m_edit_p1;
mysheet.m_page2.m_edit = m_edit_p2;
mysheet.m_page3.m_edit = m_edit_p3;
int result = mysheet.DoModal();
if(result == IDOK)
{
m_edit_p1 = mysheet.m_page1.m_edit;
m_edit_p2 = mysheet.m_page2.m_edit;
m_edit_p3 = mysheet.m_page3.m_edit;
Invalidate();
}
}
以上で、プログラムが出来ました。なお CPropertySheet は次の構文で使用します。
CPropertySheet(タイトル文字列, 親ウィンドウへのポインタ, 最初に表示されるページ)
ビルドして実行します。 「ファイル」/「プロパティーシート」 から次のようなプロパティーシートが 立ち上がるはずです。
プロパティーページのエディットボックスの文字列を修正して、 OK を押すと、修正結果がグラフィック画面に反映されることを確認します。
Step 1 のようにして作ったプロパティーシートをよく見てみると、『適用』ボタンがあります。 これはコードを適当に付け加えて、プロパティーページが修正されたときに、 『適用』ボタンを押すと、 修正内容が直ちに反映できるようにプログラムを組むためにあります。
CMySheet のコンストラクターに次のように書き込むと、 『適用』ボタンが消えます。
CMySheet::CMySheet(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
m_psh.dwFlags |= PSH_NOAPPLYNOW;
}
CMySheet::CMySheet(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
AddPage(&m_page1);
AddPage(&m_page2);
AddPage(&m_page3);
m_psh.dwFlags |= PSH_NOAPPLYNOW;
}
詳しいことは Visual C++ の help で CPropertySheet を調べてください。なお
m_psh.dwFlags |= PSH_NOAPPLYNOW
は、次の意味です。(| はビット or です。知らなければ C 言語の
教科書を調べてください。)
m_psh.dwFlags = m_psh.dwFlags | PSH_NOAPPLYNOW
『適用』ボタンは、そもそもプロパティーページが修正されたときに、 『適用』ボタンを押すと、 修正内容が直ちに反映できるようにプログラムを組むためにあります。 このようにプログラムを組むのはかなり面倒なのですが、 ともかくやってみることにしましょう。
各プロパティーページで修正があったときに,
発送されるメッセージは EN_CHANGE です。これを受け取ることが
できるのは各プロパティーページです。そのため、処理するための
ルーチンは各ページごとに行わないといけません。そうすると、Step 1 での
プログラムそのままでは部分的に不都合となります。各ページごとの
文字列をプロパティーシートクラス CMySheet のメンバー変数として定めているためです。
そこで、この変数を大局変数にして、各プロパティーページから
アクセスが可能な状況にして、処理手順を各ページごとに記述することにします。
そこで各頁の文字列を保存するための変数を CPropSampleView のメンバー変数から削除します :
// 属性 public: CPropSampleDoc* GetDocument() const; CString m_edit_p1; <---- 消去する CString m_edit_p2; <---- 消去する CString m_edit_p3; <---- 消去する
このあとで、これらの変数を PropSampleView.cpp のヘッダーで 次のように大局変数として宣言します。
#include "stdafx.h" #include "PropSample.h" #include "PropSampleDoc.h" #include "PropSampleView.h" #include "MySheet.h" #include ".\propsampleview.h" #ifdef _DEBUG #define new DEBUG_NEW #endif
CString m_edit_p1; CString m_edit_p2; CString m_edit_p3;
更に、これらの変数を各頁ごとに使用するためには、 各ページのクラスのヘッダーで、 変数が外部宣言されていること (declared externally) を明示します。 Page1.cpp の場合であれば次のようにします。 Page2.cpp, Page3.cpp に関しても同様の修正を施します。
// Page1.cpp : 実装ファイル // #include "stdafx.h" #include "PropSample.h" #include "Page1.h"
extern CString m_edit_p1;
更に、「Edit Control」が修正されたときに処理をする 「イベント ハンドラ」を作る必要があります。これも各頁ごとに処理する必要が あります。1 頁目の場合であれば、「リソース ビュー」から「IDD_DIALOG1」をダブルクリックして 1頁目のダイアログを表示して、更に「Edit Control」の上で右クリックして表示されるメニューから 「イベント ハンドラの追加」を選びます。ここで次のように設定されていることを確認して 「追加して編集」を選びます。
| メッセージの種類 | EN_CHANGE |
|---|---|
| クラス名 | CPage1 |
作成された「イベント ハンドラ」の「フレーム ワーク」に次のような 書き込みをします。
void CPage1::OnEnChangeEdit1()
{
// TODO : これが RICHEDIT コントロールの場合、....
.........
.........
// TODO : ここにコントロール通知ハンドラ コードを追加してください。
SetModified(TRUE);
}
以上の書き込みも 2 ページ目、3 ページ目にも同様に行います。 これで一応『適用』(Apply) ボタンが有効になりますが、『適用』ボタンが押されたときの 処理が必要となります。 そこで「クラス ビュー」で「CPage1」を指定して、「プロパティー」の 「オーバライド」ボタンをクリックし、「OnApply」を探します。
「OnApply」をクリックすると、右端に下向きの矢印が表示され、これをクリックすると「<追加>OnApply」 が表示されます。そのままこれを選択すると、OnApply() 関数のフレーム ワークが追加されます。
これに書き込みをする必要があります。これも各頁ごとにする必要がありますが、 1 ページ目の場合であれば次のように書き込みをします。
BOOL CPage1::OnApply()
{
// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
m_edit_p1 = m_edit;
CWnd* parent1 = (CWnd*)GetParent();
CWnd* parent2 = (CWnd*)(parent1->GetParent());
parent2->Invalidate();
SetModified(FALSE);
return CPropertyPage::OnApply();
}
以上で完成です。ビルドして実行してみることにします。
プロパティーシートをウィザードに変更するためには、作成するプロパティーシートの メンバー関数 SetWizardMode() を呼び出すだけで十分なのですが、 これだけだとすべてのページのボタンが
となってしまいます。最初のページには『戻る』は必要ありませんし、 また最後のページには『完了』がないといけません。そこで、各プロパティーページに 関数を追加する必要があります。 そこで「クラス ビュー」で「CPage1」を指定して、「プロパティー」の 「オーバライド」ボタンをクリックし、「OnSetActive」を探します。
「OnSetActive」をクリックすると、右端に下向きの矢印が表示され、これをクリックすると「<追加>OnSetActive」 が表示されます。そのままこれを選択すると、OnSetActive() 関数のフレーム ワークが追加されます。 CPage2, CPage3 に関しても同様に OnSetActive 関数を追加します。
OnSetActive 関数はプロパティーページがアクティブになったときただ一度実行される関数で、 ここでプロパティーページの体裁を指定します。 ボタンは実はプロパティーシートのメンバーです。そのため、 ボタンの体裁を変えるためには、 各々の OnSetActive 関数に次のように書き込みをしなければなりません。
BOOL CPage1::OnSetActive()
{
// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
CPropertySheet* parent = (CPropertySheet*)GetParent();
parent->SetWizardButtons(PSWIZB_NEXT);
return CPropertyPage::OnSetActive();
}
BOOL CPage1::OnSetActive()
{
// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
CPropertySheet* parent = (CPropertySheet*)GetParent();
parent->SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT);
return CPropertyPage::OnSetActive();
}
BOOL CPage1::OnSetActive()
{
// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
CPropertySheet* parent = (CPropertySheet*)GetParent();
parent->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);
return CPropertyPage::OnSetActive();
}
更に Step 1 で 作成したプログラムの メニュー項目のイベント ハンドラへの書き込みを修正します。
void CPropSampleView::On32771()
{
// TODO: この位置にコマンド ハンドラ用のコードを追加してください
CMySheet mysheet("プロパティーシート", this, 0);
mysheet.m_page1.m_edit = m_edit_p1;
mysheet.m_page2.m_edit = m_edit_p2;
mysheet.m_page3.m_edit = m_edit_p3;
mysheet.SetWizardMode();
int result = mysheet.DoModal();
if(result == ID_WIZFINISH)
{
m_edit_p1 = mysheet.m_page1.m_edit;
m_edit_p2 = mysheet.m_page2.m_edit;
m_edit_p3 = mysheet.m_page3.m_edit;
Invalidate();
}
}
Windows ソフトのインストーラーは一般にウィザード形式になっています。 インストーラーは必ずと言っていよいくらいにユーザー名や所属を要求してきます。 しかし面倒だからと言って空白のまま処理しようとしても先に進むことが出来ません。 こんなことも簡単にプログラムを組むことができます。 前項のウィザードプログラムの 2 ページ目の文字列が空白である場合には 先に進むことができないようにしてみましょう。
「クラス ビュー」で「CPage2」を指定して、「プロパティ」の 「オーバライド」ボタンを押して 「OnWizardNext」を探します。
「OnWizardNext」をクリックすると、右端に下向きの矢印が現れます。これをクリックすると 「<追加>OnWizardNext」と書かれた項目が表示されます。これを選択すると OnWizardNext() のフレーム ワークが作成されます。ここに以下のような書き込みをします。
LRESULT CPage2::OnWizardNext()
{
// TODO : ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
CEdit* p = (CEdit*) GetDlgItem(IDC_EDIT1);
if( p->LineLength() == 0)
{
AfxMessageBox("文字列を入力してください");
return -1;
}
return CPropertyPage::OnWizardNext();
}
このようにすると 2 ページ目の文字列が空白であれば 3 ページ目に進めなくなります。 ビルドして、確かめてみましょう。