メニューの使用法

前節で作成したプログラムspec25.zipを用います。 spec25.cppのなかのOnStart()関数の中身を次のようにしてください。

void CMainFrame::OnStart() 
{
	CClientDC dc(this);

	//白のペンを定義します。RGB(255,255,255)は白を意味します。
	CPen pP(PS_SOLID,1,RGB(255,255,255));
	//この白のペンを選択します。oldpPには元のペン(黒)の情報が入ります。
	//黒では見えにくいので白にしました。
	CPen *oldpP=dc.SelectObject(&pP);

	dc.MoveTo(100,100);
	dc.LineTo(200,200);

	//元のペンに戻します。
	dc.SelectObject(oldpP);
}
ここで、プログラムを実行して、メニューの測定開始を選択してください。白い線が引かれるのが分かります。

次に、このウィンドウの上に何か別のウィンドウを重ねて、さらに重ねたウィンドウをどけてください。すると、白い線が消えていますね。何故でしょう。そうです、再描画されるのはOnPaint()関数の中身だけでしたね。したがって、この問題を解決するためには、OnPaint()の中にも上記と同じものを書く必要があります。つまり、

void CMainFrame::OnPaint() 
{
	CPaintDC dc(this); 


	CPen pP(PS_SOLID,1,RGB(255,255,255));
	CPen *oldpP=dc.SelectObject(&pP);

	dc.MoveTo(100,100);
	dc.LineTo(200,200);

	dc.SelectObject(oldpP);

	dc.TextOut(0, 0, "Hello World!");
}
です。ただ、ここでプログラムを実行すると、ウインドウには初めから線が書かれてしまいます。そこで、メニューの測定開始を選択する前と後で、値を異にする変数が必要となります。たとえば、測定したデータの数をint datanumに入れることにして、プログラムの最初で、それを0に初期化して、メニューの測定開始を選択したときに、1に変える。そして、OnPaint() では、datanumが0の時は描画しないことにすれば問題は解決するはずです。具体的には、spec25.hを開いて、クラスの定義であるclass CMainFrame : public CFrameWndの中の最後のprivate:の下に

private:
	CStatusBar status;
	int datanum;

のようにint型の変数datanumを追加します。この変数を使って、spec25.cppのなかのOnStart()関数とOnPaint()関数を次のように手直しします。

void CMainFrame::OnStart() 
{
	CClientDC dc(this);

	datanum++;	//datanumに1を加えて行きます。

	//白のペンを定義します。RGB(255,255,255)は白を意味します。
	CPen pP(PS_SOLID,1,RGB(255,255,255));
	//この白のペンを選択します。oldpPには元のペン(黒)の情報が入ります。
	//黒では見えにくいので白にしました。
	CPen *oldpP=dc.SelectObject(&pP);

	dc.MoveTo(100,100);
	dc.LineTo(200,200);

	//元のペンに戻します。
	dc.SelectObject(oldpP);
}

void CMainFrame::OnPaint() 
{
	CPaintDC dc(this); 

	if(datanum){	//datanumがゼロでないならば以下の{から}までを実行する。
		CPen pP(PS_SOLID,1,RGB(255,255,255));
		CPen *oldpP=dc.SelectObject(&pP);
		dc.MoveTo(100,100);
		dc.LineTo(200,200);
		dc.SelectObject(oldpP);
	}
	dc.TextOut(0, 0, "Hello World!");
}
さらに、datanumを初期化するため、コンストラクタ(注)と呼ばれる初期化関数CMainFrame::CMainFrame()の何処かにdatanum=0;を加えてください。たとえば
CMainFrame::CMainFrame()
{
(上略)
	status.SetPaneText(0,"測定状況");

	datanum=0;
}
のように。 以上の操作で、プログラムが完成しましたので、実行して再描画の効果を確かめてください。なお、できあがったプログラムはspec25new.zipです。

注: コンストラクタはCMainFrame::CMainFrame()のように::の前後に同じ名前(今の場合はCMainFrame)が付きます。クラスCMainFrameが実体化するときに、最初に実行されるので初期化関数として使われます。なお、実体化はCspec25App::InitInstance()関数のm_pMainWnd = new CMainFrame();なる行でなされています。ついでながらCspec25Appクラスはどこで実体化しているかというと、Cspec25App theApp;なる行でです。

問題:再描画のところで保留してあった、「再描画する必要があるときに、"マウスが押下されました"という文字列も再び表示したい場合、どうしましょうか」という問題ですが、もう解決できますね。

留意点:

1.測定操作は上の例ではOnStart()関数のなかで行うことを想定しています。測定をOnPaint()の中で行うと、いつ起こるか分からない再描画の時にしか測定することができませんので、別な関数の中(今の場合OnStart関数)で行います。

2.測定を目的とする関数(今の場合OnStar関数)のなかで描画を行わないで、描画はすべてOnPaint() で行えば良いのではないかとの疑問をもたれる方もおられると思いますが、これはあまりお勧めできません。OnPaint() を駆動するためには、再描画しなければいけません。強制的にプロクラムで再描画する方法があります。しかし、再描画には時間がかかるので画面がちらつきます。測定が1秒ごととすると、1秒ごとに再描画となり、1秒ごとに画面がちらつくので大変気になります。

普通、次のような方法をとります。たとえば100点のデータを得て、それらを直線で結んで来たとします。次に101点目のデータを得て、100点目のデータと直線で結びたいとします。OnStart関数のなかで行うことは、(100点目までのデータはウィンドウにすでに描かれているので)100点目のデータと101点目のデータを直線で結ぶことだけです(再描画はしません)。一方、OnPaint()関数中では、再描画することが目的ですから、1番目のデータから101番目のデータをすべて直線で結ぶようにします。この場合、本当に再描画が必要なときにしかOnPaint()関数が呼ばれないので、ちらつきは全く気になりません。

3.OnPaint()で描画しようとするデータは、すべて記憶しておく必要があります。たとえば、温度の時間変化を1秒ごとにとって、最大1000点を直線で結んで表示したい場合は、1000点分のデータを記憶しておく必要があります。記憶していないと再描画ができません。つまり、これらのデータをたとえばprivate: double data[1000]に記憶させておくということです。

後は、 メニューからダイアログを出す、ツールバーにダイアログを張り付けるなどの操作が必要となると思います。Webで検索するとサンプルが見つかりますので、必要な時に参照すれば良いと思います。また、測定に関してはインターフェースの章を参照ください。


戻る