无闪烁绘图

闪烁是草率的编程迹象和对细节的关注不足。 There is no reason why any part of a Windows program should flicker. 目前没有任何理由的Windows计划的一部分,应闪烁。 The aim of this article is to present the reader (that's you) with the techniques used to prevent their windows applications from flickering. 本文的目的是与目前用于防止闪烁的Windows应用程序的技术读者(就是你)。

What is flickering? 什么是闪烁?

Flicker is simply this: the display of one image over the top of another in rapid succession. 闪烁很简单:一个形象在另一快速连续顶部显示。 The result of this is screen flicker, where you can see one image briefly before another one is shown on top. 这样做的结果是屏幕闪烁,在这里你可以看到一个简单的图像,然后另外一个是在上面显示。 Personally I find applications that "flicker" annoying to use, for this one reason: If the user-interface has been badly coded, then what does this say about the rest of the application, the part that you trust your data with? 我个人觉得现在的应用程序的“闪烁”烦人使用,为这一个原因:如果用户界面进行了编码错误的,那么这是否对其他应用程序的说,你信任的部分,你的数据呢? An application that has a smooth, fast user interface inspires confidence in it's users - it's as simple as that. 一个应用程序,平稳,快速的用户界面中激发信心的用户 - 这是这么简单。

An application can flicker in many ways. 应用程序可以在许多方面闪烁。 The most common cause is when a window is resized, causing the contents to flicker badly as it is redrawn. 最常见的原因是,当窗口大小被改变,造成严重的内容,因为它是闪烁重绘。

Only draw things once 只画一次

This is the golden rule when doing any kind of painting on a computer, be it Windows or whatever OS you are using. 这是一个黄金法则,在任何一台计算机上的画种,无论是Windows或任何操作系统所使用。 You must never draw over the same pixel twice. 你绝不能在同一像素画两次。 A lazy programmer will often avoid putting any thought into the painting process, instead opting to take the easy route. 一个懒惰的程序员常常不愿意投入绘画过程中的任何思想,而是采用了简单的方法。

With the case of flickering, it is your responsiblity to ensure that no "overdraw" occurs. 要避免闪烁时,它是你的责任心要确保没有“透支”发生。 Now, Windows and your computer are fundamentally stupid; they won't do anything unless you instruct them explicitly. 现在,Windows和您的计算机还是愚蠢,他们不会做任何事情,除非你给他们指令。 If any flickering is occuring, it is because some part of your program has deliberately overdrawn some area of the screen. 如果闪烁的现象发生,那是因为你的计划的一部分,故意透支了屏幕的某些区域。

This may be because of some explicit command, or something which you have neglected to do. 这可能是因为一些明确的命令,或一些被你忽略的事情。 In either case, if your Windows program has a flickering problem, you need to understand how best to remove the problem. 在这两种情况下,如果程序有闪烁的现象出现,您需要了解如何以最佳方式解决这个问题。

WM_ERASEBKGND WM_ERASEBKGND消息

The prime suspect is usually the WM_ERASEBKGND message. 首要嫌疑人通常是WM_ERASEBKGND消息。 This message is sent to a window when it's background needs to be erased. 此消息被发送到一个窗口时,它的背景需要被删除。 This happens because windows are usually painted using a 2-stage process: 这是因为窗口的绘画通常使用2阶段:

  • WM_ERASEBKGND : Clear the background WM_ERASEBKGND消息 :清除背景
  • WM_PAINT : Draw the contents on top WM_PAINT消息 :在上面绘制的内容

This makes it easy to draw a window's contents: Every time you receive a WM_PAINT message, you know that you have a nice fresh canvas to draw on. 这使得它容易得出一个窗口的内容:每次当收到WM_PAINT消息时,你知道你已经有了一个新的画布上绘制。 However, drawing a window twice (once with WM_ERASEBKGND, once again with WM_PAINT) will cause the window to badly flicker. 然而,画窗口两次(一次是通过WM_ERASEBKGND消息,一次是WM_PAINT)将会导致严重的窗口闪烁。 Just take a look at the standard Edit control in Windows - open up Notepad.exe and resize the window, and see how the contents flicker as it is redrawn. 只要看一看在标准外观编辑 - 打开Windows的写字板并改变窗口大小,看看如何闪烁的内容,因为它是重新绘制。

Right then, how do we avoid erasing the background of a window? 就在那时,我们如何避免擦除窗口的背景是什么? There are two methods. 有两种方法。

  • Set the window's background brush to NULL. 设置窗口的背景刷子为NULL。 (Set the hbrBackground member of the WNDCLASS structure to zero when you register the window class). (设置WNDCLASS 结构的hbrBackground 成员为零时,注册窗口类)。
  • Return non-zero in the WM_ERASEBKGND message handler. 返回非零在WM_ERASEBKGND消息处理程序。

Any one of these will steps will prevent the WM_ERASEBKGND message from clearing the window. 任何一种方法都人会阻止清除窗口WM_ERASEBKGND消息。 The last option is usually easiest to implement: 最后一个选项通常是最容易实现的:

 case WM_ERASEBKGND:
案件WM_ERASEBKGND消息:

    return 1;
返回1;
 

It is also possible to prevent WM_ERASEBKGND when you invalidate and update a window. 它也可以防止WM_ERASEBKGND消息时失效和更新的一个窗口。 The InvalidateRect API call's last parameter specifies whether or not a portion of a window is to have it's background erased when it is next redrawn. InvalidateRect函数的最后一个参数指定是否不是一个窗口的一部分,是有它的背景会被当它下一次重新绘制。 Specifying FALSE for this paramter prevents WM_ERASEBKGND from being sent when the window is redrawn. 将该参数置为False可以防止WM_ERASEBKGND消息被发送时,窗口重绘。

 InvalidateRect(hwnd, &rect, FALSE);
 InvalidateRect函数(HWND的,与矩形,假);
 

Don't draw things when you don't have to 不画东西的时候,你不必

It is quite common for a Windows application to redraw it's entire window contents, even if only a small part of it changed. 这是相当一个Windows应用程序重绘它的整个窗口的内容一样,哪怕只有一小部分改变。 This is most usually the case when a window is resized - some (but not all) programs redraw the whole window. 这是最通常的,当窗口大小被改变 - 一些(但不是全部)计划重绘整个窗口。 This is normally not necessary, because when a window is resized, more often than not the previous window contents is left unchanged, and the resize has just uncovered a small border which needs painting. 这通常是没有必要的,因为当一个窗口被调整大小,往往比前一个窗口内容是不变的,而且调整刚刚发现了一个小的边界需要重画。 It is not necessary to redraw the entire contents in this case. 这是没有必要在这种情况下,重新划定的全部内容。 If a little thought and care is used, the painting algorithms can be written so that only the bare minimum is painted at any one time. 如果一点思考和谨慎使用,他的画算法可以被写入,以便只有最小的部分被涂在任何一个时间。

Every window in the system keeps an update region. 系统中的每个窗口都有更新区域。 This region describes the area of a window that has become invalidated and needs repainting. 这个区域描述了一个已经成为无效需要重画的窗口区域。 If a windows only updates the required area, and no more, then the window will draw much quicker as a result. 如果一个窗口只需要更新的区域,没有更多的,则窗口将以此作为一个结果非常快。

There are several ways to retrieve the update region for a window. 有几种方法可以获得窗口的更新区域。 The GetUpdateRgn API call retrieves the exact region, be it rectangular, or a more irregular shape.GetUpdateRgn API调用检索准确地区,无论是长方形的,或更不规则的形状。 The GetUpdateRect API call retrieves the smallest bounding rectangle that encloses the update region.GetUpdateRect函数可以获得需要 的最小矩形包含了需要更新的区域。 It is usually easier to just work with a rectangular area like this. 它通常容易一些工作,这样一种矩形区域。 The third method is to use the PAINTSTRUCT structure in conjunction with the BeginPaint / EndPaint API calls. 第三种方法是使用API调用EndPaint / PAINTSTRUCT 结构,联同BeginPaint函数

A normal painting procedure looks like this: 一个常规的画法是这样的:

 PAINTSTRUCT  ps;
 PAINTSTRUCT ps的;

HDC          hdc;
 HDC的HDC的;


case WM_PAINT: 案件WM_PAINT消息:
hdc = BeginPaint(hwnd, &ps); HDC的= BeginPaint函数(HWND的,和PS);

// do painting / /做画

EndPaint(hwnd, &ps); EndPaint(HWND的,和PS);
return 0; 返回0;

BeginPaint initializes the ps (PAINTSTRUCT) structure. BeginPaint函数初始化PS(PAINTSTRUCT)结构。 One member, rcPaint , is a RECT structure which describes the smallest bounding rectangle that encloses the update region (Just like the GetWindowRect API call). 一位成员,rcPaint 是一个RECT结构,描述了最小矩形包围更新区域(就像GetWindowRect函数)。 By only limiting drawing to just this rectangular region, painting can be dramatically sped up. 通过唯一的限制只在这个矩形绘图区域,绘画可以大大加快。

 

Now, Windows automatically clips any drawing you perform outside the update region when you use BeginPaint/EndPaint. 现在,Windows会自动剪辑掉画到更新区域外执行当使用BeginPaint / EndPaint。 This means that there is no way you can draw outside the update region even if you tried. 这意味着,有没有办法可以更新区域以外的画,即使你努力着。 You might think that it is pointless to make sure your code doesn't try to draw outside the update region, even when nothing will be drawn anyway. 你可能认为这是毫无意义的,以确保您的代码不试图画到更新区域外,即使什么也不会画反正。 However, you are still avoiding unnecessary API calls and calculations, so I think it is always worth putting in a little more effort to get things working as fast as possible. 但是,你仍然可以避免不必要的API调用和计算,所以,我认为这是绝对值得的多一点精力在如何尽快投入工作。

 

When you just can't help it 当你不能帮助它

 

There are occasions when you spend alot of time and effort getting your super-duper drawing code working, only to find that your window is still getting redrawn in it's entirety. 有些时候,当你花了很多时间和精力让你超好的画法时,才发现,你的窗口还是会被它的全部刷新。 This is usually the cause of two window class styles - CS_VREDRAW and CS_HREDRAW . 这通常是两种风格的原因窗口类- CS_VREDRAW和 CS_HREDRAW。 When a window class has either of these two styles set, the window contents will be completely redrawn every time it is resized either vertically or horizontally (or both). 当一个窗口类要么这两个设置样式有,窗口的内容将完全重绘每次调整大小垂直或水平(或两者)。 So, you need to turn off these two class styles. 所以,你需要关闭这两个类样式。 The only way to do this is to make sure your window isn't created with them in the first place, and to prevent this from happening, you have to make sure that CS_HREDRAW and CS_VREDRAW aren't included when the window class is registered. 只有这样,才能做到这一点,以确保你的窗口不是因为他们创造了首位,并防止这种情况发生,你必须确保CS_HREDRAW和CS_VREDRAW不包括在窗口类被注册。

 

 WNDCLASSEX wc;
的WNDCLASSEX厕所;


wc.cbSize = sizeof(wc); wc.cbSize =一下SizeOf(厕所);
wc.style = 0; /* CS_VREDRAW | CS_HREDRAW ; */ wc.style = 0; / * CS_VREDRAW | CS_HREDRAW; * /
... ...
RegisterClassEx(&wc); RegisterClassEx(&水柱);

The above example is just to help illustrate the point that these two styles must not be included when the window class is registered. 上面的例子只是为了帮助说明这两个属性不被包括在窗口类被注册点。

 

Just a word of warning here: If the main window in an application has these two class styles set, then this will cause all child windows to be redrawn during a resize, even if those children don't have the redraw flags set. 有一点需要在这里警告:如果在一个应用程序的主窗口设置,那么这将导致所有子窗口重新绘制调整过程中,即使这些孩子没有重绘标志设置这两个类样式。 This can be avoided by following the next step: 这可以通过以下方式避免下一步:

 

Clipping child windows 裁剪子窗口

 

Sometimes flickering occurs because a parent window doesn't clip it's children when it paints itself. 有时,闪烁的发生,因为一个父窗口没有将它夹在儿童时,涂料本身。 This results in the entire parent window contents being shown, and the the child windows being displayed on top (causing flicker). 在整个父窗口的内容这将导致显示,与子窗口被上面(造成闪烁)。 This can be easily solved by setting the WS_CLIPCHILDREN style on the parent window. 这可以迎刃而解通过设置父窗口WS_CLIPCHILDREN样式。

 

When a window has this style set, any areas that its child windows occupy are excluded from the update region. 当一个窗口具有此样式设置,任何地区,它的子窗口占用被排除在更新区域。 So, even if you try to draw over a child control, the clipping region that BeginPaint assigns will prevent you from doing so. 所以,即使你试图画了一个子控件,剪辑区域BeginPaint的分配将阻止你这样做。

 

Double-buffing and memory-DC's 双抛光和内存DC的

 

A common method to completely eliminate flickering windows is to use a technique called double-buffering. 常见的方法,彻底消除闪烁的是使用了一种叫做双缓冲。 This basic idea is to draw a window's contents into an off-screen buffer, and then transfer this buffer to the screen in one fell-swoop (using BitBlt). 其基本的思路是绘制成一个离屏缓冲区的窗口的内容,然后将其转移到这个缓冲区在一个屏幕下跌,一举(使用BitBlt)。 This is a pretty good way to reduce flicker, but is often overused, especially by programmers who don't really understand how get efficient drawing working. 这是一个很好的方式,以减少闪烁,但是经常被滥用,特别是程序员谁不真正了解如何有效地绘制工作。

 

The basic way double-buffering works is like this: 典型的双缓冲工作原理是这样的:

 

 HDC          hdcMem;
 HDC的hdcMem;
 
HBITMAP hbmMem; HBITMAP句柄hbmMem;
HANDLE hOld; 手把举行;

PAINTSTRUCT ps; PAINTSTRUCT ps的;
HDC hdc; HDC的HDC的;

.... ....
case WM_PAINT: 案件WM_PAINT消息:

// Get DC for window / /获取窗口的DC
hdc = BeginPaint(hwnd, &ps); HDC的= BeginPaint函数(HWND的,和PS);

// Create an off-screen DC for double-buffering / /创建一个离屏直流双缓冲
hdcMem = CreateCompatibleDC(hdc); hdcMem = CreateCompatibleDC(HDC的);
hbmMem = CreateCompatibleBitmap(hdc, win_width, win_height); hbmMem = CreateCompatibleBitmap(HDC的,win_width,€);
hOld = SelectObject(hdcMem, hbmMem); 持有= SelectObject的(hdcMem,hbmMem);

// Draw into hdcMem / /绘制成hdcMem

// Transfer the off-screen DC to the screen / /转移离屏DC到屏幕
BitBlt(hdc, 0, 0, win_width, win_height, hdcMem, 0, 0, SRCCOPY); BitBlt函数(HDC的,0,0,win_width,win_height,hdcMem,0,0,SRCCOPY);

// Free-up the off-screen DC / /自由了离屏幕DC
SelectObject(hdcMem, hOld); 选择对象(hdcMem,持有);
DeleteObject(hbmMem); DeleteObject(hbmMem);
DeleteDC (hdcMem); DeleteDC(hdcMem);

EndPaint(hwnd, &ps); EndPaint(HWND的,和PS);
return 0; 返回0;

This method is a little slow, because the offscreen memory-DC is created from scratch every time the window needs to be drawn. 这种方法是有点慢,因为在离屏内存DC是从头每次要绘制窗口需要创建。 A more efficient method would be to create the memory DC only once, big enough so that the entire window can be painted at any time. 一个更有效的方法是创建内存DC只有一次,大到足以使整个窗口可以在任何时间画。 When the application terminates, the memory DC would then be destroyed. 当应用程序终止时,内存DC,再销毁。 Both these methods are potentially quite memory-intensive, especially if the memory DC needs to be the size of a screen (1024 * 768 * 32 bytes=2.5 Mb). 这两种方法都存在对内存密集型,尤其是如果需要将内存DC的屏幕(1024 * 768 * 32 = 2.5兆字节)的大小。

 

Double-buffering will also be twice as slow as it needs to be. 双缓冲也慢的两倍,因为它必须这样做。 Because you are drawing once to the memory-DC, then again during the "blit", you are using up clock cycles when you don't need to. 因为一旦你正在绘制的内存区,在“blit操作”,然后再次使用时脉周期时,你不需要。 Granted, a fast graphics card will perform a BitBlt very quickly, but it's still wasted CPU. 当然,快速图形卡将执行一个BitBlt更快,但它仍然是浪费的CPU。

 

If your application needs to display quite complicated information (say, like a web-page), then you would need to use the memory-DC method. 如果应用程序需要显示相当复杂的信息(如网页说),那么你就需要使用内存直流电法。 Take Internet Explorer, for instance. 以IE浏览器,例如。 There is no way it would be able to render a web-page with no flickering without using double-buffering. 有没有办法将能够使不使用双缓冲网页与不闪烁的。

 

Double-buffering doesn't have to be used to paint a whole window. 双缓冲没有被用来画整个窗口。 Imagine that you had just a small portion of a window that contained a complex graphic object (maybe a semi-transparent bitmap or something). 想象一下,你只是一个窗口,包含了复杂的图形对象(可能是半透明位图或某事)一小部分。 You could use an off-screen DC to draw just this one region, and BitBlt that to the screen, whilst drawing the rest of the window normally. 你可以使用外屏幕DC绘制仅这一个地区,和BitBlt到屏幕上,同时绘制窗口的其余部分正常。

 

Sometimes though, with a little careful thinking, it is often possible to avoid double-buffering and draw straight to the screen. 虽然有时,通过仔细的思考,常常是有可能避免双重缓冲和直接绘制到屏幕上。 As long as you don't break the golden rule, "Never draw over the same pixel twice", you will achieve flicker-free drawing. 只要你不破坏黄金法则,“永远不要在同一像素画两次”,你会实现无闪烁绘图。

 

Avoiding deliberate overdraw 避免故意透支

 

What I mean by this is the following type of situation. 我的意思是以下类型的情况。 Say, you are custom-drawing the titlebar of a window. 喂,你是定制绘图窗口的标题栏。 You draw the caption first, then draw some additional graphics over the top. 你画了标题,然后再在上面画了一些其他的图形。 Now, whenever the caption needs to be painted, it will flicker. 现在,只要标题需要被重画,就会出现闪烁现象。 This is because you haven't followed the "golden rule". 这是因为你没有遵循“金科玉律”。 In this case, the caption is being shown briefly before additional graphics are painted on top, which appear to flicker. 在这种情况下,标题被很快地显示在其他图形的顶部上,这似乎闪烁画。

 

There are two techniques you can use to prevent this type of flickering. 有两种技术可用于防止这种类型的闪烁。 The first is to use clipping, the second is to use your brain. 第一种方法是使用剪切,第二个是使用你的大脑。

 

In the case of clipping, you can use the ExcludeClipRect API call to mask out certain areas of a device context. 在裁剪案件,可以使用 ExcludeClipRect API调用来屏蔽掉某些领域的设备上下文的。 When an area is masked, it is not affected when painted over. 当一个地区被屏蔽,它不受到影响时,画了。 Once a background has been drawn, the clipping area can be removed with SelectClipRgn , and another graphic can be painted in the previously masked-out area. 一旦背景已经绘就,该标记的区域可以通过 SelectClipRgn移掉,其他图形可区涂在以前屏蔽掉。 By using appropriate masking (or clipping), overdraw can be eliminated in alot of cases. 通过使用适当的标记(剪切),透支可以消除很多的情况下。

 

The other option is to take a more intelligent approach. 另一种选择是采取更聪明的解决办法。 Imagine you had to draw a grid. 想象一下,当你需要画一个网格。 A grid would normally be painted by first drawing a blank background, and then drawing a series of lines (horizontal and vertical) to create the grid effect. 网格通常是先画一画空白的背景,再画线(水平和垂直)系列创建电网的影响。 The problem with this type of approach is that the grid lines will appear to flicker, because the background is briefly appearing underneath each line before the lines are drawn. 与此类型的方法的问题是,格线会出现闪烁,因为背景是每行下面简要出现之前,线绘制。 However, the same effect can be achieved with a different approach. 然而,同样的效果可以达到与不同的方法。 Instead of drawing a single blank background, draw a series of blank squares, separated by a pixel-wide space on each side. 而不是单一的空白背景画,画一个由每边像素宽的空间分隔空白广场,系列。 When you come to draw the grid lines, they can be placed in the pixel-wide gaps which haven't been painted over yet. 当你来到画网格线,他们可以被放置在像素宽的有没有画过但差距。 The result is the same, but this time there is no flickering because no pixel has been painted over twice. 其结果是一样的,但这次没有,因为没有像素被画了两次闪烁。

 

Using your brain to think around a problem may take slightly longer than the direct "no-brainer" approach, but I think it is worth the extra effort, because the results can be so much better. 使用你的头脑去思考一个问题可能需要大约比直接“没有脑子”的做法更长的时间,但我认为这是值得额外的努力,因为其结果可能是好多了。

 

Conclusion 结论

 

Hopefully you should never have to ask the question "Why does my window flicker?" 希望你永远不应该问这个问题:“为什么我的窗口闪烁?” ever again. 一劳永逸。 I have presented the major causes of flickering in a windows program, and also the techniques you can use to remove this flickering. 我已经介绍了在Windows程序闪烁的主要原因,也是技术,你可以用它来消除这种闪烁。 If you encounter flickering in a program you are developing, you should be able to identify the possible causes, and use the techniques described in this tutorial to completely eliminate flicker from your applications. 如果你遇到一个程序,你正在开发闪烁,你应该能够找出可能的原因,并使用本教程中描述的完全消除闪烁,从您的应用程序的技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值