I'm writing a simple program which list every window being shown on the screen. I can select a window from the list to surround it with a rectangle. I am drawing the rectangle directly to the desktop window. I need to clear the canvas before, as I want to select the window I desire, one window selected at a time.
I have tried invalidating the desktop window, which actually works, but it makes a lot of flickering.
InvalidateRect(0, 0, True);
Is it correct in this case to draw directly on the desktop? Any other idea to avoid the flickering?
Thank you.
Usually when you draw directly to the desktop, the only way to prevent artifacts from showing up is to constantly redraw the entire screen. This can become heavy, and defeats the whole purpose of video caching.
I've seen applications accomplish this not by drawing directly to the screen's canvas, but by creating 4 different "edge" windows. For example, there will be a window for each the Top, Left, Right, and Bottom edge of the window you'd like to "frame". The top and bottom windows can have a height of 5px and the left and right edges have a width of 5px. Position those windows (no border style) around the edges of the window, color each of those forms, for example, green, and then you have a green border around the window.
For example:
bsNone
fsStayOnTop
Using this method, you don't have to worry about invalidating. It's possible to do it from one single form, but then you'd have to then worry about transparency and such. Using 4 forms for each edge ensures the user can still click on the focused form without any transparency necessary.
A quick sample...
Unit: uMain.pas
unit uMain;
interface
uses
Winapi.Windows, Winapi.Messages,
System.SysUtils, System.Variants, System.Classes,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls;
type
TfrmMain = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
private
FTop: TForm;
FLeft: TForm;
FRight: TForm;
FBottom: TForm;
procedure PositionBorder(const ARect: TRect; const Thickness: Integer;
const Color: TColor);
procedure HideBorder;
public
function FormRect: TRect;
end;
var
frmMain: TfrmMain;
implementation
{$R *.dfm}
procedure TfrmMain.FormCreate(Sender: TObject);
begin
//Create each form
FTop:= TForm.Create(nil);
FLeft:= TForm.Create(nil);
FRight:= TForm.Create(nil);
FBottom:= TForm.Create(nil);
//Default position
FTop.Position:= poDefault;
FBottom.Position:= poDefault;
FLeft.Position:= poDefault;
FRight.Position:= poDefault;
//Border Style
FTop.BorderStyle:= bsNone;
FBottom.BorderStyle:= bsNone;
FLeft.BorderStyle:= bsNone;
FRight.BorderStyle:= bsNone;
//Form Style
FTop.FormStyle:= fsStayOnTop;
FBottom.FormStyle:= fsStayOnTop;
FLeft.FormStyle:= fsStayOnTop;
FRight.FormStyle:= fsStayOnTop;
end;
procedure TfrmMain.FormDestroy(Sender: TObject);
begin
FTop.Free;
FBottom.Free;
FLeft.Free;
FRight.Free;
end;
procedure TfrmMain.PositionBorder(const ARect: TRect; const Thickness: Integer; const Color: TColor);
var
Thick: Integer;
HalfThick: Integer;
begin
Thick:= Thickness;
if Thick < 1 then Thick:= 1;
HalfThick:= Thickness div 2;
if HalfThick < 1 then HalfThick:= 1;
//Color
FTop.Color:= Color;
FBottom.Color:= Color;
FLeft.Color:= Color;
FRight.Color:= Color;
//Thickness
FTop.Height:= Thick;
FBottom.Height:= Thick;
FLeft.Width:= Thick;
FRight.Width:= Thick;
//Lengths
FTop.Width:= ARect.Width + Thick;
FBottom.Width:= ARect.Width + Thick;
FLeft.Height:= ARect.Height + Thick;
FRight.Height:= ARect.Height + Thick;
//Positions
FTop.Left:= ARect.Left - HalfThick;
FTop.Top:= ARect.Top - HalfThick;
FBottom.Left:= ARect.Left - HalfThick;
FBottom.Top:= ARect.Bottom + HalfThick;
FLeft.Left:= ARect.Left - HalfThick;
FLeft.Top:= ARect.Top - HalfThick;
FRight.Left:= ARect.Right + HalfThick;
FRight.Top:= ARect.Top - HalfThick;
//Show windows
FTop.Show;
FBottom.Show;
FLeft.Show;
FRight.Show;
end;
procedure TfrmMain.HideBorder;
begin
FLeft.Hide;
FTop.Hide;
FRight.Hide;
FBottom.Hide;
end;
function TfrmMain.FormRect: TRect;
begin
Result.Left:= Left;
Result.Top:= Top;
Result.Width:= Width;
Result.Height:= Height;
end;
procedure TfrmMain.Button1Click(Sender: TObject);
begin
PositionBorder(FormRect, 5, clGreen);
end;
end.
Form: uMain.dfm
object
frmMain: TfrmMain
Left = 315
Top = 113
Caption = 'frmMain'
ClientHeight = 204
ClientWidth = 368
Color = clBtnFace
Font.Charset = DEFAULT_CHARSET
Font.Color = clWindowText
Font.Height = -11
Font.Name = 'Tahoma'
Font.Style = []
OldCreateOrder = False
Position = poScreenCenter
OnCreate = FormCreate
OnDestroy = FormDestroy
PixelsPerInch = 96
TextHeight = 13
object Button1: TButton
Left = 64
Top = 80
Width = 209
Height = 25
Caption = 'Button1'
Default = True
TabOrder = 0
OnClick = Button1Click
end
end
That's assuming that you have a button Button1
. The call is like...
PositionBorder(WindowRect, 5, clGreen);
...where WindowRect = a TRect record with coordinates of the window to be "framed", 5 is the thickness of this frame, and clGreen is the color of the frame.