views:

134

answers:

4

I have a form (TBigForm in the example below) which allows to manipulate some complex data and needs additional information to be shown. I put this info in a fsStayOnTop form (OnTopForm in the example) to ensure it's always visible but can be moved out of the way if necessary. Now when some user action in TBigForm shows a modal form this often gets hidden behind OnTopForm which makes the app look frozen. How can I avoid this? (Searching yields many, many hits but I wasn't able to distill a solution out of them.)

In my real app there are a lot of place where modal forms are shown, so I would like to avoid changing all of these calls.

Example: Create a new VCL application, drop a TButton on Form1, double-click the button and replace the generated Button1Click implementation stub with the following:

type
  TBigForm = class(TForm)
  strict private
    OnTopForm: TForm;
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  protected
    procedure DoHide; override;
    procedure DoShow; override;
  public
    constructor Create(AOwner: TComponent); override;
  end;

{ TBigForm }

procedure TBigForm.Button1Click(Sender: TObject);
begin
  ShowMessage('Test');
end;

constructor TBigForm.Create(AOwner: TComponent);
begin
  inherited CreateNew(AOwner);

  Caption := 'Big form';
  WindowState := wsMaximized;

  Button1 := TButton.Create(Self);
  Button1.Parent := Self;
  Button1.Caption := 'Freeze!';
  Button1.SetBounds(10, 10, 100, 100);
  Button1.OnClick := Button1Click;
end;

procedure TBigForm.DoHide;
begin
  OnTopForm.Free;
  inherited DoHide;
end;

procedure TBigForm.DoShow;
begin
  inherited DoShow;
  OnTopForm := TForm.Create(Self);
  OnTopForm.Caption := 'Important information';
  OnTopForm.BorderStyle := bsToolWindow;
  OnTopForm.FormStyle := fsStayOnTop;
  OnTopForm.Position := poScreenCenter;
  OnTopForm.Show;
end;

{ TForm1 }

procedure TForm1.Button1Click(Sender: TObject);
var
  f: TBigForm;
begin
  f := TBigForm.Create(nil);
  try
    f.ShowModal;
  finally
    f.Free;
  end;
end;

Start the app, click on "Button1" and then on "Freeze!".

(BTW: We use D2007.)

A: 

Change temporarily the FormStyle of your OnTopform before displaying another Form as Modal:

procedure TBigForm.Button1Click(Sender: TObject);
begin
  OnTopForm.FormStyle := fsNormal;
  ShowMessage('Test');
  OnTopForm.FormStyle := fsStayOnTop;
end;

It should work for what you want...

François
Thanks! This seems to work, but it requires to manipulate each and every call to ShowModal, ShowMessage etc. - I'd like to avoid that. Using Application.OnModalBegin doesn't help here because it's only called once if you nest ShowModal calls. :-(
Ulrich Gerhardt
+2  A: 

Try setting the modal Form's PopupParent property to be the StayOnTop Form, or set the Application.ModalPopupMode property to something other than pmNone, prior to calling ShowModal().

Remy Lebeau - TeamB
The former would change the window hierarchy of the application - the StayOnTop form would stay under the modal form. And the latter also wouldn't help since 'ShowMessage' is called from the modal form, but the StayOnTop form is a different form and it will still stay on top.
Sertac Akyuz
You canot have it both ways. You say the problem is the modal window getting stuck behind the StayOnTop window. So obviously the modal window would need to be shown on top of it instead. Otherwise, just move the modal window to an area outside of the StayOnTop window before calling ShowModal(). As for ShowMessage(), it displays a modal TForm, and as such is subject to the TApplication.ModalPopupMode property.
Remy Lebeau - TeamB
@Remy - As I see it, the problem is not the modal form getting stuck under the StayOnTop form, it is that the ShowMessage form is getting stuck under the StayOnTop form. And since the ShowMessage is called from the Modal form and **not** from the StayOnTop form, it will not get owned by the StayOnTop form.. with any ModalPopupMode...
Sertac Akyuz
A: 

Here your goody

Create an global TApplicationEvents
Declare an global var to keep track of modal form count
Hookup the OnMessage

var
  Ctrl: TControl;

if Msg.hwnd <> 0 then
  case Msg.message of
    CM_ACTIVATE,
    CM_DEACTIVATE:
    begin
      Ctrl := FindControl(Msg.hwnd);
      if Ctrl is TForm then
        if fsModal in TForm(Ctrl).FormState then
        begin  
          if Msg.message = CM_ACTIVATE then
            Inc(Modal form count var)
          else
            Dec(Modal form count var);

          add more logic based on Modal form count var
        end;
    end;
  end;

Have fun

APZ28
I can't get the Inc/Dec lines to be called - Ctrl is always nil. Have you tried this in my example app?
Ulrich Gerhardt
A: 
procedure TForm1.ScreenOnActiveFormChange(Sender: TObject);
begin
  if (Screen.ActiveForm <> nil) then
  begin
    if (Screen.ActiveForm.Handle <> Application.MainForm.Handle) then
      with Screen.ActiveForm do
        SetWindowPos(Handle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE or SWP_NOSIZE);
    Windows.SetForeGroundWindow(Screen.ActiveForm.Handle);
  end;
end;

This should work.

Jerry Claxton