我正在尝试使用MFC制作交互式图形,其中可以使用鼠标单击更改图形中采样点的y轴。我使用本教程实现了双缓冲 enter link description here。我还应该指出,我需要不时地为我的程序更改视口的来源。但是,当我点击要更新的采样点的图表时,我仍然可以看到它闪烁。这不是一个不方便,但我需要扩展这个图表,以包括大量的采样点和其他功能,如网格线,轴,标签,边界区域等,我担心闪烁可能会成为我将来的问题,因为这个项目的规模在增长。实现双缓冲似乎没有对输出进行任何更改。此外,现在我已经实现了双缓冲,程序似乎在执行过程中停止(当我在Visual Studio中以调试模式运行它时),并出现此错误:
Unhandled exception at 0xffffff3a in graph_on_dlgbox.exe: 0xC0000005: Access violation reading location 0xffffff3a.
我仍然不确定是什么原因导致它出现,但如果我开始随机快速点击图形区域,似乎就会发生。由于我在我的代码中没有看到这个错误(还没有使用双缓冲),我假设它与双缓冲代码有关,但我不确定。
无论如何,我想一次解决这个问题,第一个问题是闪烁。这是我没有双缓冲的代码:
void Cgraph_on_dlgboxDlg::OnPaint()
{
CPaintDC dc(this);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
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;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// Dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the clinet origin
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
}
这是带有双缓冲的修改版本:
void Cgraph_on_dlgboxDlg::OnPaint()
{
// /*****
CPaintDC dc_blt(this);
CDC dc;
CBitmap bmpDC;
// CRect rcClient;
// GetClientRect(rcClient);
if (IsIconic())
{
// CPaintDC dc(this); // device context for painting
SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);
// Center icon in client rectangle
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;
// Draw the icon
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}
dc.CreateCompatibleDC(&dc_blt);
// bmpDC.CreateCompatibleBitmap(&dc_blt, rcClient.Width(),rcClient.Height());
bmpDC.CreateCompatibleBitmap(&dc_blt, theGraph.width,theGraph.height );
dc.SelectObject(&bmpDC);
// ----------- After this point, do all operations considering (0,0) to be the origin of the bitmap
// consider bitmap coordinates a device coordinates for Viewport operations
CPen pen;
COLORREF pencolour = RGB(0, 0, 0);
COLORREF brushcolour = RGB(0, 0, 255);
COLORREF graphColour = RGB(0, 0, 150);
// Draw boarder
pen.CreatePen(PS_SOLID, 3, pencolour);
// CBrush brush(HS_CROSS, brushcolour);
dc.SetBkMode(TRANSPARENT);
dc.SetMapMode(MM_TEXT);
// dc.SetViewportOrg(theGraph.x1, theGraph.y1);
// dc.SetViewportExt(theGraph.width, theGraph.height);
dc.SelectObject(&pen);
// dc.SelectObject(&brush);
// Draw graph boundary
CPoint point1(0,0);
point1.x = 0;
point1.y = 0;
CPoint point2(0,0);
point2.x = point1.x + theGraph.width;
point2.y = point1.y + theGraph.height;
dc.Rectangle(CRect(point1, point2));
pen.DeleteObject();
// Draw Horizontal at 0
pen.CreatePen(PS_SOLID, 1, pencolour);
dc.SelectObject(&pen);
dc.MoveTo(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
dc.LineTo(theGraph.width, theGraph.height - ORG_DIST_FROM_BOTTOM);
pen.DeleteObject();
// Shift overall graph origin from top left corner to beginning of this horizontal line
// dc.SetViewportOrg(theGraph.x1, theGraph.y1 + theGraph.height - ORG_DIST_FROM_BOTTOM); // dc.SetViewportOrg() always works relative to the client area origin
// New origin defined in terms of the Bitmap's origin
dc.SetViewportOrg(0, theGraph.height - ORG_DIST_FROM_BOTTOM);
// Draw graph line
pen.CreatePen(PS_SOLID, 2, graphColour);
dc.SelectObject(&pen);
for(int i = 0; i<NUM_OF_SECTIONS_IN_GRAPH; i++){
dc.MoveTo(graphSamplePoints[i].x, graphSamplePoints[i].y);
dc.LineTo(graphSamplePoints[i+1].x, graphSamplePoints[i+1].y);
}
// draw circles at graph sample points
for(int i = 0; i<NUM_OF_POINTS_IN_GRAPH; i++){
CIRCLE(dc, graphSamplePoints[i].x, graphSamplePoints[i].y, GRP_SMP_RAD);
}
dc.SetViewportOrg(0, 0);
// dc_blt.BitBlt(rcClient.left+100,rcClient.top+50,rcClient.Width(), rcClient.Height(), &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(0,0,rcClient.Width(), rcClient.Height(), &dc, theGraph.x1, theGraph.y1, SRCCOPY);
dc_blt.BitBlt(theGraph.x1,theGraph.y1, theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// --- Bring the bitmap to this particular location on screen specified by (theGraph.x1,theGraph.y1, theGraph.width, theGraph.height)
// dc_blt.BitBlt(0,0,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// dc_blt.BitBlt(theGraph.x1,theGraph.y1,theGraph.width, theGraph.height, &dc, 0, 0, SRCCOPY);
// *****/
m_bMyDraw = FALSE;
}
以下是输出的示例屏幕截图:
可以通过单击更改图表上样本点的y轴值,并且程序在每次单击后重新绘制图形,方法是调用InvalidateRect()
,图形区域为要重新绘制的矩形。登记/>
样本点的坐标存储在CPoint对象数组中,每次在适当的区域单击图形时,它的成员都会更新。然后由于调用InvalidateRect()
,图表重新绘制,但闪烁;当然,除非程序在调试模式下崩溃并出现此错误:
如何消除闪烁?
----更新----
BOOL Cgraph_on_dlgboxDlg::OnEraseBkgnd(CDC* pDC)
{
// TODO: Add your message handler code here and/or call default
if ( m_bMyDraw )
return TRUE;
else
return CDialogEx::OnEraseBkgnd(pDC);
}
这个功能是这样做的,因为它是在我之前提到的教程中这样做的
------更新2 ----------
如果我只是把它返回TRUE;在上述功能的主体中,闪烁似乎消失了,但现在输出看起来像这样
对话框背景似乎已采用我的Visual Studio窗口的内容。我该如何防止这种情况?
答案 0 :(得分:3)
你很亲密!双缓冲的想法是只在窗口中绘制一次像素。如果它被绘制为零次,则Visual Studio之类的工件仍然存在。如果它是第一次涂漆,然后再涂上不同的颜色,你会看到闪烁。因此,为了确保绘制每个像素,请在窗口的整个宽度和高度上创建兼容的dc,以便在复制到CPaintDC
时,它覆盖整个区域,而不仅仅是theGraph
。继续在TRUE
中返回OnEraseBkgnd
,以便首先在OnEraseBkgnd
中绘制像素,然后再在OnPaint
中绘制。
答案 1 :(得分:2)
两件事:
您是否确定OnEraseBkgnd()只返回TRUE并且不调用基类来删除视图?
在OnPaint()中,您不需要为双缓冲做任何绘图。你需要在OnPaint()中做的就是BitBlt。您可以在UpdateRect()函数中绘制到内存位图,只要您需要更新屏幕就会调用它,然后调用InvalidateRect()来更新屏幕。我发布了一些关于无闪烁双缓冲方法的代码,我已多次使用here这可能会有所帮助。
答案 2 :(得分:2)
闪烁防止工作的方式是首先从OnEraseBkgnd返回TRUE以禁止默认擦除。但是,您的OnPaint代码必须包含完整的窗口擦除。你不这样做,所以你得到你的源代码的背景图像或之前的任何东西。因此,向OnPaint添加FillSolidRect调用以清除窗口。
在调用CDialogEx :: OnPaint之前创建CPaintDC会破坏对话框正确绘制自身的能力,因为该函数还会创建CPaintDC。但是每个绘制消息只允许对CPaintDC进行一次调用。要避免这个问题,您需要一种完全不同的方法。对话框上应该有一个图片控件(CStatic),你应该在你从CStatic派生的类中绘制你的图形。