How to get Login Dialogs Appearing on the TaskBar

Most of the applications I have worked on in Delphi are database apps that may present a splash form quickly followed by a login dialog.  If the user fails to authenticate, the application needs to terminate gracefully.  The only way to do so cleanly is to modify the DPR code with some conditional logic. I’ve seen scenarios where after the main form was created the login dialog was invoked and if authentication failed everything was torn down. This complicates the shutdown logic, and often didn’t work well, encouraging a call to Halt() and sometimes leaving the process in memory.

Any long time Delphi user knows that messing with the generated DPR code in Delphi can cause all sorts of grief later when Delphi tries to auto create forms and add units to the uses clause.  That is out of scope for this post, suffice to say that it is possible to write something like this:

var
  User :TUser;
begin
  Screen.Cursor := crAppStart;
  try
    Application.Initialize;
    Application.MainFormOnTaskbar := True;
    Application.Title := 'My Secure App';
    Application.CreateForm(TMainDm, MainDm);
  finally
    Screen.Cursor := crDefault;
  end;
  User := TfrmLogin.Login
  (
    function (const UserName,Password :string) :TObject
    begin
      Result := MainDm.Session.FindWhere<TUser>(
        Restrictions.&And(
          Restrictions.Eq('UserName',UserName),
          Restrictions.Eq('Password',Password)
        )
      ).FirstOrDefault;
    end,
    {
      UserName can be passed as first parameter so don't
      have to type it in all the time
    }
    ParamStr(1),
    3  {credential retries available }
  ) as TUser;
  if User <> nil then
  begin
    Application.CreateForm(TfrmMain, frmMain);
    frmMain.CurrentUser := User;
    Application.Run;
  end;
end.

The problem is that the Login Dialog does not appear on the Windows Taskbar. If it is hidden behind other windows, the user may think the application has not been launched and attempt to start another instance. There is no easy way for the user to bring the login dialog to the foreground short of closing other windows that may be in front of it. Putting the form on the taskbar solves this. As a quick solution I looked at the SetMainForm method in the Vcl.Forms unit, and decided to extract the ChangeAppWindow() procedure since it is not available outside of the Forms unit. Then I simply called it from this event, and voila! a taskbar button showing the Login form.

procedure TfrmLogin.FormCreate(Sender: TObject);
begin
  ChangeAppWindow(handle,True,True);
end;

I’m sure there are reasons why this method is not exposed as a public TApplication class procedure, but perhaps it could be with a usage caveat.

5 Responses to “How to get Login Dialogs Appearing on the TaskBar”

  1. Anders Melander Says:

    or even simpler:

    type
    TfrmLogin = class(TForm)
    protected
    procedure CreateParams(var Params: TCreateParams); override;
    end;

    procedure TfrmLogin.CreateParams(var Params: TCreateParams);
    begin
    inherited;
    Params.ExStyle := Params.ExStyle or WS_EX_APPWINDOW;
    end;

  2. Pawel Says:

    an other way to achive the same

    Form1 = class(TForm)
    {…}
    protected
    procedure CreateParams(var Params: TCreateParams); override;
    end;

    procedure TForm1.CreateParams(var Params: TCreateParams);
    begin
    inherited;

    // not strictly necessary, but if the application already has a main form, then setting the WndParent to 0 would create a second taskBar entry for this form
    if not assigned(application.MainForm) then
    Params.WndParent := 0;
    end;

  3. Larry Hengen Says:

    @Anders,

    Thanks for the info. I think I will change my implementation.

  4. Ian Branch Says:

    Hi Larry,
    This has almost completely resolved an issue for me.
    One last element is needed - Given that the Log in form will be destroyed,

    procedure TLogInForm.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
    Action := caFree;
    end;

    how to get the User ID out of the Login module to the Main Form?

    Ian

  5. Larry Hengen Says:

    @Ian,

    Glad the article was helpful. As you can see in the article source code, my login form is completely generic thanks to an anonymous method which returns a TUser object. If the TUser returned is nil that means the user cancelled the dialog, or they did not successfully authenticate and the program needs to terminate. If the TUser object is not Nil, then it contains the information gathered about the user in the anonymous method. In your case I would define TUser as:

      TUser = class(TObject)
      private
        FUserID :string;
      public
        property UserID :string read FUserID write FUserID;
      end;
    

    then change the anonymous method appropriately. The login form simply passes back the result of the anonymous method in the Login class method and I later pass that User object to the main form via a property.

Leave a Reply