Pony279 发表于 2012-1-16 18:20:38

求助:MFC 在 ONLbuttonUp 中画图显示迟钝。。。

这个问题在网上搜了很久,找到同类的问题但是没有看到解决方案,所以就到坛子里发个贴问问,
这里有几个我看过的相关问题的链接:
http://www.cppblog.com/aiversonwk/archive/2010/12/13/136342.aspx?opt=admin    //一开始还以为他的方法可以了,试了不行
http://topic.csdn.net/u/20101224/13/f0cb14a0-4ea2-492e-bbb1-cfc0ba0d1c01.html   //这个帖子上说的问题和我的是一样的,没看到好的解决办法
http://www.uipower.com/bbs/thread-169152-1-1.html//类似

现在说明问题:
最近在看孙鑫老师 Visual C++ 的视频
第 10 和 第 11集(讲绘图的)

有一段代码是在鼠标按下后记录坐标,键释放后,
在消息处理函数 ONLbuttonUp 中,根据 按下的坐标 和 鼠标释放时的坐标 画出一条直线

问题现象:看视频里的运行效果,完全没有问题,可是一样的代码,在我这里完全没有改动,运行的时候发现,直线会延迟显示,延迟多久说不准

我的测试:

1. 如果直接 Invalidate() 的话,经过 OnDraw 函数 就可以正常显示了(前提是要显示的东西在OnDraw也有相关的代码)

也就是说 OnDraw 里画图就一定可以显示

2. 我在 OnLbuttonUp里 dc.Moveto dc.Lineto 完后,调用一个MessageBox(_T("Graph drawn!")
   运行程序,显示不会有延时,就是会有个消息框,不爽,
   如果在OnDraw加断点,程序没跳进去,说明直线不是OnDraw画的
   上张图好了,不是动画。。。好像没什么意义。。。
http://cache.amobbs.com/bbs_upload782111/files_50/ourdev_713382SYSKVZ.jpg
(原文件名:1.jpg)
   
3. 我画完后,把鼠标移动到菜单栏,画的线也立刻显示


解决方案的讨论:

有人说画完后 Invalidate,我认为这样会比较麻烦,因为,如果我做的是一个复杂的画图程序,难道我每画一个小Object都要重新画所有的Objects吗?

当然,如果只是Invalidate 一小片区域可以比较好地解决这个问题,或者在OnDraw里面弄个状态机(因为测试发现只有OnDraw里面画的才会立刻显示嘛)

但是这些方案都不令人满意,所以希望大家帮帮忙,解决下这个问题




这里是代码:ourdev_713383A6BIG8.zip(文件大小:86K) (原文件名:Graphic.zip)(11课的)

代码里View类的OnLbuttonUp做的主要工作就是:
CClientDC dc(this);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

Pony279 发表于 2012-1-16 18:25:27

问题已经说得很清楚了,

希望大家帮下忙吧./emotion/em010.gif

Pony279 发表于 2012-1-16 18:52:53

现在又不会了。。。今天这个程序时一段时间会有问题,一段时间没有
唉。。。我也没改什么代码。。。
感觉MFC隐藏了好多东西,搞得一个程序的问题比硬件的问题还神秘。。。
看了文档,也没找到原因。。。

Pony279 发表于 2012-1-16 18:54:10

画完后这样也是可以的,这样会让OnDraw出马,又只画一小块区域
InvalidateRect(CRect(point, m_ptOrigin),0);

Pony279 发表于 2012-1-16 18:57:07

如果在OnLButtonUp画完后使用的是Invalidate()的话,每画一个object,就会闪一下,比较烦人

不过感觉要用到InvalidateRect(CRect(point, m_ptOrigin),0); 让OnDraw出来做,效率还是比较低

Pony279 发表于 2012-1-16 20:42:33

唉。。。又发作了。。。
我压根就没改代码

xivisi 发表于 2012-1-16 21:04:08

双缓冲效果好得多 也不闪表示画一幅 1280*800 的图片也OK

xivisi 发表于 2012-1-16 21:14:10

另外
代码里View类的OnLbuttonUp做的主要工作就是:
CClientDC dc(this);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);

直接这样做 相当于内存中画了线但是显存中还没有(假设显存的内容直接呈现到显示器) 必须强制刷新一下 Invalidate()

“每画一个object,就会闪一下”:闪 请用双缓冲


“效率还是比较低” 还是双缓冲只不过之前画的内容不要再刷到窗口上(我的理解是更新到显存)之后就释放掉,新的object就画上去就是,你甚至可以缓存N个历史 当后悔药呢(另一个效率高的就是缓存object做后悔药)

Pony279 发表于 2012-1-16 21:40:17

回复【7楼】xivisi LiYong
-----------------------------------------------------------------------

"直接这样做 相当于内存中画了线但是显存中还没有(假设显存的内容直接呈现到显示器) 必须强制刷新一下 Invalidate() "


这样是可以,但是Invalidate的作用就是让MFC调用OnDraw
就是说不能在OnDraw以外直接画图了吗?



双缓冲我还不懂,研究研究
谢谢你了!

xivisi 发表于 2012-1-16 21:46:22

回复【8楼】Pony279 霍斯
回复【7楼】xivisi liyong
-----------------------------------------------------------------------
"直接这样做 相当于内存中画了线但是显存中还没有(假设显存的内容直接呈现到显示器) 必须强制刷新一下 invalidate() "
这样是可以,但是invalidate的作用就是让mfc调用ondraw
就是说不能在ondraw以外直接画图了吗?
双缓冲我还不懂,研究研究
谢谢你了!
-----------------------------------------------------------------------

你不放 ondraw 的话第一次刷新(windows控制)你的线显示了再刷新一次你的线就没有了   比如说 拿个记事本啥的窗口在你的程序上晃两下(这时候系统自动发送重绘消息,不发送那界面上留的画面就是记事本的样子,你可以拦截该消息试试) 你的画的线就全没了

118139 发表于 2012-1-16 22:03:03

你要看到实时的画线结果都是用 OnMouseMove 来做的
在Move中保存上一个坐标,获取当前坐标,再两坐标画线

ondraw是用来当窗体被遮挡时,系统调用ondraw() 自动重画窗口区
没被其它东西遮挡时,不自动调用这东西

所以在
ondraw()中需要把你刚才所画的东西的所有动作,

再重新运行再画一次

Pony279 发表于 2012-1-16 22:03:45

回复【9楼】xivisi LiYong
-----------------------------------------------------------------------

嗯,
这个我明白
每次重画都调用OnDraw

“个记事本啥的窗口在你的程序上晃两下”
这个我试了,在OnDraw上加了断点,不过没有调用OnDraw
http://cache.amobbs.com/bbs_upload782111/files_50/ourdev_713401AV51ZE.jpg
可能是Windows7的画图机制和以前的版本不同?
如果resize或者滑动滚动条的话就会调用OnDraw

Pony279 发表于 2012-1-16 22:07:22

回复【10楼】118139
-----------------------------------------------------------------------

你要看到实时的画线结果都是用 OnMouseMove 来做的
在Move中保存上一个坐标,获取当前坐标,再两坐标画线


谢谢
以前跟着 《Beginning Visual C++ 2010》 写过一个那样的程序,
孙鑫老师的教程的程序比较简单罢了
我只是纳闷为什么同样的程序在孙鑫老师的视频里运行就正常,在我这里就不正常了。。。

118139 发表于 2012-1-16 22:09:31

win7,不清楚。。

Pony279 发表于 2012-1-16 22:10:57

回复【10楼】118139
-----------------------------------------------------------------------

ondraw是用来当窗体被遮挡时,系统调用ondraw() 自动重画窗口区



我这里测试的结果是,只有在resize或者是 滑动滚动条的时候才会调用OnDraw...(可能是Win7比较不同吧。。。)

daiqx 发表于 2012-1-16 22:11:50

在window的机制本来刷新就要重画所有的对象。分别是框架已做好还是要编程做。你最深入点就会明白。

Pony279 发表于 2012-1-16 22:16:19

回复【15楼】daiqx
-----------------------------------------------------------------------

请问我现在只是想实时显示一个新添加的object,该怎么做?

Pony279 发表于 2012-1-16 22:40:16

回复【10楼】118139
-----------------------------------------------------------------------


翻了下以前照着那本书上抄的代码,用到了多态,m_pTempElement->Draw(&aDC); 这个调用的函数里面也没有用到Invalidate
持续纳闷中。。。估计是MFC在这方面相比以前的版本做了点修改吧, mouse move特别照顾,文档上也说了,大部分的画图都是在OnDraw里面完成的
“Nearly all drawing in your application occurs in the view's OnDraw member function, which you must override in your view class. (The exception is mouse drawing, discussed in Interpreting User Input Through a View.) ”(引自 vs2010 help -> Drawing in a View)
又或者是我的电脑有问题(话说网上也看到几个和我类似的问题。。。) ,

这个是那本书上的 在mouse move 里实时画图的函数(完整的程序在我的电脑上运行是没问题的)
void CtestMFC02View::OnMouseMove(UINT nFlags, CPoint point)
{
        // Define a Device Context object for the view
        CClientDC aDC(this);               // DC is for this view
        OnPrepareDC(&aDC);
        aDC.DPtoLP(&point);
        if(m_MoveMode)
        {
          MoveElement(aDC, point);      // Move the element
          return;
        }
        if((nFlags & MK_LBUTTON) && (this == GetCapture()))
        {
                m_SecondPoint = point;             // Save the current cursor position
                if(m_pTempElement)
                {
                        if(CURVE == GetDocument()->GetElementType())   // Is it a curve?
                        {// We are drawing a curve so add a segment to the existing curve
                                static_cast<CCurve*>(m_pTempElement)->AddSegment(m_SecondPoint);
                                m_pTempElement->Draw(&aDC);                        // Now draw it
                                return;                                                                // We are done
                        }
                        // If we get to here it's not a curve so
                        // redraw the old element so it disappears from the view
                        aDC.SetROP2(R2_NOTXORPEN);                        // Set the drawing mode
                        m_pTempElement->Draw(&aDC);                        // Redraw the old element
                        delete m_pTempElement;                                // Delete the old element
                        m_pTempElement = nullptr;                        // Reset the pointer
                }
                // Create a temporary element of the type and color that
                // is recorded in the document object, and draw it
                m_pTempElement = CreateElement();        // Create a new element
                m_pTempElement->Draw(&aDC);                        // Draw the element
        }
        else
        {
                CtestMFC02Doc* pDoc = GetDocument();
                CElement* pOldSelected(m_pSelected);
                m_pSelected = pDoc->FindElement(point);
                if(m_pSelected != pOldSelected)
                {
                        if(m_pSelected)
                                InvalidateRect(m_pSelected->GetBoundRect(), FALSE);
                        if(pOldSelected)
                                InvalidateRect(pOldSelected->GetBoundRect(), FALSE);
                        pDoc->UpdateAllViews(nullptr);
                }
        }
}

照着Ivor Horton的 <Beginning Visual C++ 2010> 的书上抄的程序代码ourdev_713415QW1IJN.zip(文件大小:3.10M) (原文件名:sketch.zip) ,事隔半年多了。忘得差不多了。
那时是急着想看完那本书,然后用临时学到的windows编程去完成了C++实验课的大作业(当时比较闲,大作业的要求没挑战性,就想自己加点东西进去~)

Pony279 发表于 2012-1-16 23:23:46

我郁闷的继续看视频

发现孙鑫老师讲到元文件(CMetaFileDC)的时候也出现了同样的现象了。。。

Pony279 发表于 2012-1-17 02:18:27

我现在使用了另一种的方法,不过还是有问题。。。

另建一个void DrawNewElement(CGraphicView* pGraphView, CDC* pDC ) 函数

然后用一个成员变量来判断是不是刚添加了新object,如果是
那么就在OnDraw里面就调用这个函数,调用完直接返回,不画全部的objects

在 OnLButton UP里面用
        InvalidateRect(CRect(point, m_ptOrigin),0);

        UpdateWindow();
来产生一个WM_PAINT消息

调试运行发现,还是会延迟,它还是会延迟!!!在OnDraw里面调用还是会迟钝!!!
更奇怪的是,如果我在DrawNewElement里面加一个断点,然后再调试,就可以立刻显示!!!

受不了了。。。

还有一个现象就是,鼠标移动到一个按钮之类的控件(就是移到上面显示状态会变的那种,包括菜单栏),然后之前画的东西就立刻显示了,而且这个显示不是因为调用了OnDraw。。。

有谁能解释下么。。。

118139 发表于 2012-1-17 02:52:18

编译成Relese版本
在OnDraw() 函数里
随便加个信息框
AfxMessageBox("ssss");
运行看有没有弹出来


你是用哪种编译器,是VC6.0还是 vc2010

Pony279 发表于 2012-1-17 12:08:06

回复【20楼】118139
-----------------------------------------------------------------------

会弹出来

我用的是vs2010
工程代码是孙鑫老师在VC6.0下编辑的

PS:楼上够晚睡的。。。

mored 发表于 2012-1-17 17:17:47

MFC的框架是要求用OnDraw显示内容的。
关于OnDraw的调用时机,可以参考WM_PAINT消息的生成和发送,他是在Windows认为需要重新绘制窗口,并且该窗口的队列里没有其他消息时才会到达该窗口。像鼠标移过、菜单的覆盖不会生成该消息,窗口的移动如果不导致部分窗口重新显现也不会有该消息。楼主说的用别的窗口晃两下,也不一定会产生这个消息。在Windows保存了窗口的图像的情况下,即使窗口从最小化、最大化恢复以及被别的窗口覆盖后重新显示都不一定产生这个消息。
MFC的Invalidate有几种形式:Invalidate,InvalidateRect,InvalidateRgn,第一种是无效整个窗口,第二、三种是无效局部区域。这三个函数都有个bool参数,用来指示是否擦除背景。
为防止闪烁,在OnDraw里,应该调用GetBoundsRect获得无效的区域,以减少重画的工作量;必要时禁止MFC擦除背景,而是在准备好数据后用背景色自行擦除。
另外也可以采用双缓冲方式。

jpchen 发表于 2012-1-17 20:44:32

为了兼顾效率和效果,建议如下:
1、建立一个MemDC,这在《VC技术内幕》书里有讲(孙鑫的视频我没看过,不知道里面有没有讲到MemDC的使用),它其实就是一个普通的CDC,记住需要将MemDC和一个Bitmap绑定(就是SelectObject),此bitmap需要足够大,能满足你绘图区域的要求。
2、你在OnLbuttonUp里的绘制动作都画到MemDC上面,这时其实就是画在了Bitmap上的,这时还没实际显示,只是显示内容已经准备在Bitmap上了。
3、在OnDraw里只需要简单地将MemDC的内容Bitblt到窗口的DC上即可,因为MemDC是和Bitmap绑定的,所以其实是将Bitmap里的内容Bitblt了出来。

这样之后,所有绘制object的动作只在OnLbuttonUp发生,在OnDraw里是不需要的,在你有很多Object时,就不会因为刷新而做很多重复动作。而在Ondraw里bitblt,就能保证窗口在被遮挡和刷新时能及时响应。

Pony279 发表于 2012-1-17 21:08:52

回复【22楼】mored
-----------------------------------------------------------------------

。。。木有解决我的问题

请问我19楼提到的问题是什么原因

Pony279 发表于 2012-1-17 21:18:58

回复【23楼】jpchen
-----------------------------------------------------------------------

谢谢你的建议,

视频里面也有讲到这种方式

不过我现在只是想解决19楼的问题,不明白,在OnDraw里面画图也会延迟。。。

(PS:感觉单纯的复制位图的效率不如矢量图的效率高,省空间。。。可能是我钻牛角尖了。。。)

mored 发表于 2012-1-17 22:26:48

回复【24楼】Pony279 霍斯
-----------------------------------------------------------------------
不知道你为什么有这种现象。我用VS2010在Win7下试验了下,没有任何明显的延迟,按键抬起就线就画出来了。
类CtestPaintView继承自CView;
在OnDraw里加入下面两行:
    pDC->MoveTo( first );
    pDC->LineTo( second );
两个鼠标按键响应函数内容:
void CtestPaintView::OnLButtonDown(UINT nFlags, CPoint point)
{
    first = point;
}
void CtestPaintView::OnLButtonUp(UINT nFlags, CPoint point)
{
    second = point;
    CRect r( first, second );
    InvalidateRect( r );
}

Pony279 发表于 2012-1-18 10:47:32

回复【26楼】mored
-----------------------------------------------------------------------

可能是我的电脑比较特别吧。。。

奇怪的是网上也有和我一样特别的,就是没找到原因。。。

可能是CDC dc;
dc.moveto...dc.lineto...
不是直接对显存操作的原因吧
然后我把鼠标移动到一个移上去状态会变的按钮的时候系统就更新缓存了,
另外,如果InvalidateRect后面的参数是true的话是会立刻更新的,不过这种方式所有的object得重画

Pony279 发表于 2012-1-18 14:43:43

一个更好的解决方法有了
只要我每画完一个object,就更新一下状态栏,就没问题了,下面的代码是在OnLButtonUp后面加的,
另外,我测试的时候是,如果count不++,那么只有第一次会实时显示,后面画的object,还是会延时
无所谓了,反正实时更新状态栏也是一个良好的人机接口
        CString str;
        static int count = 0;
        //str.Format("%d", count++);
        str.Format("%d", count);
        ((CMainFrame*)(this->GetParent()))->SetMessageText(str + _T("画图完毕!"));

daiqx 发表于 2012-1-18 20:07:24

vc都忘得差不多了,不过mfc是一种所谓mvc架构,是数据和显示分离。假设框架只会调用ondraw来输出显示,那么你可以怎样做?你将所有的变化都做在内存中,然后在ondraw中做一个重制的动作,将内存中的画面复制到的窗口客户端的dc。实际上就是重画了所有东西,不过速度快,看上去就是实时变化。你可以放下这个问题继续学下去,问题就不是问题。

zf8848 发表于 2012-1-18 21:20:47

win7 和 以前的 xp 对窗口的管理有些不同,如果要在 Win7 作图或画文字,建议采用 DirectDraw 和 DirectWrite.

Pony279 发表于 2012-1-18 21:51:38

回复【30楼】zf8848
-----------------------------------------------------------------------

谢谢!

Pony279 发表于 2012-1-18 22:06:53

回复【30楼】zf8848
-----------------------------------------------------------------------

我去帮助文档上看了下

Microsoft.DirectX.DirectDraw

Warning: Microsoft DirectDraw has been deprecated.. Deprecated components of Microsoft DirectX 9.0 for Managed Code are considered obsolete. While these components are still supported in this release of DirectX 9.0 for Managed Code, they may be removed in the future. When writing new applications, you should avoid using these deprecated components. When modifying existing applications, you are strongly encouraged to remove any dependency on these components.

这个是功能说明
DirectDraw enables you to directly manipulate display memory, the hardware blitter, hardware overlay support, and flipping surface support. The following tables list the members exposed by the Microsoft.DirectX.DirectDraw namespace.

貌似不太好啊。。。

xingyi7 发表于 2012-2-5 09:57:44

回复【楼主位】Pony279 霍斯
-----------------------------------------------------------------------

我遇到了跟你一样的问题,在Release模式下运行就没有延迟。我也想知道为什么?
另外,楼主的解决方法更新下状态栏,能再详细解释下为什么更新状态栏就OK啦?

xingyi7 发表于 2012-2-5 10:00:28

回复【28楼】Pony279 霍斯
-----------------------------------------------------------------------

哎呀,错了,也不一定Release模式下就反应不延迟,这个随机问题我还没解决?lz解决了详解下啊!

Pony279 发表于 2012-3-30 16:53:05

xingyi7 发表于 2012-2-5 09:57 static/image/common/back.gif
回复【楼主位】Pony279 霍斯
-----------------------------------------------------------------------



报歉,我现在才看到你的回复。

解决方案是试出来的,我也不会解释,最近没在搞windows编程,呵呵,你现在解决了没?分享下你的方案?
页: [1]
查看完整版本: 求助:MFC 在 ONLbuttonUp 中画图显示迟钝。。。