jueves, 30 de junio de 2016

Creating a Yosemite shadow for borderless form in Delphi [part 3]


yosemiteshadow

In this part we are going to use the shadow form from out project source code and main form.


program ShadowDemo;

{$R *.dres}

uses
   Vcl.Forms,
   Yosemite in 'Yosemite.pas' {Form1},
   FormShadow in 'FormShadow.pas' {frmShadow};

{$R *.res}

begin
   Application.Initialize;
   Application.MainFormOnTaskbar := True;
   Application.CreateForm(TForm1, Form1);
  Application.CreateForm(TfrmShadow, frmShadow);
   Application.Run;
end.

We must make sure that our FormShadow.pas is included in the Uses section of our project code and also in the main form. As well we need it to be created after our main form as shown in the code above.

procedure TForm1.FormCreate(Sender: TObject);
begin
   BorderStyle := bsNone;
   Position := poScreenCenter;
   Application.OnActivate := OnFocus;
   Application.OnDeactivate := OnUnfocus;
   Constraints.MinHeight := 160;
   Constraints.MinWidth := 160;
   DoubleBuffered := True;
end;

Let’s start with the FormCreate procedure, where we set our main form with no border style, assign OnActivate and OnDeactivate procedures that are going to be crucial for our main form to be assigned with the correct shadow if it is focused or we are not using this window.

procedure TForm1.OnFocus(Sender: TObject);
begin
   frmShadow.ActivateShadow := True;
end;

procedure TForm1.OnUnfocus(Sender: TObject);
begin
   frmShadow.ActivateShadow := False;
end;

Setting ActivateShadow to true or false, will immediately update the shadow type.

procedure TForm1.FormShow(Sender: TObject);
begin
   frmShadow.ClientWidth:=ClientWidth+frmShadow.Margins.Left+frmShadow.Margins.Right;
   frmShadow.ClientHeight:=ClientHeight+frmShadow.Margins.Top+frmShadow.Margins.Bottom;
   frmShadow.Left := Self.Left - frmShadow.Margins.Left;
   frmShadow.Top := Self.Top - frmShadow.Margins.Top;
   ShowWindow(frmShadow.Handle, SW_SHOWNA);
   frmShadowEnabled := True;
end;

We must prepare the size and position of the shadow form in the FormShow procedure, as you noticed it, we are using the frmShadow.Margins to configure the shadow width and height, as well its left and top position. Following we will show the shadow form and tell that frmShadowEnabled is TRUE, that will be necessary for other procedures that will need to read frmShadow values, and if they try to read them before the shadow form is created, a read error will raise.

For example, we will also need to update the shadow form size when we resize our main form, so the FormResize procedure will be available as soon as the form is created, and even before the formShadow is created.

procedure TForm1.FormResize(Sender: TObject);
begin

  if WindowState = wsMaximized then
   begin
     ShowWindow(frmShadow.Handle, SW_HIDE);
   end
   else if WindowState = wsNormal then
   begin
     try
       if frmShadowEnabled then
       begin
        if not IsWindowVisible(frmShadow.Handle) then
         begin
            ShowWindow(frmShadow.Handle, SW_SHOWNA);
         end;
         frmShadow.ClientWidth:=ClientWidth+frmShadow.Margins.Left+frmShadow.Margins.Right;
         frmShadow.ClientHeight:=ClientHeight+frmShadow.Margins.Top+frmShadow.Margins.Bottom;
       end;
     except
     end
;
   end
   else if WindowState = wsMinimized then
   begin
     ShowWindow(frmShadow.Handle, SW_HIDE);
   end;

  Repaint;
end;

This resize procedure will update the size of the shadow form too, but when calling IsWindowVisible function, if the shadow form is not created yet, it will fail, so we avoid that error by using the frmShadowEnabled as a boolean help.

Finally, we need to make sure our shadow form to stick firmly to our main form, so we will use the WMMove function which reacts to a window move message.

protected
    procedure WMMove(var Msg: TWMMove); message WM_MOVE;


procedure TForm1.WMMove(var Msg: TWMMove);
begin
   inherited;

  if frmShadowEnabled then
   begin
     frmShadow.Left := Self.Left - frmShadow.Margins.Left;
     frmShadow.Top := Self.Top - frmShadow.Margins.Top;
   end;
end;

And that’ll do, making sure that the form shadow is ready, we can update its Top and Left values accordingly.

Of course there are other procedures that we will need to add, such us those required to resize a borderless window, drag it, etc. I have mentioned some of them in my other blog post about creating a Metro like window in Delphi. You can also see some of them in the source code of this tutorial.

Animation

Conclusion

This is a simple method to create a custom shadow for our windows, specially for borderless windows, giving it back a nice shadow instead of nothing or the very basic CS_DROPSHADOW style (those applied to contextual menus).

However, there are some disadvantages, like creating many more extra forms for other borderless forms in our application, but converting the shadow to a Delphi component will be enough.

Another issue that it presents, is that it flickers while resizing our window, not sure how to fix it, but at least it is working fine.

Finally

The source code and executable demo can be downloaded here.

Hope you liked it, and if you have more experience about this topic, feel free to leave a comment to improve this method I found by “write and try”.

<— Creating a Yosemite shadow for borderless form in Delphi [part 2]