Search code examples
delphidelphi-xerad-studio

Create a borderless form without losing Windows commands


I've changed my form to a borderless form, I just changed the BorderStyle property to bsNone, but now my application loses the windows anchor and some commands like

WIN + ↑ (Align the form Client)
WIN + ↓ (Minimize the form)
WIN + →(Align the form Right)
WIN + ←(Align the form Left)

I've tried to set BorderStyle: bsSizeable and use the below code inside of the FormCreate, but this does not worked:

procedure TfrmBase.FormCreate(Sender: TObject);
begin
  SetWindowLong(Handle
               ,GWL_STYLE
               ,GetWindowLong(Handle, GWL_STYLE)
                AND (NOT WS_CAPTION)
                AND (NOT WS_THICKFRAME)
               );


  Refresh;
  FormColor := oLauncher.oCor;
end;

This results:

My form

The image above is what I want, but the Windows commands that I already have mentioned don't work

Have any way to set the BorderStyle: bsNone and don't lose these commands?

EDITED

If I use the WS_THICKFRAME my form returns a little top border and the windows commands works well, but I don't want that top border.

My form2

EDITED 2

I got very close to the expected result, but have a little problem yet...

I put this on my FormCreate

SetWindowLong(Handle
             ,GWL_STYLE
             ,GetWindowLong(Handle, GWL_STYLE)
              AND (NOT WS_CAPTION)
              );

And I create the method

private
   procedure WmNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;

and then

procedure TfrmBase.WmNCCalcSize(var Msg: TWMNCCalcSize);
begin
  inherited;
  if Msg.CalcValidRects then
  begin
    InflateRect(Msg.CalcSize_Params.rgrc[0], 0, 6);
    Msg.Result := 0;
  end;
end;

I got this method here

Now the border has disappeared, but when my Form loses the focus, the top / bottom border is shown again....

How can I avoid this?

enter image description here


SOLVED

I left the border as BorderStyle: bsSizeable, then I did it:

private
  procedure WmNCCalcSize(var Msg: TWMNCCalcSize); message WM_NCCALCSIZE;
[...]
procedure TfrmBase.WmNCCalcSize(var Msg: TWMNCCalcSize);
var
  R: TRect;
begin
  if not Msg.CalcValidRects then
    R := PRect(Msg.CalcSize_Params)^;
  inherited;
  if Msg.CalcValidRects then
    Msg.CalcSize_Params.rgrc0 := Msg.CalcSize_Params.rgrc1
  else
    PRect(Msg.CalcSize_Params)^ := R;

  Msg.Result := 0;
end;

procedure TfrmBase.FormCreate(Sender: TObject);
begin
  BorderStyle := bsNone;
  SetWindowLong(Handle
               ,GWL_STYLE
               ,WS_CLIPCHILDREN or WS_OVERLAPPEDWINDOW
               );
end;

procedure TfrmBase.FormShow(Sender: TObject);
begin
  Width := (Width - 1);
end;

Solution at GitHUB

I've create a repository here


Solution

  • Some of the commands you refer to are system commands related to sizing of the window. That requires the thick frame, without it "WIN + right" and "WIN + left" won't work. Additionally you need the minimize box and the maximize box for the WIN + up/down commands to work.

    Best is to start from scratch and include the styles you need, otherwise VCL might interfere. If there's a possibility of your form to be recreated, put styling in a CreateWnd override.

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      BorderStyle := bsNone;
      SetWindowLong(Handle, GWL_STYLE, WS_CLIPCHILDREN or WS_OVERLAPPEDWINDOW);
    end;
    


    Then there's the frame that you don't want. In an edit in the question you inflate the client rectangle to get rid of it. Don't guess the frame width/height, do it like the below.

    procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
    var
      R: TRect;
    begin
      if not Message.CalcValidRects then
        R := PRect(Message.CalcSize_Params)^;
      inherited;
      if Message.CalcValidRects then
        Message.CalcSize_Params.rgrc0 := Message.CalcSize_Params.rgrc1
      else
        PRect(Message.CalcSize_Params)^ := R;
      Message.Result := 0;
    end;
    

    Reading the documentation for the message is mandatory at this point, the parameters have different meanings at different stages, etc..


    The above leaves a window without any non-client area at all. The client rectangle is equal to the window rectangle. Although the caption is not visible, you can activate the system menu by pressing Alt+Space. The problem is, the system insists on drawing activation state. Now it draws a frame in the client area!!

    Get rid of it by intercepting WM_NCACTIVATE, you also need it to draw your title according to the activation status:

    procedure TForm1.WMNCActivate(var Message: TWMNCActivate);
    begin
      if Message.Active then
        // draw active caption
      else
        // draw incactive caption
    
      // don't call inherited
    end;
    


    You might have to deal with some glitches, messing up with the window has consequences. In my test, the minimized form does not have an associated icon in the alt+tab dialog for instance.



    Below is my test unit in full.

    unit Unit1;
    
    interface
    
    uses
      Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants,
      System.Classes, Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs;
    
    type
      TForm1 = class(TForm)
        procedure FormCreate(Sender: TObject);
      protected
        procedure WMNCActivate(var Message: TWMNCActivate); message WM_NCACTIVATE;
        procedure WMNCCalcSize(var Message: TWMNCCalcSize); message WM_NCCALCSIZE;
      end;
    
    var
      Form1: TForm1;
    
    implementation
    
    {$R *.dfm}
    
    procedure TForm1.FormCreate(Sender: TObject);
    begin
      BorderStyle := bsNone;
      SetWindowLong(Handle, GWL_STYLE, WS_CLIPCHILDREN or WS_OVERLAPPEDWINDOW);
    end;
    
    procedure TForm1.WMNCActivate(var Message: TWMNCActivate);
    begin
      if Message.Active then
        // draw active caption
      else
        // draw incactive caption
    
      // don't call inherited
    end;
    
    procedure TForm1.WMNCCalcSize(var Message: TWMNCCalcSize);
    var
      R: TRect;
    begin
      if not Message.CalcValidRects then
        R := PRect(Message.CalcSize_Params)^;
      inherited;
      if Message.CalcValidRects then
        Message.CalcSize_Params.rgrc0 := Message.CalcSize_Params.rgrc1
      else
        PRect(Message.CalcSize_Params)^ := R;
      Message.Result := 0;
    end;
    
    end.