Search code examples
delphivcldelphi-10-seattle

Keep form on top of other application windows


I'm trying to show a VCL form which stays on top of all other applications so that data can be copied from this form into another application without the two applications flicking between which one is in front.

I can show a form and keep it on top of every other application and interact with the other application using fsStayOnTop and opening it using this code:

form := TForm2.Create(nil);

SetWindowPos(form.Handle, // handle to window
    HWND_TOPMOST, // placement-order handle
    form.Left, // horizontal position
    form.Top, // vertical position
    form.Width, form.Height,
    // window-positioning options
    SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);

form.Show();

The problem I currently have is that when I click on my form, to copy and paste the data out of it, the other forms for my application are also brought forward which hides the application I'm copying the data to. The main application form opens a TForm1 using ShowModal which can then open TForm2 using the above code which I thought would keep it bringing the TForm1 forward since it shouldn't have an owner or parent.

I have looked at this question How can get my form to be on top of everything all the time? and unfortunately it doesn't stop the other forms from being brought forward.

So the ordering of the windows I would like when TForm2 is focused is:

mainform
TForm1
Whatever application, normally word
TForm2

Instead I'm getting

Whatever application, normally word
mainform
TForm1
TForm2

I know that this functionality seems a little weird but it is important to improving the usability, mainly on single monitor machine, as the user could be flicking between my form and the other application quite frequently.

I'm currently using Delphi 10 Seattle on Windows 10 Professional 64-bit in case that helps.

Super simple example

(if there's a better way to provide said example I'd love to know):

Form1 Button

procedure TForm1.Button1Click(Sender: TObject);
var
    form2: TForm2;
begin
    form2 := TForm2.Create(self);
    form2.ShowModal();
end;

Form2 Button

procedure TForm2.Button1Click(Sender: TObject);
var
    form3: TForm3;
begin
    form3 := TForm3.Create(nil);

    SetWindowPos(form3.Handle, // handle to window
        HWND_TOPMOST, // placement-order handle
        form3.Left, // horizontal position
        form3.Top, // vertical position
        form3.Width, form3.Height,
        // window-positioning options
        SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);

    form3.Show();
end;

Form3: FormStyle = fsStayOnTop


Solution

  • Thanks to Nat's suggestion to hide the other forms I have come up with this solution making use of the information found from How to get a list from all opened forms of my software?

    Simple Solution

    procedure TForm2.Button1Click(Sender: TObject);
    var
        form3: TForm3;
        ii: integer;
    begin
        for ii := 0 to Screen.FormCount - 1 do
            Screen.Forms[ii].Visible := false;
    
        form3 := TForm3.Create(nil);
    
        SetWindowPos(form3.Handle, // handle to window
            HWND_TOPMOST, // placement-order handle
            form3.Left, // horizontal position
            form3.Top, // vertical position
            form3.Width, form3.Height,
            // window-positioning options
            SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
    
        form3.ShowModal();
        form3.Free();
    
        for ii := 0 to Screen.FormCount - 1 do
            Screen.Forms[ii].Visible := true;
    end;
    

    The idea is to simply loop through all the currently displayed forms using Screen.Forms/Screen.FormCount and make them invisible and then when we're done make them all visible again.

    Extended Solution

    If your forms are created using Application.CreateForm() and are just showed and closed but never freed then this will open all of those forms as well. The solution I've found is to store a list of the visible forms and make only those visible at the end.

    procedure TForm2.Button1Click(Sender: TObject);
    var
        form3: TForm3;
        ii: integer;
        visibleForms: TList<TForm>;
    begin
        visibleForms := TList<TForm>.Create();
        try
            for ii := 0 to Screen.FormCount - 1 do
            begin
                if Screen.Forms[ii].Visible then
                    visibleForms.Add(Screen.Forms[ii]);
                Screen.Forms[ii].Visible := false;
            end;
    
            form3 := TForm3.Create(nil);
    
            SetWindowPos(form3.Handle, // handle to window
                HWND_TOPMOST, // placement-order handle
                form3.Left, // horizontal position
                form3.Top, // vertical position
                form3.Width, form3.Height,
                // window-positioning options
                SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
    
            form3.ShowModal();
            form3.Free();
        finally
            for ii := 0 to visibleForms.Count - 1 do
                visibleForms[ii].Visible := true;
            visibleForms.Free();
        end;
    end;