Title: Make a form TopMost or BottomMost in C#
Keeping a form on top of other windows is simple. Just set the form's TopMost property to true.
Keeping a form below other windows is harder. To do that, the program must override its WndProc method, look for messages that might move the form to the top, and use SetWindowPos to move the form to the bottom when they occur.
This program starts with the following API declarations.
using System.Runtime.InteropServices;
...
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool SetWindowPos(
IntPtr hWnd, IntPtr hWndInsertAfter,
int x, int y, int cx, int cy, uint uFlags);
// Constants for detecting messages.
private const int WM_WINDOWPOSCHANGING = 0x0046;
// Constants for positioning the window.
private IntPtr HWND_BOTTOM = (IntPtr)1;
private const UInt32 SWP_NOMOVE = 0x0002;
private const UInt32 SWP_NOSIZE = 0x0001;
This code declares the SetWindowPos API function used to move the form to the bottom of the window stacking order. It defines the messages that the program must look for and the values it passes to SetWindowPos to move the window to the bottom.
The following code shows the form's overridden WndProc method.
protected override void WndProc(ref Message m)
{
// See if we should be bottommost.
if (radBottommost.Checked)
{
if (m.Msg == WM_WINDOWPOSCHANGING)
{
// We're being activated. Move to the bottom.
MoveToBottom();
m.Result = (IntPtr)0;
}
}
// Handle the message normally.
base.WndProc(ref m);
}
This method executes when the form receives a message from Windows. If the Bottommost radio button is checked, the code examines the message to see what message the window is receiving. The WM_WINDOWPOSCHANGING message indicates that the window's size, location, or position in the stacking order is changing. In that case, the code calls the following MoveToBottom method.
private void MoveToBottom()
{
UInt32 flags = SWP_NOSIZE | SWP_NOMOVE;
if (!SetWindowPos(this.Handle, HWND_BOTTOM, 0, 0, 0, 0, flags))
Console.WriteLine("Error in SetWindowPos");
}
This code calls SetWindowPos, passing it the HWND_BOTTOM value to make it move the form to the bottom of the window stacking order. It also passes the flags SWP_NOSIZE and SWP_NOMOVE to indicate that SetWindowPos should not resize or move the form.
When the user clicks any of the radio buttons, the the following code executes.
private void radOption_Click(object sender, EventArgs e)
{
this.TopMost = radTopmost.Checked;
if (radBottommost.Checked) MoveToBottom();
}
If the Topmost button is checked, the code simply sets the form's TopMost property.
If the Bottommost radio button is checked, the code calls MoveToBottom to immediately move the form to the bottom.
This code works ... mostly. Suppose you make the form BottomMost. If you click and drag its title bar, it remains on the bottom of the stacking order. If you drag one of the form's borders to resize it, it also remains on the bottom of the stacking order.
If you click on the form's title bar, it also remains on the bottom. However, if you click on the title bar again, the form pops to the top. I think the problem is that the form is already activated so clicking it again doesn't activate it and that's where the WM_WINDOWPOSCHANGING message originates.
I have scoured the internet and tried all sorts of solutions, and none of them seem to handle this case. Perhaps it's a change in Windows 10.
For now, I'm going to call this a feature. The program remains on the bottom most of the time, but there's still a way to bring it to the top if you really must. To put it back on the bottom, either add a button to the program to do that, or drag the form slightly.
If you find a solution to this problem, please post a comment below.
Download the example to experiment with it and to see additional details.
|