Mega Code Archive

 
Categories / Delphi / Examples
 

How to avoid flicker using double buffering in LARGE controls

Title: How to avoid flicker using double buffering in LARGE controls? Question: You may avoid flicker by setting DoubleBuffered property of TWinControl to True - this will cause control first to paint itself to memory bitmap and then copy this bitmap into its device context. Unfortunatelly, this trick does not work on large controls cause buffering bitmap will take too much memory - even if this TWinControl will be kept inside a smaller one - for example, TPanel sized 10000x10000 inside of TScrollBox sized 100x100. Answer: The problem is in WM_PAINT message handler of the TWinControl: procedure TWinControl.WMPaint(var Message: TWMPaint); ... begin ... MemBitmap := CreateCompatibleBitmap(DC, ClientRect.Right, ClientRect.Bottom); ... end; As you may see, this method creates a memory bitmap which size is just the same as the size of the control itself - in our case 10000x10000, that's too much. Solution may be creating a smaller buffer. Let's create a component TMyPanel inherited form TPanel and override WM_PAINT handler as follows: procedure TMyPanel.WMPaint(var Message: TWMPaint); var DC, MemDC: HDC; MemBitmap, OldBitmap: HBITMAP; PS: TPaintStruct; begin if not FDoubleBuffered or (Message.DC 0) then begin if not (csCustomPaint in ControlState) and (ControlCount = 0) then inherited else PaintHandler(Message); end else begin DC := GetDC(0); // Creating smaller buffer MemBitmap := CreateCompatibleBitmap(DC, FBufferWidth, FBufferHeight); ReleaseDC(0, DC); MemDC := CreateCompatibleDC(0); OldBitmap := SelectObject(MemDC, MemBitmap); try DC := BeginPaint(Handle, PS); Perform(WM_ERASEBKGND, MemDC, MemDC); Message.DC := MemDC; // Move vieport origin MoveViewportOrg(Message.DC, Left, Top); // Draw in the buffer WMPaint(Message); // Move viewport origin back MoveViewportOrg(Message.DC, -Left, -Top); Message.DC := 0; // Copy buffer with the offset BitBlt(DC, -Left, -Top, ClientRect.Right, ClientRect.Bottom, MemDC, 0, 0, SRCCOPY); EndPaint(Handle, PS); finally SelectObject(MemDC, OldBitmap); DeleteDC(MemDC); DeleteObject(MemBitmap); end; end; end; MoveViewportOrg procedure is quite similar to MoveWindowOrg: procedure MoveViewportOrg(DC: HDC; DX, DY: integer); var P: TPoint; begin GetViewportOrgEx(DC, P); SetViewportOrgEx(DC, P.X + DX, P.Y + DY, nil); end; FBufferWidth and FBufferHeight indicate buffer's width and height. In our case, FBufferWidth:=100 and FBufferHeight:=100 are quite enough for TScrollBox sized 100x100. P.S. This article is kinda request for comments. I am not sure that this trick will work in 100% of cases, but do not see any faults. Please feel free to comment.