Chinaunix首页 | 论坛 | 博客
  • 博客访问: 495059
  • 博文数量: 96
  • 博客积分: 6046
  • 博客等级: 准将
  • 技术积分: 908
  • 用 户 组: 普通用户
  • 注册时间: 2006-03-07 22:40
文章分类

全部博文(96)

文章存档

2009年(12)

2008年(18)

2007年(45)

2006年(21)

我的朋友

分类: C/C++

2007-05-23 09:12:58

关于窗口重画的初级问题

既然是初级问题,我尽量少说一点原理,并且使用通俗易懂的话。

 

初初级问题:

我在视图画的图象或者文字,当窗口改变后(包括最小化后还原,被别的窗口挡住后重新显示等)为什么不见了?

 

这就是窗口重绘或者说重画的问题。当窗口改变后,会产生无效区域,这个无效的区域需要重画。什么是无效区域?自己到网上搜索或者看相关资料。我这里给出一个特殊的解释:以最小化后还原为例,假设只有一个程序在运行,窗口最小化时显示计算机桌面,并不妨假设桌面是蓝色的背景,当窗口还原时,窗口所占的这一块区域该显示些什么东西呢?操作系统并不知道,因此,就形成一块无效区域。要是我们告诉操作系统,显示一个红方块,于是它就显示一个红方块,我们的程序过一会想显示一个绿方块呢?同样也要告诉它。在哪里告诉它呢?在OnDrawOnPaint函数这里(这里不准备讨论他们的区别,并且只关注OnDraw)。OnDraw函数在什么时候执行呢?在存在无效区域窗口需要重绘时执行。

 

下面开始做例子(为简化程序,不显示方块显示线条),建立一个SDI工程,并添加一个菜单项DrawLine,添加OnDrawline函数:

显示线条A(20,20,50,50)

void CDrawView::OnDrawline()

{

       // TODO: Add your command handler code here

       CClientDC dc(this);

       dc.MoveTo(20,20);

       dc.LineTo(50,50);

}

编译运行,出现一个线条,最小化还原,没有了。更改OnDraw函数:

显示线条B(100,100,70,70)

void CDrawView::OnDraw(CDC* pDC)

{

       CDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       pDC->MoveTo(100,100);

       pDC->LineTo(70,70);

}

运行,最小化还原,你会发现线条B一直存在,线条A没有了。

 

到这里,你应该有一个疑问:我们新建一个SDI程序,什么都没做啊,为什么这个时候还是能显示菜单,框架这些东西?我们并没有告诉操作系统要显示什么啊?这是因为一般Windows会发送两个消息WM_PAINT(通知客户区有变化)和WM_NCPAINT(通知非客户区有变化)。非客户区的重画系统自己搞定了,而客户区的重画需要我们自己来完成。

 

初级问题:

我要在菜单函数里面画图怎么办?或者说,我希望点击菜单就画图,不想把代码放在OnDraw里面。

 

这个问题主要是思路没有转过弯来。其实很简单,在菜单函数里面设置一个开关,然后在OnDraw里面根据这个开关来决定是否显示就可以了。例如:

BOOL bShowLineA=FALSE;

void CDrawView::OnDrawline()

{

       // TODO: Add your command handler code here

       bShowLineA=TRUE;

        Invalidate();//这个函数暂时只需要知道是用来“调用”OnDraw函数的

}

void CDrawView::OnDraw(CDC* pDC)

{

       CDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

        if(bShowLineA)

       {

           pDC->MoveTo(20,20);

          pDC->LineTo(50,50);

       }    

}

单击菜单项DrawLine就会画线条A,当然你还可以再搞一个函数令bShowLineA=FALSE达到删除线条的目的。

 

上述做法存在很大局限:必须事先知道要画什么图。考虑这样一种情况:我们用鼠标画图,鼠标按下时决定开始线条开始点,鼠标弹起时决定结束点并画图。你也许会说,还是可以用上面的做法啊,用变量来代替数字就可以了,例如:

void CDrawView::OnLButtonDown(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler code here and/or call default

       start=point;

       CView::OnLButtonDown(nFlags, point);

}

void CDrawView::OnLButtonUp(UINT nFlags, CPoint point)

{

       // TODO: Add your message handler code here and/or call default

       end=point;

       Invalidate();

       CView::OnLButtonUp(nFlags, point);

}

void CDrawView::OnDraw(CDC* pDC)

{

       CDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       pDC->MoveTo(start.x,start.y);

       pDC->LineTo(end.x,end.y);

}

这样的确是可以解决一部分问题,考虑更复杂的情况:画若干条线条,还要画一些圆、正方形,甚至还要显示一行文字。这个时候上面的方法就不太适合了。但原理是一样的:在菜单函数里面计算,保存数据结果,通知系统更新;在OnDraw函数里面根据新的结果数据进行重画。在菜单函数里面如何保存数据结果呢?可以使用一个结构(或者类)保存在内存里(例如可以使用数组,链表等等方式),也可以把数据保存在一个文件里,然后,在OnDraw里面读取这个结构(前面说的数组、链表、文件等)的数据进行重画。总之,具体问题具体分析。

 

记住一个大原则:画图代码(确切的说是用来显示的代码)放在OnDraw里面。

 

那么,是不是任何时候画图代码都必须要放在OnDraw里面?也不全是,例如操作的一些中间过程就要放在其他函数。一个经典的例子是用鼠标画直线,并且动态显示中间画图过程,鼠标弹起才最终决定最后的直线。那么,在鼠标移动事件中就要动态的画线了,然后保存最终结果,在OnDraw里面只需要画这个最终结果就可以了。当然,这个中间过程其实还是可以放到OnDraw里面来的。特别是一些复杂的图形处理,例如画不规则图形,就需要开一个线程专门用来计算显示画线的过程,这个不在我们讨论范围之内。

 

我还发现一个初学者有趣的心理活动,包括我自己也是一样的,就是舍不得在OnDraw里面放较多的代码。如下:

void CDrawView::OnDraw(CDC* pDC)

{

       CDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       pDC->MoveTo(start.x,start.y);

       pDC->LineTo(end.x,end.y);

        ....

        下面还有很多的画图的代码。

}

上面这种情况他们总是担心会不会效率太低了?因为窗口动不动就要刷新,这么多代码会不会太慢了?但是,要是下面这样的代码他们就放心多了:

void CDrawView::OnDraw(CDC* pDC)

{

       CDrawDoc* pDoc = GetDocument();

       ASSERT_VALID(pDoc);

       // TODO: add draw code for native data here

       fun();

}

fun

{

很多很多很多的画图的代码

}

呵呵,有时候我跟别人说,把画图代码放在OnDraw里面,他们就会认为,每一行代码都放到OnDraw里面。

 

还有一些“顽固分子”说,我把画图的代码放在菜单函数里面,然后禁止窗口重画就行了,然后到处去问怎么禁止窗口自动重画。他们都忽略了一个事实,显示器只有一个!他们都把窗口当作一个个的实体了。他们的想法是:把这个窗口画好了,就再也不动了,最小化吗?好办,请工人把这个窗口拿走,就可以看到后面的窗口了,还原,就让工人再搬回来就是了。可事实是:窗口(显示器)永远都只有一个。我们只看显示器里面中间的一个象素点:这个时候,这个点在我们的程序里面是红色的,然后把我们的窗口最小化,这个点并没有随着我们的窗口跑掉啊,它还是在那里,可是下一个窗口是绿色的,你说,这个时候是不是要重新把这个点涂成绿色的?这就是重画。还原时,又要重新把它涂成红色,如此反复。

阅读(3472) | 评论(4) | 转发(0) |
0

上一篇:课题进展

下一篇:自动单阈值分割OTSU算法

给主人留下些什么吧!~~

chinaunix网友2009-03-27 22:03:04

在vb里只要把窗口的autoredraw设为true就行了,难道是微软帮我们做了一些很复杂的工作?还有,我们为什么不把窗口视图区域设置成图片信息放在内存中,重绘的时候再把它复原,这样不行吗?

chinaunix网友2008-11-20 19:14:11

呵呵,幼稚得可爱!

chinaunix网友2008-04-03 16:03:59

看了你的帖子,受益匪浅,感谢感谢!