Verliere beim Zeichnen das Handle zum parent window

Cappaja

Erfahrenes Mitglied
Hallo,

ich sitze wahrscheinlich schon zu lange davor und verstehe nicht mehr den Zusammenhang. Ich habe eine dialogbasierende Anwendung CWiwoDlg als Main-Klasse. Nebenbei habe ich in einer Klasse CGraph für eine Zeichenroutine eines Cursors, welcher bei LBUTTONDOWN über den jeweilige CPOINT point.x in X-Richtung gesetzt wird. Beim Aufruf aus der Main-Klasse findet er an rotmarkierter Stelle nicht mehr das Handle vom Typ CWnd *m_pWnd und springt beim debuggen genau an dieser Stelle heraus. Weiß jemand woran das liegen könnte?

Codeausschnitt aus CWiwoDlg.cpp
Code:
void CWiwoDlg::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CGraph pGraph;
	CRect rect = pGraph.CalcDataArea();
	BOOL bFlag = CheckRect(point, rect);

	if(bFlag == TRUE)
	{
		int nCX, nCY;
		nCX = point.x;
		nCY = point.y;
		CString sCX, sCY;
		sCX.Format("%d", nCX);
		sCY.Format("%d", nCY);
		m_ctrlCX.SetWindowText(sCX);
		m_ctrlCY.SetWindowText(sCY);
		pGraph.DrawCursor(nCX);
	}
	else
		TRACE("\nCX und CY liegen ausserhalb!\n");
	
	CDialog::OnLButtonDown(nFlags, point);
}

BOOL CWiwoDlg::CheckRect(CPoint point, CRect rect)
{
	if(point.x < rect.left)   return FALSE;
	if(point.x > rect.right)  return FALSE;
	if(point.y > rect.bottom) return FALSE;
	if(point.y < rect.top)    return FALSE;
	return TRUE;
}

Codeausschnitt aus CGraph.cpp
Code:
void CGraph::DrawCursor(int nCX)
{
	try
	{
		if(!m_pWnd)
			return;

		CPen *pStandardPen;
		CPen MyPen;
		CRect DataArea = CalcDataArea();

		// DC zum Zeichnen anlegen
		CDC *pDC = m_pWnd->GetDC();
		MyPen.CreatePen(PS_SOLID, 1, RGB(255,255,255));
		// MyPen als Standard Pen an pDC übergeben
		pStandardPen = pDC->SelectObject(&MyPen);

		// ### TODO Zeichenroutine für Cursor ###
		pDC->MoveTo(/*m_iGraphWidth / 2*/nCX, DataArea.top);
		pDC->LineTo(/*m_iGraphWidth / 2*/nCX, DataArea.bottom);

		// CleanUp
		m_pWnd->ReleaseDC(pDC);
	}
    catch(CResourceException &CEx)
    {
        char msg[128];
        CEx.GetErrorMessage(msg, sizeof(msg));
        MessageBox(NULL, msg, "Fehler", MB_ICONERROR);
    }
}
 
Moin,

wo ist denn "m_pWnd" deklariert ? ? ? :confused:

Zeig uns mal mehr Code ......

Vermutlich solltest Du den Zeiger darauf irgendwie über die aufrufende Funktion "OnLButtonDown" durchreichen (ggf. per this).

Gruß
Klaus
 
Abgesehen davon was Klaus schon geschrieben hat: Zeichenoperation sollten immer im OnPaint-Handler ausgeführt werden, weil sie sonst schnell wieder überbügelt werden.
Bei OnLButtonDown() legst du nur fest was gezeichnet werden soll (etwa mit Hilfe von Member-Variablen) und rufst dann lediglich Invalidate() auf. Dadurch wird automatisch OnPaint() aufgerufen. Dort bekommst du einen Gerätekontext, den du als Parameter an die DrawCursor()-Methode übergeben kannst. Das Fensterhandle brauchst du dann eigentlich nicht mehr.

Gruß
MCoder
 
also erstmal danke.
m_pWnd ist in CGraph.h deklariert.
Das ganze ist eine fertige Klasse CGraph welche ich von Codeguru übernommen und eingebunden habe. In dieser Klasse exisitert die Methode PaintGraph() welche ich in der OnPaint() der Mainklasse über eine Membervariable auch ausführe. Innerhalb der PaintGraph() habe ich zuerst auch meine Methode DrawCursor ausgeführt was funktionierte. Jetzt wollte ich es aber noch nachrichtenorientiert gestalten und weiß nicht wie ich mit den MFC da am besten vorgehe.

Ich muss der Methode DrawCursor eben unbedingt die CPoint Koordinate nCX mitgeben damit er innerhalb des gültigen DataAreas in X-Richtung verschoben werden kann. dazu brauche ich doch die mausnachricht. ich wüsste momentan nicht wie ich das anders gestalten könnte. an der rotmarkierten Stelle im unteren Code müsste anstelle eines statischen Werts nCX des linken Mausklicks übergeben, aber wie?

ich habe ja bereits einen Zeiger auf die Klasse CGraph, andernfalls müsste ich auf die Klassen gegenseitig verweisen, was man doch tunlichst vermeiden sollte und zu compilerfehlern führt. sonst könnte ich ja einfach das Ergebnis von nCX in eine Membervariable schreiben und diese dem Übergabeparameter von DrawCursor() in PaintGraph() zuweisen.

hier der code von PaintGraph()
Code:
//////////////////////// PaintGraph //////////////////////////////////////
/*
	This paints the entire graph on to the holding window's
	client area;
	It does it in steps starting from the background and working forward.
	As the graph is NOT a window object in it's own right, it uses the
	display context of the holding window. If it has not been given a 
	pointer to the holding window, it will not paint.
	Any CGraph routine that paints to the screen, checks the window pointer
	first.
	The last thing to be painted is the plotting of the function data (if any)
*/
///////////////////////////////////////////////////////////////////////////
void CGraph::PaintGraph()
{
	//here we draw the graph
	//step 1: Draw the surrounding rectangle
	//for the whole graph
	if (m_pWnd==NULL)
	{
		return;
	}
	CRect rect;
	CPen pen, *oldpen;
	CDC *dc=m_pWnd->GetDC();
	//some useful calculations
	UINT lmargin=CalcLeftMargin();
	UINT rmargin=CalcRightMargin();
	UINT bmargin=CalcBottomMargin();
	UINT tmargin=CalcTopMargin();
	UINT Graphbottom=m_iGraphY+m_iGraphHeight;
	UINT Graphright=m_iGraphX+m_iGraphWidth;

	//step 2: color the background
	CBrush brush,*poldbrush;
	brush.CreateSolidBrush(m_crGraphBkColor);
	pen.CreatePen(PS_SOLID,1,m_crGraphBkColor);
	rect.left=m_iGraphX;
	rect.right =rect.left+m_iGraphWidth;
	rect.top=m_iGraphY;
	rect.bottom=rect.top+m_iGraphHeight;
	oldpen=dc->SelectObject(&pen);
	poldbrush=dc->SelectObject(&brush);
	dc->Rectangle(&rect);
	dc->SelectObject(oldpen);
	dc->SelectObject(poldbrush);
	pen.Detach();
	
	//step 3: Draw Grid if required
	DrawGrid();
	
	//step 4: Draw Axies
	//draw x-axis
	dc->MoveTo(m_iGraphX+lmargin,Graphbottom-m_iOriginY);
	pen.CreatePen(PS_SOLID,1,m_crXTickColor);
	oldpen=dc->SelectObject(&pen);
	dc->LineTo(Graphright-rmargin,Graphbottom-m_iOriginY);
	dc->SelectObject(oldpen);
	pen.Detach();

	//draw the Y Axis
	pen.CreatePen(PS_SOLID,1,m_crXTickColor);
	oldpen=dc->SelectObject(&pen);
	if (!m_bYLineAtLeft)
	{
		//draw the Y Line so that it intercepts
		//the x-line like crosshairs
		dc->MoveTo(m_iGraphX+m_iOriginX,m_iGraphY+tmargin);
		dc->LineTo(m_iGraphX+m_iOriginX,Graphbottom-bmargin);
	}
	else
	{
		//draw the Y Line at the LHS
		dc->MoveTo(m_iGraphX+lmargin,m_iGraphY+tmargin);
		dc->LineTo(m_iGraphX+lmargin,Graphbottom-bmargin);
	}
	dc->SelectObject(oldpen);
	pen.Detach();

	//step 5: draw ticks if required
	DrawTicks();

	//step 5.1: draw cursor
	//DrawCursor(0);

	//step 6: Write Graph title
	DrawGraphTitle();

	//step 7: Write Function name
	DrawFunctionName();

	//step 8: Write x-legend
	DrawXLegend();

	//step 9: Write y legend
	//doing this is very similar to doing the graph title or the
	//x-legend but the Y axis is either at the LHS or set to match the
	//x-origin
	DrawYLegend();

	//step 10: write the x & y axes values
	DrawXAxisNumbers();
	DrawYAxisNumbers();

	//Step 11
	//draw Function
	DrawFunction();

	//Cleanup
	m_pWnd->ReleaseDC(dc);
}
 
Zuletzt bearbeitet:
Hallo,

wie gesagt, ich halte es für keine gute Lösung, einen DeviceKontext zum Zeichnen auf einer Fensteroberfläche extra zu generieren, statt den in OnPaint erzeugten zu verwenden.
Zunächst würde ich der Methode "PaintGraph" einen Parameter für den DeviceKontext geben:
C++:
void CGraph::PaintGraph(CDC *dc)
{
    // ...
}

...

void CWiwoDlg::OnPaint() 
{
	CPaintDC dc(this);
    pGraph.PaintGraph(&dc);
}
Falls die anderen Zeichenmethoden (DrawTicks usw.) den CDC auf ähnliche Weise erzeugen, sollte der Parameter dort auch ergänzt werden. Die Membervariable "m_pWnd" kann dann entfallen.
Statt dessen könntest du innerhalb von CGraph eine Member-Variable für die Koordinate deklarieren und beim Button-Klick entsprechend belegen. Ebenso sollte das CGraph-Objekt als Member von CWiwoDlg deklariert werden:
C++:
// im CGraph Header
public:
    int m_nCX;

// im CWiwoDlg Header
public:
    CGraph m_Graph;

// in PaintGraph
DrawCursor(m_nCX);

void CWiwoDlg::OnLButtonDown(UINT nFlags, CPoint point) 
{
	CRect rect = m_Graph.CalcDataArea();
	BOOL bFlag = CheckRect(point, rect);

	if(bFlag == TRUE)
	{
		int nCX, nCY;
		nCX = point.x;
		nCY = point.y;
		CString sCX, sCY;
		sCX.Format("%d", nCX);
		sCY.Format("%d", nCY);
		m_ctrlCX.SetWindowText(sCX);
		m_ctrlCY.SetWindowText(sCY);
		
        m_Graph.m_nCX = nCX; // Koordinate belegen
        Invalidate(); // Aufruf von OnPaint auslösen
	}
	
	CDialog::OnLButtonDown(nFlags, point);
}
Gruß
MCoder
 
Hallo MCoder

danke für deinen ratschlag. nachdem ich alles so hatte war mein graph komplett verschwunden. es sind 10 methoden die ebenfalls ein CDC objekt anlegen und in wiederum vielen weiteren methoden aufgerufen werden, es wurde ein durcheinander wobei am ende das fenster auf der strecke blieb.

ich habe jetzt mal mein backup gestartet und deinen befehl in zeile 28 übernommen. das war es was ich ursprünglich gesucht hatte. so rufe ich die methode DrawCursor() nicht explizit auf sondern übergebe sie mit der PaintGraph() in der OnPaint(). Jetzt bekomme ich zwar keine Fehlermeldungen mehr, allerdings sehe ich auch nirgends meinen Cursor. Oder liegt der Fehler in der Paint-Methode? allerdings hab ich hier nur an einer zeile selbst hand angelegt:

Code:
void CWiwoDlg::OnPaint() 
{
	if(IsIconic())
	{
		CPaintDC dc(this); // Gerätekontext für Zeichnen

		SendMessage(WM_ICONERASEBKGND, (WPARAM) dc.GetSafeHdc(), 0);

		// Symbol in Client-Rechteck zentrieren
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width()  - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Symbol zeichnen
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
		m_pGraph->PaintGraph();
	}
}
 
Zuletzt bearbeitet:
hi mcoder,

habe das problem gelöst: in zeile 28 sollte der Inhalt mittels m_pGraph->m_nCX = nCX; übergeben werden. so stand zwar kurzzeitig der richtige wert drin, wurde aber nicht in die klasse CGraph übernommen sodass immer der grenzwert von int übergeben wurde.

nebenbei noch ne kleine frage: wäre es nicht effizienter anstelle von invalidate() nur m_pGraph->PaintGraph() aufzurufen? so wird lediglich der Graph anstelle des kompletten Fensters neu gezeichnet oder täusche ich mich da? zumindest erhalte ich auf diese weise kein flackern bei mehrmaligem aktualisieren.
 
Zuletzt bearbeitet:
nebenbei noch ne kleine frage: wäre es nicht effizienter anstelle von invalidate() nur m_pGraph->PaintGraph() aufzurufen? so wird lediglich der Graph anstelle des kompletten Fensters neu gezeichnet oder täusche ich mich da? zumindest erhalte ich auf diese weise kein flackern bei mehrmaligem aktualisieren.
Wenn es besser funktioniert, mache es so. Ich habe mir die Funktionsweise der CGraph-Klasse nicht genauer angeschaut, so dass ich auch nicht viel zur optimalen Verwendung sagen kann. Meine Tipps beruhten auf meinen bisherigen Erfahrung mit der GDI-Programmierung, die aber im konkreten Fall nicht unbedingt passen müssen.

Gruß
MCoder
 

Neue Beiträge

Zurück