Question

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.

Was it helpful?

Solution

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:

Border around window

  1. Each window's border style should be bsNone
  2. Each window's form style should be fsStayOnTop
  3. Each window should be positioned around each edge of the focused form

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top