Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Show a Form behind everything else, and without stealing focus?

Tags:

c#

.net

winforms

There's an existing question on StackOverflow on how to show a form without stealing focus. The answer is override ShowWithoutActivation and return true:

protected override bool ShowWithoutActivation
{
  get { return true; }
}

This works well enough.

Now i want to go one step further. i want a show a Form (i.e. make it visible), but have it be behind other forms in the z-order.

Possible in .net?

If not, possible with P/Invoke?

Bonus Chatter

Calling SendToBack() doesn't work:

   RunnerForm frm = new RunnerForm();
// frm.Show();
   frm.Visible = true;
   frm.SendToBack();
like image 546
Ian Boyd Avatar asked Feb 17 '12 20:02

Ian Boyd


2 Answers

A little bit of PInvoke using SetWindowPos function

public static class HWND {
  public static readonly IntPtr
  NOTOPMOST = new IntPtr(-2),
  BROADCAST = new IntPtr(0xffff),
  TOPMOST = new IntPtr(-1),
  TOP = new IntPtr(0),
  BOTTOM = new IntPtr(1);
}

public static class SWP {
  public static readonly int
  NOSIZE = 0x0001,
  NOMOVE = 0x0002,
  NOZORDER = 0x0004,
  NOREDRAW = 0x0008,
  NOACTIVATE = 0x0010,
  DRAWFRAME = 0x0020,
  FRAMECHANGED = 0x0020,
  SHOWWINDOW = 0x0040,
  HIDEWINDOW = 0x0080,
  NOCOPYBITS = 0x0100,
  NOOWNERZORDER = 0x0200,
  NOREPOSITION = 0x0200,
  NOSENDCHANGING = 0x0400,
  DEFERERASE = 0x2000,
  ASYNCWINDOWPOS = 0x4000;
}

[DllImport("user32.dll")]
public static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X, int Y, int cx, int cy, int uFlags);

private void button1_Click(object sender, EventArgs e) {
  RunnerForm frm = new RunnerForm();
  SetWindowPos(frm.Handle, HWND.BOTTOM, 0, 0, 0, 0, SWP.SHOWWINDOW | SWP.NOMOVE | SWP.NOOWNERZORDER | SWP.NOSIZE | SWP.NOACTIVATE);
}
like image 158
LarsTech Avatar answered Oct 04 '22 21:10

LarsTech


To add on to the viable solution Lars provided, you could also prevent the user from ever changing the window's Z order altogether.

To do that, you would override the form's WndProc method and catch the WM_WINDOWPOSCHANGING message that is sent to a window when its size, position, or Z order position is about to change. The super cool thing about this message—I think it's one of my favorites—is that it actually allows you to change or modify the parameters of the change that is about to take place.

So in this case, you'll want to set the SWP_NOZORDER flag to prevent the window's Z order from being changed.

The notable thing about this approach is that it will always maintain your window in the Z order at the last position you left it. The user won't be able to bring it to the front, which may or may not be a good thing, depending on how your UI is designed. It will also work just fine with controls other than forms.

Sample code pulled from one of my libraries:

internal class NativeMethods
{
    public const int WM_WINDOWPOSCHANGING = 0x46;
    public const int WM_WINDOWPOSCHANGED = 0x47;

    [Flags()]
    public enum SetWindowPosFlags
    {
        SWP_NOSIZE = 0x1,
        SWP_NOMOVE = 0x2,
        SWP_NOZORDER = 0x4,
        SWP_NOREDRAW = 0x8,
        SWP_NOACTIVATE = 0x10,
        SWP_FRAMECHANGED = 0x20,
        SWP_DRAWFRAME = SWP_FRAMECHANGED,
        SWP_SHOWWINDOW = 0x40,
        SWP_HIDEWINDOW = 0x80,
        SWP_NOCOPYBITS = 0x100,
        SWP_NOOWNERZORDER = 0x200,
        SWP_NOREPOSITION = SWP_NOOWNERZORDER,
        SWP_NOSENDCHANGING = 0x400,
        SWP_DEFERERASE = 0x2000,
        SWP_ASYNCWINDOWPOS = 0x4000,
    }

    public enum WindowZOrder
    {
        HWND_TOP = 0,
        HWND_BOTTOM = 1,
        HWND_TOPMOST = -1,
        HWND_NOTOPMOST = -2,
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct WINDOWPOS
    {
        public IntPtr hWnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public SetWindowPosFlags flags;

        // Returns the WINDOWPOS structure pointed to by the lParam parameter
        // of a WM_WINDOWPOSCHANGING or WM_WINDOWPOSCHANGED message.
        public static WINDOWPOS FromMessage(Message msg)
        {
            // Marshal the lParam parameter to an WINDOWPOS structure,
            // and return the new structure
            return (WINDOWPOS)Marshal.PtrToStructure(msg.LParam, typeof(WINDOWPOS));
        }

        // Replaces the original WINDOWPOS structure pointed to by the lParam
        // parameter of a WM_WINDOWPOSCHANGING or WM_WINDOWPSCHANGING message
        // with this one, so that the native window will be able to see any
        // changes that we have made to its values.
        public void UpdateMessage(Message msg)
        {
            // Marshal this updated structure back to lParam so the native
            // window can respond to our changes.
            // The old structure that it points to should be deleted, too.
            Marshal.StructureToPtr(this, msg.LParam, true);
        }
    }
}

And then I have a slick little subclassed form that raises .NET events corresponding to these messages and allows the event handler to modify the values or cancel the event if desired. I don't handle SWP_NOZORDER, but you can get an idea of how it might work.

public class FormEx : System.Windows.Forms.Form
{
    // ...snip constructors and such          

    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case NativeMethods.WM_WINDOWPOSCHANGING:
                this.WmWindowPosChanging(m);
                return;
            // ...snip
        }

        base.WndProc(m);
    }

    private void WmWindowPosChanging(ref Message m)
    {
        // Extract the WINDOWPOS structure corresponding to this message
        NativeMethods.WINDOWPOS wndPos = NativeMethods.WINDOWPOS.FromMessage(m);

        // Determine if the size is changing (absence of SWP_NOSIZE flag)
        if (!((wndPos.flags & NativeMethods.SetWindowPosFlags.SWP_NOSIZE) == NativeMethods.SetWindowPosFlags.SWP_NOSIZE))
        {
            // Raise the LowLevelSizeChanging event
            SizeChangingEventArgs e = new SizeChangingEventArgs(this.Size, new Size(wndPos.cx, wndPos.cy));
            this.OnLowLevelSizeChanging(e);

            // Determine if the user canceled the size changing event
            if (e.Cancel)
            {
                // If so, add the SWP_NOSIZE flag
                wndPos.flags = wndPos.flags | NativeMethods.SetWindowPosFlags.SWP_NOSIZE;
                wndPos.UpdateMessage(m);
            }
        }

        // Determine if the position is changing (absence of SWP_NOMOVE flag)
        if (!((wndPos.flags & NativeMethods.SetWindowPosFlags.SWP_NOMOVE) == NativeMethods.SetWindowPosFlags.SWP_NOMOVE))
        {
            // Raise the LowLevelPositionChanging event
            PositionChangingEventArgs e = new PositionChangingEventArgs(this.Location, new Point(wndPos.x, wndPos.y));
            this.OnLowLevelPositionChanging(e);

            // Determine if the user canceled the position changing event
            if (e.Cancel)
            {
                // If so, add the SWP_NOMOVE flag
                wndPos.flags = wndPos.flags | NativeMethods.SetWindowPosFlags.SWP_NOMOVE;
                wndPos.UpdateMessage(m);
            }
        }

        base.WndProc(m);
    }

    // ...snip event infrastructure
}

Edit: Hmm, this code was originally written in VB.NET and I ran it through an automatic translator. It looks like the refs didn't get properly inserted everywhere that they should be when making function calls. Follow the urging of the compiler to fix it...

like image 38
Cody Gray Avatar answered Oct 04 '22 22:10

Cody Gray