お世話になります。
カスタムコントロールに折れ線グラフを書いています、やりたい事を箇条書きにしますと
1. グラフの上でマウスの左クリックイベントが発生。
2. マウスイベントの発生したx座標に垂直線を描画。
3. 以降、マウスイベントが発生する都度に、前回描画した垂直線消去。
4. 新たな座標に垂直線を描画。
この動作を行いたいのですが、垂線を描くことは出来るのですが、背景のグラフを
残したまま垂線の消去方法が分かりません、どの様な考え方で実装していったら良いのか
(デバイスコンテキストにビットマップデータを退避させデータ加工を行い、BitBitで出
力すると思うのですがその方法が分かりません)
ご教示願えませんでしょうか。
Windows XP SP2 VS2005 MFC
ダブルバッファリングの方法は次のようなものです。
// 変数(ダイアログのメンバ変数などにする)
CDC m_dcMem;
CBitmap m_bmpMem;
// 準備(初回のみ)
CClientDC dc(this);
m_dcMem.CreateCompatibleDC(&dc);
m_bmpMem.CreateCompatibleBitmap(&dc, 1600, 1200); // 必要なサイズで
m_dcMem.SelectObject(m_bmpMem);
// 背景の描画(画面にはまだ表示されない)
m_dcMem.Rectangle(r); // このタイミングで折れ線グラフを描く
// 実際の描画(OnDrawなど)
pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &m_dcMem, 0, 0, SRCCOPY);
ソイレントグリーンさんのケースでは、実際の描画の時に
背景であるグラフをBitBltしてから、垂直線を直接描けばよいでしょう。
次のクリックで垂直線を消すことはできませんので、
全て描きなおしになりますが、グラフについては背景としてm_dcMemに
残っています。これをBitBltしてから新しい垂直線を引いてください。
グラフを更新する場合も一旦m_dcMemに描き足すか描きなおしてから、
実際の描画をBitBltで行います。
たいちうさんお世話になります。
大筋考え方はよく理解できました、つもりです(^^;
処で、たいちうさんのご教示して頂いたコードですが、
・変数(ダイアログのメンバ変数などにする)
・準備(初回のみ)
・背景の描画(画面にはまだ表示されない)
・実際の描画(OnDrawなど)
と遷移していくのですが、私の場合、訳ありで既に最初にグラフが描画されている状態
からの実装になるのですがその場合、現在描かれているビットマップデータをメモリデバ
イスコンテキストにロードして、それを背景画面とすれば良いと思うのですが、
デバイスコンテキストに実際のインスタンスを割り当てるために、
CDC *pDC;(デッバッガーの状態+ pDC 0xcccccccc {hDC=???
attrib=???} CDC *)
pDC = GetDC();(上記のためpDCには何も取得できません)
としたのですが、インスタンスが取得できません、
結果
m_dcMem.CreateCompatibleDC(&pDC);の部分で
Debug Assertion Failed!
になります。考え方が間違っているのでしょうか
その状態を、
// 変数(ダイアログのメンバ変数などにする)
CDC m_dcMem;
CBitmap m_bmpMem;
CBitmap* pBitmap; // ビットマップ
CDC *pDC; // デバイスコンテキストを格納するポインタ
// 準備(初回のみ)
CRect cClient; // クライアント領域(グラフや垂線が描画されるカスタムコントロー
ル)
pBitmap = new CBitmap(); // ビットマップオブジェクトを動的に作成
GetClientRect(&cClient);
pDC = GetDC(); // インスタンスが取得できません
m_dcMem.CreateCompatibleDC(&pDC); // 無効な引数を与えているのでDebug Assertion
Failed!
// カスタムコントロールのサイズでビットマップデータ作成
m_bmpMem.CreateCompatibleBitmap(pDC,cClient.Width(),cClient.Height());
m_dcMem.SelectObject(m_bmpMem);
// 垂線の描画(画面にはまだ表示されない)
CPen pen(PS_SOLID, 0, RGB(255,255,255));
CPen* pOldPen = m_dcMem.SelectObject(&pen);
m_dcMem.MoveTo(CPoint(x_pos, rcClient.top)); // x_posは垂線のx座標
m_dcMem.LineTo(CPoint(x_pos, rcClient.bottom));
m_dcMem.SelectObject(pOldPen);
// 実際の描画(OnDrawなど)
pDC->BitBlt(0, 0, rect.Width(), rect.Height(), &m_dcMem, 0, 0, SRCCOPY);
少し混乱していたので、整理しました
・古い垂直線を消去するためにグラフ描画済のメモリデバイスコンテキストの表示
・既にグラフは描画してあるので、保存用のメモリデバイスコンテキストm_MemDCを定義
・m_MemDCをm_tmpMemDCにコピー
・m_tmpMemDCに垂直線を描画
・画像出力
この様な動作を行いたいので下記のように実装してのですが、グラフの上をクリックして
も画面が変化しません、どこか勘違いしていると思うのですが分かりません、ご指摘願え
ませんでしょうか。宜しくお願い致します。
CDC m_MemDC; // グラフ描画済DC
CDC m_tmpMemDC; // グラフ描画用と垂直線を描画するDC
CBitmap m_Bitmap;
CBitmap* pBitmap;
void CLineChartCtrl::DrawVerticalLine(int x_pos)
{
// クライアント領域
CRect cClient;
// クライアント領域の取得
GetClientRect(&cClient);
// 画面消去(グラフだけの画面を表示)
BitBlt(m_MemDC, 0, 0, cClient.Width(), cClient.Height(), NULL, 0, 0, SRCCOPY);
CDC *pDC; // デバイスコンテキストを格納するポインタ
pBitmap = new CBitmap(); // ビットマップオブジェクトの動的確保
// 現在のDCのポインタを取得
pDC = GetDC();
// 通常のDCとメモリDCに互換性をつける
m_tmpMemDC.CreateCompatibleDC(pDC);
m_tmpMemDC.SelectObject(pBitmap);
// 通常のDCの互換性のあるビットマップをメモリ上に作成
pBitmap->CreateCompatibleBitmap(pDC, cClient.Width(), cClient.Height());
// 垂線描画
m_tmpMemDC.PatBlt(0, 0, cClient.Width(), cClient.Height(), PATCOPY);
CPen pen(PS_SOLID, 0, RGB(255, 255, 255));
CPen* pOldPen = m_tmpMemDC.SelectObject(&pen);
m_tmpMemDC.MoveTo(CPoint(x_pos, cClient.top));
m_tmpMemDC.LineTo(CPoint(x_pos, cClient.bottom));
m_tmpMemDC.SelectObject(pOldPen);
// 画像出力
BitBlt(m_tmpMemDC, 0, 0, cClient.Width(), cClient.Height(), NULL, 0, 0,
SRCCOPY);
//後始末
ReleaseDC(pDC);
m_tmpMemDC.DeleteDC();
delete m_tmpMemDC;
DeleteObject(pBitmap);
}
単純な線であれば描画モードをR2_NOTXORPEN
にするだけで良いのではないでしょうか?
イメージ的にはこんな感じで。
OnMouseMove()
{
pDC->SetROP2(R2_NOTXORPEN);
前回の線をMoveTo~LineTo
今回の線をMoveTo~LineTo
今回の線を前回の線へ保存
}
間違ってたらすいません
FUKUさんお世話になります、その方法も下記のような感じで試していたのですが
前回の線が残ってしまいます(^^;
う~ん分からん??
また、メモリデバイスコンテキストによる再描画の方法も、脳内では正しいと思うのです
が
思い通りにならないしww
m_tmpMemDCにインスタンスが取得できていないのではと疑い
// 通常のDCの互換性のあるビットマップをメモリ上に作成
pBitmap->CreateCompatibleBitmap(pDC, cClient.Width(), cClient.Height());
if (m_tmpMemDC.GetSafeHdc() != NULL) { // ←これを追加
・・・・・
以下同様
この様に追加して確認したところ、m_tmpMemDCには値が取れているので良いと思うのです
がなぜなんだろう?
後、疑うのは
m_tmpMemDC.PatBlt(0, 0, cClient.Width(), cClient.Height(), PATCOPY);
ここでビットマップをm_tmpMemDCにコピーしているつもりなんですが、間違ってないでし
ょうか?
//以下FUKUさんにご教示して頂いたことを参考に実装しました
public:
void SetNewLinePos(int newline){m_newLine = newline;};
void SetOldLinePos(int oldline){m_oldLine = oldline;};
int GetNewLinePos(){return m_newLine;};
int GetOldLinePos(){return m_oldLine;};
private:
inr m_newLine;
int m_oldLine;
void CLineChartCtrl::DrawVerticalLine(int x_pos)
{
CClientDC dc(this);
// クライアント領域
CRect cClient;
// クライアント領域の取得
GetClientRect(&cClient);
CDC *pDC = this->GetDC(); // デバイスコンテキストを格納するポインタ
pDC->SetROP2(R2_NOTXORPEN);
// 古い線と同じ座標に描画
CPen pen(PS_SOLID, 0, RGB(0, 255, 255));
CPen* pOldPen = m_MemDC.SelectObject(&pen);
m_MemDC.MoveTo(CPoint(GetOldLinePos(), cClient.top));
m_MemDC.LineTo(CPoint(GetOldLinePos(), cClient.bottom));
// 新しい座標に垂線描画
m_MemDC.MoveTo(CPoint(x_pos, cClient.top));
m_MemDC.LineTo(CPoint(x_pos, cClient.bottom));
m_MemDC.SelectObject(pOldPen);
//今回の座標を保存
SetOldLinePos(x_pos);
}
SetROP2は描画するDCに対して行います。
こちらで書いたテストコードを示しておきます。
int m_X = -1;
void CChildView::OnMouseMove(UINT nFlags, CPoint point)
{
if (GetCapture() != this) return;
RECT rr;
this->GetClientRect(&rr);
CDC* pDC = this->GetDC();
CPen pen(PS_SOLID, 1, RGB(0,0,255));
CPen* pOldPen;
int iOldMode;
pOldPen = pDC->SelectObject(&pen);
iOldMode = pDC->SetROP2(R2_NOTXORPEN);
// 前回の線を消す
if (m_X >=0) {
pDC->MoveTo(m_X, 0);
pDC->LineTo(m_X, rr.bottom);
}
// 今回の線を描画する
pDC->MoveTo(point.x, 0);
pDC->LineTo(point.x, rr.bottom);
// 今回の線を保存しておく
m_X = point.x;
pDC->SetROP2(iOldMode);
pDC->SelectObject(pOldPen);
CWnd::OnMouseMove(nFlags, point);
}
あと
OnPaint()内でグラフ描画
OnLButtonDown()内で SetCapture()
OnLButtonUp()内でReleaseCapture()
消したり書いたりってことですね。
こんな方法は如何?。以下ソースイメージで
crass xxxx
{
protected:
CBitmap m_Graph ;
}
//グラフだけ描画
void xxxx::drawGraph( CDC *pDC )
{
//記憶用ビットマップ
m_Graph.CreateCompatibleBitmap( pDC , cx , cy ) ;
//画面DCへ選択
pDC->SelectObject ( &m_Graph ) ;
//ここでグラフ書く?書かないだっけ?まぁいいや
・・・・・・・
//画面DCへ描画
pDC->BitBlt( 0 , 0 , cx , cy , &memDC , cx , cy ) ;
}
//グラフ上の古い縦線を消し、新しい位置に縦線を引く
void xxxx::mouseClick( CDC *pDC , int newX , int newY , int oldX , int oldY )
{
//メモリDCへ縦線の無いグラフ描画
CDC memDC ;
memDC.CreateCompatibleDC( pDC ) ;
pDC->SelectObject ( &m_Graph ) ;
//古い縦線の在る付近を画面へ。(=消す)
pDC->BitBlt( oldX , oldY , cx , cy , &memDC , srcX , srcY ) ;
//新しい縦線を描画
pDC->MoveTo( ・・・
pDC->LinTo( ・・・
}
あまりイイ方法じゃないな・・・
間違えてるしorz
//グラフ上の古い縦線を消し、新しい位置に縦線を引く
void xxxx::mouseClick( CDC *pDC , int newX , int newY , int oldX , int oldY )
{
//メモリDCへ縦線の無いグラフ描画
CDC memDC ;
memDC.CreateCompatibleDC( pDC ) ;
pDC->SelectObject ( &m_Graph ) ; ×
memDC.SelectObject ( &m_Graph ) ; ○
あと、各関数の出入り口にSaveDC、RestorDC必須です
FUKUさん、菟ーお世話になります、ソイレントグリーンです
FUKUさんにご教示して頂いたコードを試して見ましたところ
目的にかなう動作となりました。
// 今回の線を保存しておく
m_X = point.x;
pDC->SetROP2(iOldMode);
pDC->SelectObject(pOldPen);
CWnd::OnMouseMove(nFlags, point);
ReleaseDC(pDC); //これを追加しないと何度もこの関数を実行するとリソース不足と
怒られます。
菟ーさんどうもダブルバッファーによるご説明ありがとうございます、BitBitによるグ
ラフの描画が巧く出来なくて何日も半ば意地になっていましたww
菟ーさんの方法で実装して検証してみたいと思います。
ごめんなさい、遅くなりました
解決とさせていただきます、たいちうさん、FUKUさん、菟ーさんお世話になりまし
た。