ページ 11

MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月20日(金) 14:09
by かなたん
MFCでユーザーが好きに絵を描いて、特定のボタンが押されたらその描いてもらった絵の上を線が移動するというのをやりたいんです。
でもボタンを押す前の画面の記憶方法が分からず、移動する線が足跡を残しながら移動してしまう状態です。
CreateCompatibleBitmapやSaveDCなどを試してみましたが、それではだめなようで。

もう少し具体的なやりたいこと
LButtonDownやMouseMoveでキャンバスの範囲(画面左側)内で自由に絵を描き(描かなくても可)、画面右下にあるボタン(のために表示した画像)をクリックすると、そのキャンバスの上を縦に長い線が左から右へ移動させたい。
そのために、ボタンを押したときの画面を記憶して、その画面と縦長の線とを合成したものを画面へ出力したい。
クリップボードへコピー&画面へ張り付けまたは、BitMap画像等で保存&画面へ読み込みなどができるならしたい。
今回やりたいこと的には前者でいいが、描いたものを画像ファイルに保存したいという気持ちもあるので、後者もやってみたい。

描いているときの動きを逐一記憶して再現するしかないのでしょうか?
そもそも、WIndowsのペイントなどはどのようにして保存等しているのでしょうか?
MFCでこのようなことをするのは難しいでしょうか?

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月21日(土) 00:18
by softya(ソフト屋)
直接画面に絵を書かずに、CDCクラスでCreateCompativleDCとCreateCompatibleBitmapで作ったメモリDC側に描画してはどうでしょうか?
これを仮にCDC m_MemDC;とします。
これをOnPaint()などで描画する時にBitBltでm_MemDCから描画DCに表示した後で、問題の線を描画DCに書けば良いと思います。
これなら書き換えられているのは描画DC側なのでm_MemDCの画像側には影響は受けません。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 00:01
by かなたん
softya(ソフト屋) さんが書きました:直接画面に絵を書かずに、CDCクラスでCreateCompativleDCとCreateCompatibleBitmapで作ったメモリDC側に描画してはどうでしょうか?
これを仮にCDC m_MemDC;とします。
これをOnPaint()などで描画する時にBitBltでm_MemDCから描画DCに表示した後で、問題の線を描画DCに書けば良いと思います。
これなら書き換えられているのは描画DC側なのでm_MemDCの画像側には影響は受けません。
最初はこんな風にやっていました。

コード:

//グローバル変数
COLORREF color[9]; //描画で使用できる色を記憶
int set_color=0; //何番目の色を使うか (初期値:0番目)
CRect canvas(0,0,500,500); //描画できる範囲の指定 ((0,0)~(500,500))
CBitmap canvas_Bit; //描画の時に使いたいビットマップ
bool paint=false; //描画可能かどうか (初期値:不可)
CPoint pointed; //移動前の座標を記憶
int timer; //SetTimerのとき戻り値を受け取ってみる (実は使い道をわかっていない)
int x,y; //必要に応じて線の座標等を記憶
//省略 (その他グローバル変数やOnDrawなどなど)
//ボタンの描画等はOnDrawでしていますが、描いてもらうとき等は一切OnDrawは呼び出したりしません。(Invalidateを使用していないので。)
void CView::OnIntialUpdate()
{
	CView::OnInitialUpdate();

	// TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
	//省略 (色の設定など)
	CCreateDC canvasDC(this);
	canvas_Bit.CreateCompatibleBitmap(&canvasDC,500,500); //とりあえず、ビットマップをここで作成してみる
}
//省略 (OnLButtonDown)
void CView::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnMouseMove(nFlags, point);
	CClientDC canvasDC(this);
	if(paint==true&&PtInRect(canvas,point)==1){ //描画可能で、描画範囲内なら
		CPen pen(PS_SOLID,5,color[set_color]); //今の色のペンを作成
		canvasDC.SelectObject(&pen); //選択
		canvasDC.SelectObject(&canvas_Bit); //描画のために用意したビットマップを選択
		canvasDC.MoveTo(pointed); //直前の座標から
		canvasDC.LineTo(point); //今の座標まで線を引く
		pointed=point; //今の座標は次には過去のもの
	}
	if(PtInRect(canvas,point)!=1){ //範囲外なら
		paint=false; //描画不可にする
	}
}
void C落書きMusicView::OnTimer(UINT_PTR nIDEvent) //Playボタンが押されたら、SetTimer(1,100,NULL)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnTimer(nIDEvent);
	CClientDC canvasDC(this); //ビットマップ読み込み用のつもり
	canvasDC.SelectObject(&canvas_Bit); //ビットマップを読み込んでるつもり
	CClientDC lineDC(this); //ここでビットマップと線を合成させる予定
	lineDC.BitBlt(0,0,500,500,&canvasDC,0,0,SRCCOPY); //ビットマップをコピーして
	CPen line(PS_SOLID,5,RGB(0,0,0));
	lineDC.SelectObject(line);
	lineDC.MoveTo(x,0); //その上から線を描く
	lineDC.LineTo(x,500);
	//省略 (GetPixelによる指定の座標の色を取得など)
}
しかし、このやりかたでは質問のように移動する線が足跡を残しながら移動してしまう状態に。
そこで、softya(ソフト屋)さんの回答を読んでからこのように改造してみることに。

コード:

//グローバル変数
COLORREF color[9]; //描画で使用できる色を記憶
int set_color=0; //何番目の色を使うか (初期値:0番目)
CRect canvas(0,0,500,500); //描画できる範囲の指定 ((0,0)~(500,500))
CBitmap canvas_Bit; //描画の時に使いたいビットマップ
bool paint=false; //描画可能かどうか (初期値:不可)
CPoint pointed; //移動前の座標を記憶
int timer; //SetTimerのとき戻り値を受け取ってみる (実は使い道をわかっていない)
int x,y; //必要に応じて線の座標等を記憶
bool set=false; //ビットマップを作成済みかどうか (初期値:作成済みではない)
bool playing=false; //再生中かどうか (初期値:再生中ではない)
//省略 (その他グローバル変数などなど)
void CView::OnDraw(CDC* pDC)
{
	C落書きMusicDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: この場所にネイティブ データ用の描画コードを追加します。
	//省略 (ボタンの描画など)
	CDC canvasDC; //ビットマップを読み込むためのCDCを作成
	canvasDC.CreateCompatibleDC(pDC);
	if(set==false){ //ビットマップを作成済みではないなら
		canvas_Bit.CreateCompatibleBitmap(pDC,500,500); //ビットマップを作成し
		CPen white(PS_SOLID,1,RGB(255,255,255));
		canvasDC.SelectObject(&white);
		canvasDC.SelectObject(&canvas_Bit);
		canvasDC.Rectangle(canvas); //その範囲を白く塗りつぶす
		set=true;
	}
	if(playing==true){ //再生中なら
		canvasDC.SelectObject(&canvas_Bit); //読み込んだつもりのビットマップを
		pDC->BitBlt(0,0,500,500,&canvasDC,0,0,SRCCOPY); //画面へコピーし
		CPen line(PS_SOLID,5,RGB(0,0,0)); 
		pDC->SelectObject(line);
		pDC->MoveTo(x,0); //その上から線を引く
		pDC->LineTo(x,500);
	}
}
//省略 (OnMouseMoveなどなど)
void CView::OnTimer(UINT_PTR nIDEvent) //Playボタンが押されたら、SetTimer(1,100,NULL)とplaying=true
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnTimer(nIDEvent);
	Invalidate(); //OnDrawでの線描画の指示
	//省略 (GetPixelによる指定の座標の色を取得など)
}
すると、移動する線が足跡を残しながら移動してしまう状態はなくなったものの、ボタン押す前に描いていたものもなくなってしまっていました。
デバッグをしてみたところ、Invalidate();のところで止めるとまだ絵は残っていて、OnTimerの最後でも残っていて、その次OnDrawの再生中かどうかのif文の先頭では消えてしまっていました。
実は、私の環境が原因なのか他のプログラムでもOnDrawの先頭で止めてみると、画面が真っ白になっています。
それが作成したビットマップに影響しているのかどうか―
あとはどのようにすればよいでしょうか?

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 00:30
by softya(ソフト屋)
なにかC++とC言語を混ぜたような書き方ですね。
グローバル変数を使わず出来ればC++の書き方を勉強されることをおすすめします。

あと肝心な部分がないのですが、canvas_Bitに描画してるんですよね?
それとcanvas_Bitを画面に描画するのはplayingがtrueの時だけですか?

[補足]
出来れば動く簡単な再現サンプルみたいなものを作ってViewのcpp/hを含めて載せてもらえると助かります。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 09:34
by かなたん
softya(ソフト屋) さんが書きました:なにかC++とC言語を混ぜたような書き方ですね。
グローバル変数を使わず出来ればC++の書き方を勉強されることをおすすめします。
C++の書き方―グローバル変数ではなく、クラスのメンバ変数にするってことですよね?
C++でそういうことをしたことはあるのですが、MFCではメンバ変数を作るということを習ってなくて、授業で先生もグローバル変数を使っていました。
グローバル変数だとそのcpp内のどこでも使えて、メンバ変数だとそのクラスでしか使えないんですよね?
グローバル変すよりもメンバ変数でって言うのは、範囲を制限した変数も使えるようになった方がいいとかってことですか?
softya(ソフト屋) さんが書きました:あと肝心な部分がないのですが、canvas_Bitに描画してるんですよね?
それとcanvas_Bitを画面に描画するのはplayingがtrueの時だけですか?

[補足]
出来れば動く簡単な再現サンプルみたいなものを作ってViewのcpp/hを含めて載せてもらえると助かります。
わかりました。
今度は省略なしのソース(Viewのcpp/h両方)を載せます。
(自分で記述したコメントはcppのみ(デフォルトで記述されるものは除く))
落書きMusic
画面に絵を描いてもらい、再生ボタンを押されたらその絵の上を黒い線がスキャンするように移動しながら指定の座標の色を音に変えて演奏。
(音を鳴らす部分はまだ実装できていません。)
(画像を使っている部分は、図形を描くように変更)

コード:

// 落書きMusicView.cpp : C落書きMusicView クラスの実装
//

#include "stdafx.h"
#include "落書きMusic.h"

#include "落書きMusicDoc.h"
#include "落書きMusicView.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif

CRect button[12]; //ボタンの位置を記憶
COLORREF color[9]; //描画で使用できる色を記憶
int set_color=0; //何番目の色を使用するのか記憶
CRect canvas(0,0,500,500); //描画可能範囲を記憶
CBitmap canvas_Bit; //描画時等に使用したいビットマップ
bool paint=false; //描画可能かどうかを記憶
CPoint pointed; //移動前の座標を記憶
int timer; //なんとなくSetTimerの戻り値を受け取ってみる
int x,y; //線の座標等を記憶
bool set=false; //ビットマップを作成したかどうかを記憶
bool playing=false; //再生中かどうかを記憶

// C落書きMusicView

IMPLEMENT_DYNCREATE(C落書きMusicView, CView)

BEGIN_MESSAGE_MAP(C落書きMusicView, CView)
	// 標準印刷コマンド
	ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
	ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
	ON_WM_TIMER()
	ON_WM_LBUTTONDOWN()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_WM_KEYDOWN()
END_MESSAGE_MAP()

// C落書きMusicView コンストラクション/デストラクション

C落書きMusicView::C落書きMusicView()
{
	// TODO: 構築コードをここに追加します。

}

C落書きMusicView::~C落書きMusicView()
{
}

BOOL C落書きMusicView::PreCreateWindow(CREATESTRUCT& cs)
{
	// TODO: この位置で CREATESTRUCT cs を修正して Window クラスまたはスタイルを
	//  修正してください。

	return CView::PreCreateWindow(cs);
}

// C落書きMusicView 描画

void C落書きMusicView::OnDraw(CDC* pDC)
{
	C落書きMusicDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);
	if (!pDoc)
		return;

	// TODO: この場所にネイティブ データ用の描画コードを追加します。
	CPen edge(PS_SOLID,1,RGB(0,0,0)); //画面を分割したいので黒いペンを作成
	pDC->SelectObject(&edge);
	pDC->MoveTo(500,0); //画面(500×600)を描画可能部分とボタン配置部分とに分けるように縦線を引く
	pDC->LineTo(500,500);
	for(int n=0;n<9;n++){ //9色のボタンを描画
		CBrush pen(color[n]);
		pDC->SelectObject(pen);
		pDC->Ellipse(button[n]);
	}
	//以下3つのボタンは本来は(リソース内の)画像を使用しているが、今回は画像がなくても再現できるよう四角い図形を描くことに変更
	CBrush white(RGB(255,255,255));
	pDC->SelectObject(&white);
	pDC->Rectangle(button[9]);
	CBrush red(RGB(255,0,0));
	pDC->SelectObject(&red);
	pDC->Rectangle(button[10]);
	CBrush blue(RGB(0,0,255));
	pDC->SelectObject(&blue);
	pDC->Rectangle(button[11]);
	//変更ここまで
	CDC canvasDC; //ビットマップ読み込んだりとか用CDCを作成
	canvasDC.CreateCompatibleDC(pDC);
	if(set==false){ //ビットマップを作成していないなら
		canvas_Bit.CreateCompatibleBitmap(pDC,500,500); //ビットマップ作成
		CPen white(PS_SOLID,1,RGB(255,255,255));
		canvasDC.SelectObject(&white);
		canvasDC.SelectObject(&canvas_Bit); //一度ビットマップを読み込み
		canvasDC.Rectangle(canvas); //白く塗っておく 出ないと再生時にその部分が真っ黒に・・・;
		set=true;
	}
	if(playing==true){ //再生中なら
		canvasDC.SelectObject(&canvas_Bit); //ビットマップを読み込んで
		pDC->BitBlt(0,0,500,500,&canvasDC,0,0,SRCCOPY); //画面にコピーして
		CPen line(PS_SOLID,5,RGB(0,0,0));
		pDC->SelectObject(line);
		pDC->MoveTo(x,0); //その上から縦線を引く
		pDC->LineTo(x,500);
	}
}


// C落書きMusicView 印刷

BOOL C落書きMusicView::OnPreparePrinting(CPrintInfo* pInfo)
{
	// 既定の印刷準備
	return DoPreparePrinting(pInfo);
}

void C落書きMusicView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: 印刷前の特別な初期化処理を追加してください。
}

void C落書きMusicView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
	// TODO: 印刷後の後処理を追加してください。
}


// C落書きMusicView 診断

#ifdef _DEBUG
void C落書きMusicView::AssertValid() const
{
	CView::AssertValid();
}

void C落書きMusicView::Dump(CDumpContext& dc) const
{
	CView::Dump(dc);
}

C落書きMusicDoc* C落書きMusicView::GetDocument() const // デバッグ以外のバージョンはインラインです。
{
	ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(C落書きMusicDoc)));
	return (C落書きMusicDoc*)m_pDocument;
}
#endif //_DEBUG


// C落書きMusicView メッセージ ハンドラ

void C落書きMusicView::OnInitialUpdate()
{
	CView::OnInitialUpdate();

	// TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
	color[0]=RGB(255,0,0); //描画時に使用できる色を設定
	color[1]=RGB(255,95,0);
	color[2]=RGB(255,255,0);
	color[3]=RGB(0,255,0);
	color[4]=RGB(0,128,0);
	color[5]=RGB(0,255,255);
	color[6]=RGB(0,0,255);
	color[7]=RGB(255,0,255);
	color[8]=RGB(255,255,255);
	for(int n=0;n<12;n++){ //ボタンの座標を設定
		button[n].top=4+n*41;
		button[n].bottom=44+n*41;
		button[n].left=530;
		button[n].right=570;
	}
}

void C落書きMusicView::OnLButtonDown(UINT nFlags, CPoint point)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnLButtonDown(nFlags, point);
	CClientDC canvasDC(this); //画面書き換え用
	for(int n=0;n<9;n++){
		if(PtInRect(button[n],point)==1){ //色のボタンが押されたら
			set_color=n; //使用する色の番号を変更
		}
	}
	if(PtInRect(button[9],point)==1){ //今までの描画をすべてクリアするためのボタンが押されたら
		CPen white(PS_SOLID,1,RGB(255,255,255));
		canvasDC.SelectObject(&white);
		canvasDC.SelectObject(&canvas_Bit); //ビットマップとともに
		canvasDC.Rectangle(canvas); //描画範囲を白く塗りつぶす
	}
	if(PtInRect(button[10],point)==1){ //再生ボタンが押されたら
		x=0; //初期座標を左上に設定し
		y=0;
		playing=true; //再生中だといい
		timer=SetTimer(1,100,NULL); //タイマー作動
	}
	if(PtInRect(button[11],point)==1){ //停止ボタンが押されたら
		playing=false; //再生中でないといい
		timer=KillTimer(1); //タイマー停止
	}
	if(PtInRect(canvas,point)==1){ //描画可能範囲が押されたら
		paint=true; //描画可能だといい
		pointed=point; //今の座標は次には過去のもの
	}
}

void C落書きMusicView::OnMouseMove(UINT nFlags, CPoint point)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnMouseMove(nFlags, point);
	CClientDC canvasDC(this); //画面描画に使用
	if(paint==true&&PtInRect(canvas,point)==1){ //描画可能で範囲内なら
		CPen pen(PS_SOLID,5,color[set_color]); //今の色のペンを作成し
		canvasDC.SelectObject(&pen);
		canvasDC.SelectObject(&canvas_Bit); //ビットマップとともに
		canvasDC.MoveTo(pointed); //前の座標から今の座標まで線を引く
		canvasDC.LineTo(point);
		pointed=point; //今の座標は次には過去のもの
	}
	if(PtInRect(canvas,point)!=1){ //範囲外なら
		paint=false; //描画不可にする
	}
}

void C落書きMusicView::OnLButtonUp(UINT nFlags, CPoint point)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnLButtonUp(nFlags, point);
	paint=false; //マウスの左ボタンが離されたら描画不可にする
}

void C落書きMusicView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	CView::OnTimer(nIDEvent);
	Invalidate(); //再描画(というか線との合成)を指示
	CClientDC canvasDC(this); //ここから色を取得
	COLORREF get_color; //取得した色を記憶
	if((x%5)==0){
		for(y=0;y<500;y++){
			get_color=canvasDC.GetPixel(x,y); //指定の座標の色を取得
			if(get_color==RGB(255,0,0)){ //色によってきまった音を鳴らしたい
				;//c4
			}
			else if(get_color==RGB(255,98,0)){
				;//d4
			}
			else if(get_color==RGB(255,255,0)){
				;//e4
			}
			else if(get_color==RGB(0,255,0)){
				;//f4
			}
			else if(get_color==RGB(0,128,0)){
				;//g4
			}
			else if(get_color==RGB(0,255,255)){
				;//a4
			}
			else if(get_color==RGB(0,0,255)){
				;//b4
			}
			else if(get_color==RGB(255,0,255)){
				;//c5
			}
		}
	}
	if(x<500){ //終わりでないなら
		x++; //次へ行く
	}
	else{ //終わりなら
		x=0; //先頭へ戻る
	}
}

コード:

// 落書きMusicView.h : C落書きMusicView クラスのインターフェイス
//


#pragma once


class C落書きMusicView : public CView
{
protected: // シリアル化からのみ作成します。
	C落書きMusicView();
	DECLARE_DYNCREATE(C落書きMusicView)

// 属性
public:
	C落書きMusicDoc* GetDocument() const;

// 操作
public:

// オーバーライド
public:
	virtual void OnDraw(CDC* pDC);  // このビューを描画するためにオーバーライドされます。
	virtual BOOL PreCreateWindow(CREATESTRUCT& cs);
protected:
	virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);
	virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);
	virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

// 実装
public:
	virtual ~C落書きMusicView();
#ifdef _DEBUG
	virtual void AssertValid() const;
	virtual void Dump(CDumpContext& dc) const;
#endif

protected:

// 生成された、メッセージ割り当て関数
protected:
	DECLARE_MESSAGE_MAP()
public:
	virtual void OnInitialUpdate();
	afx_msg void OnTimer(UINT_PTR nIDEvent);
	afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
	afx_msg void OnMouseMove(UINT nFlags, CPoint point);
	afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
};

#ifndef _DEBUG  // 落書きMusicView.cpp のデバッグ バージョン
inline C落書きMusicDoc* C落書きMusicView::GetDocument() const
   { return reinterpret_cast<C落書きMusicDoc*>(m_pDocument); }
#endif

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 11:15
by softya(ソフト屋)
かなたん さんが書きました:C++の書き方―グローバル変数ではなく、クラスのメンバ変数にするってことですよね?
C++でそういうことをしたことはあるのですが、MFCではメンバ変数を作るということを習ってなくて、授業で先生もグローバル変数を使っていました。
グローバル変数だとそのcpp内のどこでも使えて、メンバ変数だとそのクラスでしか使えないんですよね?
グローバル変すよりもメンバ変数でって言うのは、範囲を制限した変数も使えるようになった方がいいとかってことですか?
MFCはC++で構成されたフレームワークライブラリにすぎませんので純粋なC++です。
グローバル変数で組んでしまうと1つしかインスタンスが作れませんので、継承や多数のインスタンスが作れない問題が出てきてしまいますよ。
小規模な内は良いかもしれませんが。

とりあえず動くようにして、チラつきを出来るだけ抑えてみました。
次の点が変わっています。
・こちらの都合で 落書きMusicView → MFCsdiView になっています。
・CDC canvasDC;がグローバル変数になっています。
・OnDrawでsetの方法の変更とpDC->BitBlt(0,0,500,500,&canvasDC,0,0,SRCCOPY);が毎回行われるようになっています。
・OnMouseMoveで描画時にInvalidate(); //再描画しています。
・OnTimerでif( playing ) Invalidate(); //再描画(というか線との合成)を指示に変更しました。
・OnEraseBkgndを追加して毎回背景クリアをしないようにしました。ゴミが残る部分はOnDrawで自分でクリアして下さい。

コード:

// MFCsdiView.cpp : CMFCsdiView クラスの実装
//
 
#include "stdafx.h"
#include "MFCsdi.h"
 
#include "MFCsdiDoc.h"
#include "MFCsdiView.h"
 
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
 
CRect button[12]; //ボタンの位置を記憶
COLORREF color[9]; //描画で使用できる色を記憶
int set_color=0; //何番目の色を使用するのか記憶
CRect canvas(0,0,500,500); //描画可能範囲を記憶
CBitmap canvas_Bit; //描画時等に使用したいビットマップ
CDC canvasDC; //ビットマップ読み込んだりとか用CDCを作成
bool paint=false; //描画可能かどうかを記憶
CPoint pointed; //移動前の座標を記憶
int timer; //なんとなくSetTimerの戻り値を受け取ってみる
int x,y; //線の座標等を記憶
bool set=false; //ビットマップを作成したかどうかを記憶
bool playing=false; //再生中かどうかを記憶
 
// CMFCsdiView
 
IMPLEMENT_DYNCREATE(CMFCsdiView, CView)
 
BEGIN_MESSAGE_MAP(CMFCsdiView, CView)
    // 標準印刷コマンド
    ON_COMMAND(ID_FILE_PRINT, &CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_DIRECT, &CView::OnFilePrint)
    ON_COMMAND(ID_FILE_PRINT_PREVIEW, &CView::OnFilePrintPreview)
    ON_WM_TIMER()
    ON_WM_LBUTTONDOWN()
    ON_WM_MOUSEMOVE()
    ON_WM_LBUTTONUP()
    ON_WM_KEYDOWN()
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
 
// CMFCsdiView コンストラクション/デストラクション
 
CMFCsdiView::CMFCsdiView()
{
    // TODO: 構築コードをここに追加します。
 
}
 
CMFCsdiView::~CMFCsdiView()
{
}
 
BOOL CMFCsdiView::PreCreateWindow(CREATESTRUCT& cs)
{
    // TODO: この位置で CREATESTRUCT cs を修正して Window クラスまたはスタイルを
    //  修正してください。
 
    return CView::PreCreateWindow(cs);
}
 
// CMFCsdiView 描画
 
void CMFCsdiView::OnDraw(CDC* pDC)
{
    CMFCsdiDoc* pDoc = GetDocument();
    ASSERT_VALID(pDoc);
    if (!pDoc)
        return;
 
    // TODO: この場所にネイティブ データ用の描画コードを追加します。
    
    
    CPen edge(PS_SOLID,1,RGB(0,0,0)); //画面を分割したいので黒いペンを作成
    pDC->SelectObject(&edge);
    pDC->MoveTo(500,0); //画面(500×600)を描画可能部分とボタン配置部分とに分けるように縦線を引く
    pDC->LineTo(500,500);
    for(int n=0;n<9;n++){ //9色のボタンを描画
        CBrush pen(color[n]);
        pDC->SelectObject(pen);
        pDC->Ellipse(button[n]);
    }
    //以下3つのボタンは本来は(リソース内の)画像を使用しているが、今回は画像がなくても再現できるよう四角い図形を描くことに変更
    CBrush white(RGB(255,255,255));
    pDC->SelectObject(&white);
    pDC->Rectangle(button[9]);
    CBrush red(RGB(255,0,0));
    pDC->SelectObject(&red);
    pDC->Rectangle(button[10]);
    CBrush blue(RGB(0,0,255));
    pDC->SelectObject(&blue);
    pDC->Rectangle(button[11]);
    //変更ここまで
    if(set==false){ //ビットマップを作成していないなら
    	canvasDC.CreateCompatibleDC(pDC);
        canvas_Bit.CreateCompatibleBitmap(pDC,500,500); //ビットマップ作成
        CPen white(PS_SOLID,1,RGB(255,255,255));
        canvasDC.SelectObject(&white);
        canvasDC.SelectObject(&canvas_Bit); //ビットマップを読み込んで
        canvasDC.Rectangle(canvas); //白く塗っておく 出ないと再生時にその部分が真っ黒に・・・;
        set=true;
	}
    //	毎回画面にコピーして
    pDC->BitBlt(0,0,500,500,&canvasDC,0,0,SRCCOPY);
    
    if(playing==true){ //再生中なら
        CPen line(PS_SOLID,5,RGB(0,0,0));
        pDC->SelectObject(line);
        pDC->MoveTo(x,0); //その上から縦線を引く
        pDC->LineTo(x,500);
    }
}
 
 
// CMFCsdiView 印刷
 
BOOL CMFCsdiView::OnPreparePrinting(CPrintInfo* pInfo)
{
    // 既定の印刷準備
    return DoPreparePrinting(pInfo);
}
 
void CMFCsdiView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: 印刷前の特別な初期化処理を追加してください。
}
 
void CMFCsdiView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)
{
    // TODO: 印刷後の後処理を追加してください。
}
 
 
// CMFCsdiView 診断
 
#ifdef _DEBUG
void CMFCsdiView::AssertValid() const
{
    CView::AssertValid();
}
 
void CMFCsdiView::Dump(CDumpContext& dc) const
{
    CView::Dump(dc);
}
 
CMFCsdiDoc* CMFCsdiView::GetDocument() const // デバッグ以外のバージョンはインラインです。
{
    ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CMFCsdiDoc)));
    return (CMFCsdiDoc*)m_pDocument;
}
#endif //_DEBUG
 
 
// CMFCsdiView メッセージ ハンドラ
 
void CMFCsdiView::OnInitialUpdate()
{
    CView::OnInitialUpdate();
 
    // TODO: ここに特定なコードを追加するか、もしくは基本クラスを呼び出してください。
    color[0]=RGB(255,0,0); //描画時に使用できる色を設定
    color[1]=RGB(255,95,0);
    color[2]=RGB(255,255,0);
    color[3]=RGB(0,255,0);
    color[4]=RGB(0,128,0);
    color[5]=RGB(0,255,255);
    color[6]=RGB(0,0,255);
    color[7]=RGB(255,0,255);
    color[8]=RGB(255,255,255);
    for(int n=0;n<12;n++){ //ボタンの座標を設定
        button[n].top=4+n*41;
        button[n].bottom=44+n*41;
        button[n].left=530;
        button[n].right=570;
    }
}
 
void CMFCsdiView::OnLButtonDown(UINT nFlags, CPoint point)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    CView::OnLButtonDown(nFlags, point);
    for(int n=0;n<9;n++){
        if(PtInRect(button[n],point)==1){ //色のボタンが押されたら
            set_color=n; //使用する色の番号を変更
        }
    }
    if(PtInRect(button[9],point)==1){ //今までの描画をすべてクリアするためのボタンが押されたら
        CPen white(PS_SOLID,1,RGB(255,255,255));
        canvasDC.SelectObject(&white);
        canvasDC.SelectObject(&canvas_Bit); //ビットマップとともに
        canvasDC.Rectangle(canvas); //描画範囲を白く塗りつぶす
    }
    if(PtInRect(button[10],point)==1){ //再生ボタンが押されたら
        x=0; //初期座標を左上に設定し
        y=0;
        playing=true; //再生中だといい
        timer=SetTimer(1,100,NULL); //タイマー作動
    }
    if(PtInRect(button[11],point)==1){ //停止ボタンが押されたら
        playing=false; //再生中でないといい
        timer=KillTimer(1); //タイマー停止
    }
    if(PtInRect(canvas,point)==1){ //描画可能範囲が押されたら
        paint=true; //描画可能だといい
        pointed=point; //今の座標は次には過去のもの
    }
}
 
void CMFCsdiView::OnMouseMove(UINT nFlags, CPoint point)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    CView::OnMouseMove(nFlags, point);
    if(paint==true&&PtInRect(canvas,point)==1){ //描画可能で範囲内なら
        CPen pen(PS_SOLID,5,color[set_color]); //今の色のペンを作成し
        canvasDC.SelectObject(&pen);
        canvasDC.SelectObject(&canvas_Bit); //ビットマップとともに
        canvasDC.MoveTo(pointed); //前の座標から今の座標まで線を引く
        canvasDC.LineTo(point);
        pointed=point; //今の座標は次には過去のもの
        
 	   Invalidate(); //再描画
    }
    if(PtInRect(canvas,point)!=1){ //範囲外なら
        paint=false; //描画不可にする
    }
}
 
void CMFCsdiView::OnLButtonUp(UINT nFlags, CPoint point)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    CView::OnLButtonUp(nFlags, point);
    paint=false; //マウスの左ボタンが離されたら描画不可にする
}
 
void CMFCsdiView::OnTimer(UINT_PTR nIDEvent)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    CView::OnTimer(nIDEvent);
    
    if( playing ) Invalidate(); //再描画(というか線との合成)を指示
    
    COLORREF get_color; //取得した色を記憶
    if((x%5)==0){
        for(y=0;y<500;y++){
            get_color=canvasDC.GetPixel(x,y); //指定の座標の色を取得
            if(get_color==RGB(255,0,0)){ //色によってきまった音を鳴らしたい
                ;//c4
            }
            else if(get_color==RGB(255,98,0)){
                ;//d4
            }
            else if(get_color==RGB(255,255,0)){
                ;//e4
            }
            else if(get_color==RGB(0,255,0)){
                ;//f4
            }
            else if(get_color==RGB(0,128,0)){
                ;//g4
            }
            else if(get_color==RGB(0,255,255)){
                ;//a4
            }
            else if(get_color==RGB(0,0,255)){
                ;//b4
            }
            else if(get_color==RGB(255,0,255)){
                ;//c5
            }
        }
    }
    if(x<500){ //終わりでないなら
        x++; //次へ行く
    }
    else{ //終わりなら
        x=0; //先頭へ戻る
    }
}
BOOL CMFCsdiView::OnEraseBkgnd(CDC* pDC)
{
	// TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。

	//	背景のクリアを抑止する。
	//return CView::OnEraseBkgnd(pDC);
	return FALSE;
}

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 14:25
by かなたん
softya(ソフト屋) さんが書きました: MFCはC++で構成されたフレームワークライブラリにすぎませんので純粋なC++です。
グローバル変数で組んでしまうと1つしかインスタンスが作れませんので、継承や多数のインスタンスが作れない問題が出てきてしまいますよ。
小規模な内は良いかもしれませんが。
以前C++で1つで100人分記憶できるゲームソフト管理プログラムを作ったときは必要な数だけクラスを実装したりしましたが、それ以来はそういったことはしておらず、継承もしてみたことがなく・・・
MFCでも継承や多数のインスタンスを作ったりすることがあるのですか?
今までMFCでいくつかプログラムを作ってきたが、グローバル変数で困ったことはないもので。
softya(ソフト屋) さんが書きました:とりあえず動くようにして、チラつきを出来るだけ抑えてみました。
次の点が変わっています。
・こちらの都合で 落書きMusicView → MFCsdiView になっています。
・CDC canvasDC;がグローバル変数になっています。
・OnDrawでsetの方法の変更とpDC->BitBlt(0,0,500,500,&canvasDC,0,0,SRCCOPY);が毎回行われるようになっています。
・OnMouseMoveで描画時にInvalidate(); //再描画しています。
・OnTimerでif( playing ) Invalidate(); //再描画(というか線との合成)を指示に変更しました。
・OnEraseBkgndを追加して毎回背景クリアをしないようにしました。ゴミが残る部分はOnDrawで自分でクリアして下さい。
ソースありがとうございました。
おかげで描画したものが消えることなく線と合成させることができました。
そのままではちらついてしまっていたので、Invalidate(false);にしてみたところ、ちらつきがなくなりました。
そこで、いろいろと質問があるのですが、OnEraseBkgndはサイズ変更等があったときに呼び出されるんですよね?(MSDNの説明を見て)
ということは、サイズ変更不可のウィンドウなら呼び出されないですよね?
今回はサイズ変更不可で作成しているので、OnEraseBkgndありなしで試してみたところ、Invalidateがfalseかどうかでちらつきあるなしが変わるくらいで、どちらでも描画したものが消えてしまうことがありませんでした。
また、OnEraseBkgndありなしでOnDrawの先頭にブレークポイントを置いて試してみたところ、どちらでも画面が真っ白になっていました。
ということは、OnEraseBkgndは画面消去には影響していないということですよね?
私のソースでうまくいかなかったのは、CDC canvasDC;をグローバル等で宣言していなかったなどが原因ではないかと今の私は思っています。
では、なぜOnDrawの先頭で画面が真っ白になってしまうのでしょうか?
またその現象について、先生には普通はそうはならないと言われたのですが、そうなのでしょうか?

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 14:50
by softya(ソフト屋)
かなたん さんが書きました:以前C++で1つで100人分記憶できるゲームソフト管理プログラムを作ったときは必要な数だけクラスを実装したりしましたが、それ以来はそういったことはしておらず、継承もしてみたことがなく・・・
MFCでも継承や多数のインスタンスを作ったりすることがあるのですか?
今までMFCでいくつかプログラムを作ってきたが、グローバル変数で困ったことはないもので。
Viewを継承することもありえますよ。幾つかのタイプのViewを切り替えて使うアプリとかですね。
一番使うのはコントロールを独自実装する時ですね。
例えばCButtonとか継承して特殊ボタンを作ったりとかしますので、ここでグローバル変数を使うとインスタンスが複数作れませんので非常に困ります。
かなたん さんが書きました:ソースありがとうございました。
おかげで描画したものが消えることなく線と合成させることができました。
そのままではちらついてしまっていたので、Invalidate(false);にしてみたところ、ちらつきがなくなりました。
そこで、いろいろと質問があるのですが、OnEraseBkgndはサイズ変更等があったときに呼び出されるんですよね?(MSDNの説明を見て)
ということは、サイズ変更不可のウィンドウなら呼び出されないですよね?
今回はサイズ変更不可で作成しているので、OnEraseBkgndありなしで試してみたところ、Invalidateがfalseかどうかでちらつきあるなしが変わるくらいで、どちらでも描画したものが消えてしまうことがありませんでした。
CMainFrame::PreCreateWindowで
cs.style &= ~WS_SIZEBOX; //サイズ変更禁止
としてもOnEraseBkgndは画面が書き換わるとき必ず呼び出されます。
OnEraseBkgndにブレークポイントを設定してみれば分かると思いますよ。
かなたん さんが書きました: また、OnEraseBkgndありなしでOnDrawの先頭にブレークポイントを置いて試してみたところ、どちらでも画面が真っ白になっていました。
ということは、OnEraseBkgndは画面消去には影響していないということですよね?
私のソースでうまくいかなかったのは、CDC canvasDC;をグローバル等で宣言していなかったなどが原因ではないかと今の私は思っています。
では、なぜOnDrawの先頭で画面が真っ白になってしまうのでしょうか?
またその現象について、先生には普通はそうはならないと言われたのですが、そうなのでしょうか?
描画うまく行かなかった件でCDC canvasDC;をグローバルじゃなかった事も原因の一つですが、初期化や描画やら私の書いたことが複合的に問題の原因です。
OnEraseBkgndに関してはチラつきに関係して私が加えたのですがOnEraseBkgndでCView::OnEraseBkgnd(pDC);を抑止した場合はOnDrawで真っ白には私のところはなりません。前の画面が残ります。
なので普通はそうならないと思います。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 15:57
by かなたん
softya(ソフト屋) さんが書きました:Viewを継承することもありえますよ。幾つかのタイプのViewを切り替えて使うアプリとかですね。
一番使うのはコントロールを独自実装する時ですね。
例えばCButtonとか継承して特殊ボタンを作ったりとかしますので、ここでグローバル変数を使うとインスタンスが複数作れませんので非常に困ります。
私のアイデア等が足りないせいかそれらの具体例が思いつかないのですが・・・MFCでもそういったことで継承をしたりするのですね。
継承って、たとえば家族クラスで家族の基本(夜寝て朝起きるなど)を作り、そのクラスを継承してお父さんクラスを作ってその中に会社へ行くなどお父さんがすることを追加したり、お母さんクラスを作って家事全般などお母さんがすることを追加したりすることですよね?
softya(ソフト屋) さんが書きました:CMainFrame::PreCreateWindowで
cs.style &= ~WS_SIZEBOX; //サイズ変更禁止
としてもOnEraseBkgndは画面が書き換わるとき必ず呼び出されます。
OnEraseBkgndにブレークポイントを設定してみれば分かると思いますよ。

コード:

BOOL C落書きMusicView::OnEraseBkgnd(CDC* pDC)
{
    // TODO: ここにメッセージ ハンドラ コードを追加するか、既定の処理を呼び出します。
 
    //  背景のクリアを抑止する。
    //return CView::OnEraseBkgnd(pDC);
    return FALSE;
}
のreturnのところにブレークポイントを設置してデバッグを開始し、描画・再生・停止・全削除・ウィンドウを閉じるを適当に試しても全く引っ掛かりませんでした。
また、OnDrawの先頭では画面は真っ白になっていました。
ということは、画面が書き変わっていることがOnEraseBkgndには伝わっていないということなのですか?
それとも、もっと違うことが起きたとき呼び出されるとか・・・?
それから余談かもですが、cs.style &= ~WS_SIZEBOX;(というかWS_SIZEBOXという単語)はこのソリューションには見つかりませんでした。
Visual Studio2008は初期設定でサイズ変更を許可しなかったらなら、そういう記述をしないんですかね?
softya(ソフト屋) さんが書きました:描画うまく行かなかった件でCDC canvasDC;をグローバルじゃなかった事も原因の一つですが、初期化や描画やら私の書いたことが複合的に問題の原因です。
OnEraseBkgndに関してはチラつきに関係して私が加えたのですがOnEraseBkgndでCView::OnEraseBkgnd(pDC);を抑止した場合はOnDrawで真っ白には私のところはなりません。前の画面が残ります。
なので普通はそうならないと思います。
FALSEをコメントアウトしてその上のを利用してみたところ、OnDrawの先頭や描画しているときでも変化は見られませんでした。
ということは、CView::OnEraseBkgnd(pDC);は呼び出しを行っていないということなのでしょうか?
やはり、前述の通りそもそもOnEraseBkgndは実行されていないということでしょうか?
実はreturnでは引っかからずともOnEraseBkgndは実行されていて、それが画面消去に関与しているのでしょうか?

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 16:25
by softya(ソフト屋)
かなたん さんが書きました:私のアイデア等が足りないせいかそれらの具体例が思いつかないのですが・・・MFCでもそういったことで継承をしたりするのですね。
継承って、たとえば家族クラスで家族の基本(夜寝て朝起きるなど)を作り、そのクラスを継承してお父さんクラスを作ってその中に会社へ行くなどお父さんがすることを追加したり、お母さんクラスを作って家事全般などお母さんがすることを追加したりすることですよね?
それは実によく聞くオブジェクト指向の継承の説明パターンですね。
もっとMFCに偏って具体的に書くと例えばCButtonクラスを継承してCColorButtonクラスを作ったとします。
CColorButtonクラスではDrawItemをオーバーライドして色つきのボタンを描画させるといったことが出来るわけです。
その他の機能はCButtonのまま継承しているのでCButtonと置き換えて使うことが出来ます。
CColorButtonクラスの違う点はカラーを設定するメンバ関数が増えることぐらいでしょか。
こういう事が出来るのはMFCがC++をベースに作られているからです。

あと気づかれて無いかもしれませんが、CMainFrameやC落書きMusicViewもベースとなるクラスCFrameWndとCViewをそれぞれ継承していますよ。
かなたん さんが書きました:のreturnのところにブレークポイントを設置してデバッグを開始し、描画・再生・停止・全削除・ウィンドウを閉じるを適当に試しても全く引っ掛かりませんでした。
また、OnDrawの先頭では画面は真っ白になっていました。
ということは、画面が書き変わっていることがOnEraseBkgndには伝わっていないということなのですか?
それとも、もっと違うことが起きたとき呼び出されるとか・・・?
MFCの基本的なところをなのですが、C落書きMusicViewのBEGIN_MESSAGE_MAPに次のメッセージマクロはありますか?
コレがないとOnEraseBkgnd()が呼び出されません

コード:

BEGIN_MESSAGE_MAP(C落書きMusicView, CView)
    他の定義
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
かなたん さんが書きました:それから余談かもですが、cs.style &= ~WS_SIZEBOX;(というかWS_SIZEBOXという単語)はこのソリューションには見つかりませんでした。
Visual Studio2008は初期設定でサイズ変更を許可しなかったらなら、そういう記述をしないんですかね?
初期設定したら違うところで処理されますね。WS_SIZEBOXは後付けて処理するときだけです。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 17:14
by かなたん
softya(ソフト屋) さんが書きました: もっとMFCに偏って具体的に書くと例えばCButtonクラスを継承してCColorButtonクラスを作ったとします。
CColorButtonクラスではDrawItemをオーバーライドして色つきのボタンを描画させるといったことが出来るわけです。
その他の機能はCButtonのまま継承しているのでCButtonと置き換えて使うことが出来ます。
CColorButtonクラスの違う点はカラーを設定するメンバ関数が増えることぐらいでしょか。
こういう事が出来るのはMFCがC++をベースに作られているからです。
CButtonはダイアログのボタンのことですよね?
そもそもそれは色付きで作ることはできないのですね。
(そういうところもVBAのユーザーフォームとは違うと)
softya(ソフト屋) さんが書きました: あと気づかれて無いかもしれませんが、CMainFrameやC落書きMusicViewもベースとなるクラスCFrameWndとCViewをそれぞれ継承していますよ。
そうなのですね。
確かに、クラスビューを見てみたらC落書きMusicViewはCViewの派生形のところに書かれていました。
今まで考えても見ませんでした。
softya(ソフト屋) さんが書きました: MFCの基本的なところをなのですが、C落書きMusicViewのBEGIN_MESSAGE_MAPに次のメッセージマクロはありますか?
コレがないとOnEraseBkgnd()が呼び出されません

コード:

BEGIN_MESSAGE_MAP(C落書きMusicView, CView)
    他の定義
	ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
ON_WM_ERASEBKGND()はありました。
でも、//でコメントアウトされていました。
OnEraseBkgndを手動で追加した後かどこかで自動でコメントアウトされたんですかね?
ちなみに、OnEraseBkgndを追加していないサイズ可能なウィンドウを持つプログラムでは、ON_WM_ERASEBKGND()は見つかりませんでした。
ということは、そもそもVisual StudioでMFCを作るとOnEraseBkgndは呼び出されないということですか?
softya(ソフト屋) さんが書きました: 初期設定したら違うところで処理されますね。WS_SIZEBOXは後付けて処理するときだけです。
だからソリューション全体を探してもWS_SIZEBOXは見つからないのですね。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 17:21
by softya(ソフト屋)
かなたん さんが書きました:ON_WM_ERASEBKGND()はありました。
でも、//でコメントアウトされていました。
OnEraseBkgndを手動で追加した後かどこかで自動でコメントアウトされたんですかね?
ちなみに、OnEraseBkgndを追加していないサイズ可能なウィンドウを持つプログラムでは、ON_WM_ERASEBKGND()は見つかりませんでした。
ということは、そもそもVisual StudioでMFCを作るとOnEraseBkgndは呼び出されないということですか?
私のVisualStudio2008では新規にサイズ変更不可のSDIプロジェクトを作ってWM_ERASEBKGNDを追加してもコメントアウトされずに追加されます。
OnEraseBkgnd()もちゃんと呼び出されています。
ON_WM_ERASEBKGND()がコメントアウトされている以上は、継承元のCViewのOnEraseBkgnd()が暗黙の内に呼び出されているものと思いますので画面がクリアされるんだと思います。
ON_WM_ERASEBKGND()がコメントアウトを外してみてください。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 18:40
by かなたん
softya(ソフト屋) さんが書きました: 私のVisualStudio2008では新規にサイズ変更不可のSDIプロジェクトを作ってWM_ERASEBKGNDを追加してもコメントアウトされずに追加されます。
OnEraseBkgnd()もちゃんと呼び出されています。
ON_WM_ERASEBKGND()がコメントアウトされている以上は、継承元のCViewのOnEraseBkgnd()が暗黙の内に呼び出されているものと思いますので画面がクリアされるんだと思います。
ON_WM_ERASEBKGND()がコメントアウトを外してみてください。
ON_WM_ERASEBKGND()のコメントアウトを外してみたところ、OnDrawの先頭で画面が消去されることはなくなりました。
returnのところにブレークポイントを設置してデバッグしてみたところ、最初にOnDrawが呼ばれる前の画面が真っ白の時に引っ掛かりましたが、それ以降はなにをやっても引っかかりませんでした。
また、両方のreturnを試してみたところ、どちらでもOnDrawの先頭で画面が消去されることはなく、ブレークポイントを置いていても最初の1度しか引っ掛かりませんでした。
そういうものなのでしょうか?
勝手かもですが、私はOnDrawの直前に毎回呼び出されるものだと思っていたのですが。
それまでは毎回OnDrawの先頭では画面が真っ白になっていたもので。
また、CView::OnEraseBkgnd(pDC);がreturnされることにより画面が消去されてしまっていたのかと思っていたのですが、そうではないのでしょうか?
今までの説明から、CView::OnEraseBkgnd(pDC);の当たりが原因だと思ったのですが。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 18:48
by softya(ソフト屋)
基本的にInvalidate(FALSE);で呼び出しているので、その時はOnEraseBkgnd()は呼び出されないはずです。FALSEはそのためのフラグですからね。
ただ、ウィンドウを移動させたりとか他にウィンドウの背後に隠したときはOnEraseBkgnd()が呼び出されるはずです。
※ WndowsXPとWindws7では動作が変わります。

あとCView::OnEraseBkgnd(pDC);がどうなるかはデバッガで追いかけてみてください。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月23日(月) 19:06
by かなたん
softya(ソフト屋) さんが書きました:基本的にInvalidate(FALSE);で呼び出しているので、その時はOnEraseBkgnd()は呼び出されないはずです。FALSEはそのためのフラグですからね。
ただ、ウィンドウを移動させたりとか他にウィンドウの背後に隠したときはOnEraseBkgnd()が呼び出されるはずです。
※ WndowsXPとWindws7では動作が変わります。

あとCView::OnEraseBkgnd(pDC);がどうなるかはデバッガで追いかけてみてください。
Invalidate(false);にすると背景を消さずに再描画するとかなんとか先生から教わったような気がするのですが、Invalidate();だと勝手にOnEraseBkgndを呼んでいたのですね。
それでInvalidate(false);にしたときと違って画面消去が挟まってしまってちらつくのですね。
これできちんと仕組みを理解することができました。
softya(ソフト屋)さん、いろいろとありがとうございました。

Invalidate();以外でOnEraseBkgndが反応するときですが、Windows7のこのパソコンでは、その他ウィンドウがかぶさっても反応しませんでしたが、自分が最小化されてから復活する・ディスプレイからはみ出た部分がディスプレイ内に戻るときには反応することもわかりました。
自分がアクティブのときにどうなったかによって反応したりするのですね。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月24日(火) 00:01
by Ryo
かなたん さんが書きました:MFCでも継承や多数のインスタンスを作ったりすることがあるのですか?
例えば、エクセルのようなマルチドキュメント(MDI)を思い浮かべてください。
MDIでは、CView(の派生)の子ウィンドウが複数作成されることが前提になりますので
描画のための情報をグローバルで保持するのには無理があります。
かなたん さんが書きました:Windows7のこのパソコンでは、その他ウィンドウがかぶさっても反応しませんでしたが、
おまけ的な内容ですが
システムの詳細設定>パフォーマンスの設定>「デスクトップコンポジションを有効にする」
のチェックを外して(Aeroが切れる)あると、XPと同じ動きになります。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月24日(火) 08:29
by かなたん
Ryo さんが書きました: 例えば、エクセルのようなマルチドキュメント(MDI)を思い浮かべてください。
MDIでは、CView(の派生)の子ウィンドウが複数作成されることが前提になりますので
描画のための情報をグローバルで保持するのには無理があります。
私はいつもシングルドキュメントで作っていたので、グローバルでも困らなかったのですね。
ちなみに、MFCでシングルドキュメント以外を使用したプログラムは習ったことがないです。
Ryo さんが書きました:おまけ的な内容ですが
システムの詳細設定>パフォーマンスの設定>「デスクトップコンポジションを有効にする」
のチェックを外して(Aeroが切れる)あると、XPと同じ動きになります。
試してみたら、まず1度も背景を塗りつぶす行為をしていない右側のボタンを配置している部分の背景が透明になりました。
これもXPの仕様ですか?
また、OnEraseBkgnd内のreturnにブレークポイントを置いてデバッグしてみると、Aeroあり7ならInvalidate(false);で最初しか呼び出されなかったOnEraseBkgndが、常時呼び出されているのか何度再開してもそこで引っ掛かるようになりました。
なぜそうなるのですか?
まだウィンドウを見たか見てないかくらいで、自分で移動とかしていない状態だったのですが。
これもXPの仕様なら、XPではOnEraseBkgndがデフォルトのままだとちらついてしょうがなくなりませんか?

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月24日(火) 10:27
by softya(ソフト屋)
かなたん さんが書きました:試してみたら、まず1度も背景を塗りつぶす行為をしていない右側のボタンを配置している部分の背景が透明になりました。
これもXPの仕様ですか?
背景をクリアしていないので、それが原因です。
かなたん さんが書きました:また、OnEraseBkgnd内のreturnにブレークポイントを置いてデバッグしてみると、Aeroあり7ならInvalidate(false);で最初しか呼び出されなかったOnEraseBkgndが、常時呼び出されているのか何度再開してもそこで引っ掛かるようになりました。
なぜそうなるのですか?
ブレークポイントに引っかかった時にウィンドウがViusalStudioの背後に隠れるからだと思います。
かなたん さんが書きました: まだウィンドウを見たか見てないかくらいで、自分で移動とかしていない状態だったのですが。
これもXPの仕様なら、XPではOnEraseBkgndがデフォルトのままだとちらついてしょうがなくなりませんか?
自分で下が見える部分を塗りつぶせば良いのです。
毎回全部を塗りつぶすよりは効率的です。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月24日(火) 11:07
by かなたん
softya(ソフト屋) さんが書きました: 背景をクリアしていないので、それが原因です。
ビットマップ作るときに一緒に塗ってしまえばいいんですね。
softya(ソフト屋) さんが書きました: ブレークポイントに引っかかった時にウィンドウがViusalStudioの背後に隠れるからだと思います。
XPだとそれだけでも呼ばれるのですね。
softya(ソフト屋) さんが書きました: 自分で下が見える部分を塗りつぶせば良いのです。
毎回全部を塗りつぶすよりは効率的です。
Invalidate(false);等をすれば、最初の1回だけ塗っておけば後は平気ですよね?

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月24日(火) 11:14
by softya(ソフト屋)
どうすれば効率的かは自分で色々やってみてください。
塗りつぶし有効にしておいてInvalidate(false);とするのが操作上で一番快適なら、そうするべきだと思います。
こちらの提示は、こうすることも出来るよって事に過ぎません。

Re: MFC ボタン押す前の画面の状態を保存したい。

Posted: 2012年4月24日(火) 11:59
by かなたん
softya(ソフト屋) さんが書きました: どうすれば効率的かは自分で色々やってみてください。
塗りつぶし有効にしておいてInvalidate(false);とするのが操作上で一番快適なら、そうするべきだと思います。
こちらの提示は、こうすることも出来るよって事に過ぎません。
Invalidate(false);でOnEraseBkgndが呼ばれないようにするのと、OnEraseBkgndでFALSEを返すのとはほとんど同じ動きをするんですよね。
そもそも呼ばないか、読んでも消されないようにするかの違いとかを除いて。
ここで質問するまでの私はOnEraseBkgndについては一切知らなかったことや、ちらつきに対して先生に質問したときはInvalidate(false);にするといいって聞いていたこともあり、ちらついてたらfalseにしてそれでうまくいけばそれでいいかなくらいにしか思っていなかったのですが、回答を見ていてそれだけが解決法ではないことを知ることができました。
今まで、たとえ複数のやり方があってもどちらの方がより適しているかなどは調べたりしていなかったのですが、できるなら調べて比較することも必要ですよね。
いろいろと提示・回答ありがとうございます。