For example, here is a bare-bone control that takes up height for segmented updates (just redraw what is needed). If you create it on a form, try moving the window above it and see how it replaces parts with colors (see Drawing Method).
Does anyone have a similar base class that can handle non-client areas without modification?
Well, your TMyControl does not have an area without a client (yet). So I added BorderWidth := 10; , and now he has;)
In the general case, the area without client windows by default is automatically painted by Windows without flickering, including scrollbars, headers, etc. (at least I haven't seen it yet).
If you want to draw your own border, you have to handle WM_NCPAINT. See this code:
unit Unit2; interface uses Classes, Controls, Messages, Windows, SysUtils, Graphics; type TMyControl = class(TCustomControl) private procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT; protected procedure Paint; override; procedure CreateParams(var Params: TCreateParams); override; public constructor Create(AOwner:TComponent);override; end; implementation { TMyControl } constructor TMyControl.Create(AOwner:TComponent); Begin Randomize; inherited Create(Aowner); ControlStyle:=ControlStyle - [csOpaque]; BorderWidth := 10; Anchors := [akLeft, akTop, akBottom, akRight]; end; procedure TMyControl.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); Params.ExStyle := Params.ExStyle or WS_CLIPCHILDREN; with Params.WindowClass do style := style and not (CS_HREDRAW or CS_VREDRAW); end; procedure TMyControl.Paint; begin Canvas.Brush.Color := RGB(Random(255), Random(255), Random(255)); Canvas.FillRect(Canvas.ClipRect); end; procedure TMyControl.WMEraseBkgnd(var Message: TWMEraseBkgnd); begin Message.Result := 1; end; procedure TMyControl.WMNCPaint(var Message: TWMNCPaint); var DC: HDC; R: TRect; begin Message.Result := 0; if BorderWidth > 0 then begin DC := GetWindowDC(Handle); try R := ClientRect; OffsetRect(R, BorderWidth, BorderWidth); ExcludeClipRect(DC, R.Left, R.Top, R.Right, R.Bottom); SetRect(R, 0, 0, Width, Height); Brush.Color := clYellow; FillRect(DC, R, Brush.Handle); finally ReleaseDC(Handle, DC); end; end; end; end.
A few notes:
- Override CreateParams instead of declaring virtual. Pay attention to the compiler warning (although I think / hope this is a small error).
- You do not need to check
isEmptyRect and isSameRect . If ClipRect empty, then there is nothing to draw. This is also the reason you should never access Paint directly, but always through Invalidate or the equivalent. - AdjustClientRect is not required. He is called internally when necessary for his purposes.
And as a bonus, thatโs how I draw the component of a chess piece:
type TCustomChessBoard = class(TCustomControl) private FBorder: TChessBoardBorder; FOrientation: TBoardOrientation; FSquareSize: TSquareSize; procedure BorderChanged; procedure RepaintBorder; procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND; procedure WMNCPaint(var Message: TWMNCPaint); message WM_NCPAINT; protected procedure CreateParams(var Params: TCreateParams); override; function GetClientRect: TRect; override; procedure Paint; override; procedure Resize; override; public constructor Create(AOwner: TComponent); override; procedure Repaint; override; end; const ColCount = 8; RowCount = ColCount; procedure TCustomChessBoard.BorderChanged; begin RepaintBorder; end; constructor TCustomChessBoard.Create(AOwner: TComponent); begin inherited Create(AOwner); ControlStyle := [csOpaque]; end; procedure TCustomChessBoard.CreateParams(var Params: TCreateParams); begin inherited CreateParams(Params); with Params.WindowClass do style := style and not (CS_HREDRAW or CS_VREDRAW); end; function TCustomChessBoard.GetClientRect: TRect; begin Result := Rect(0, 0, FSquareSize * ColCount, FSquareSize * RowCount); end; procedure TCustomChessBoard.Paint; procedure DrawSquare(Col, Row: Integer); var R: TRect; begin R := Bounds(Col * FSquareSize, Row * FSquareSize, FSquareSize, FSquareSize); Canvas.Brush.Color := Random(clWhite); Canvas.FillRect(R); end; var iCol: Integer; iRow: Integer; begin with Canvas.ClipRect do for iCol := (Left div FSquareSize) to (Right div FSquareSize) do for iRow := (Top div FSquareSize) to (Bottom div FSquareSize) do DrawSquare(iCol, iRow); end; procedure TCustomChessBoard.Repaint; begin inherited Repaint; RepaintBorder; end; procedure TCustomChessBoard.RepaintBorder; begin if Visible and HandleAllocated then Perform(WM_NCPAINT, 0, 0); end; procedure TCustomChessBoard.Resize; begin Repaint; inherited Resize; end; procedure TCustomChessBoard.WMEraseBkgnd(var Message: TWMEraseBkgnd); begin Message.Result := 1; end; procedure TCustomChessBoard.WMNCPaint(var Message: TWMNCPaint); var DC: HDC; R: TRect; R2: TRect; SaveFont: HFONT; procedure DoCoords(ShiftX, ShiftY: Integer; Alpha, Backwards: Boolean); const Format = DT_CENTER or DT_NOCLIP or DT_SINGLELINE or DT_VCENTER; CoordChars: array[Boolean, Boolean] of Char = (('1', '8'), ('A', 'H')); var i: Integer; C: Char; begin C := CoordChars[Alpha, Backwards]; for i := 0 to ColCount - 1 do begin DrawText(DC, PChar(String(C)), 1, R, Format); DrawText(DC, PChar(String(C)), 1, R2, Format); if Backwards then Dec(C) else Inc(C); OffsetRect(R, ShiftX, ShiftY); OffsetRect(R2, ShiftX, ShiftY); end; end; procedure DoBackground(Thickness: Integer; AColor: TColor; DoPicture: Boolean); begin ExcludeClipRect(DC, R.Left, R.Top, R.Right, R.Bottom); InflateRect(R, Thickness, Thickness); if DoPicture then with FBorder.Picture.Bitmap do BitBlt(DC, R.Left, R.Top, R.Right - R.Left, R.Bottom - R.Top, Canvas.Handle, R.Left, R.Top, SRCCOPY) else begin Brush.Color := AColor; FillRect(DC, R, Brush.Handle); end; end; begin Message.Result := 0; if BorderWidth > 0 then with FBorder do begin DC := GetWindowDC(Handle); try { BackGround } R := Rect(0, 0, Self.Width, Height); InflateRect(R, -Width, -Width); DoBackground(InnerWidth, InnerColor, False); DoBackground(MiddleWidth, MiddleColor, True); DoBackground(OuterWidth, OuterColor, False); { Coords } if CanShowCoords then begin ExtSelectClipRgn(DC, 0, RGN_COPY); SetBkMode(DC, TRANSPARENT); SetTextColor(DC, ColorToRGB(Font.Color)); SaveFont := SelectObject(DC, Font.Handle); try { Left and right side } R := Bounds(OuterWidth, Width, MiddleWidth, FSquareSize); R2 := Bounds(Self.Width - OuterWidth - MiddleWidth, Width, MiddleWidth, FSquareSize); DoCoords(0, FSquareSize, FOrientation in [boRotate090, boRotate270], FOrientation in [boNormal, boRotate090]); { Top and bottom side } R := Bounds(Width, OuterWidth, FSquareSize, MiddleWidth); R2 := Bounds(Width, Height - OuterWidth - MiddleWidth, FSquareSize, MiddleWidth); DoCoords(FSquareSize, 0, FOrientation in [boNormal, boRotate180], FOrientation in [boRotate090, boRotate180]); finally SelectObject(DC, SaveFont); end; end; finally ReleaseDC(Handle, DC); end; end; end;
