Search code examples
delphiwindowdrawdesktopinvalidation

Invalidating desktop window for drawing frame around a window


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.


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.