17

Some background

One of my current clients runs a chain of Internet points where customers an access the net through PC:s set up as "kiosks" (a custom-built application "locks" the computer until a user has signed in, and the running account is heavily restricted through the Windows group policy). Currently, each computer is running Windows XP and uses Active Desktop to display advertisements in the background. However, since my client has got problems with Active Desktop crashing on a daily basis (in addition to generally slowing down the computer) I have been asked to develop an application that replaces it.

The problem

I am trying to investigate whether it is possible to build a Windows forms application (using C#) that always stays in the background. The application should lie above the desktop (so that it covers any icons, files etc) but always behind all other running applications. I guess I'm really looking for a BottomMost property of the Form class (which doesn't exist, of course).

Any tips or pointers on how to achieve this would be highly appreciated.

Anders Fjeldstad
  • 10,724
  • 2
  • 33
  • 50
  • 2
    Remember though, that your window will likely be hidden when the user presses Win+M or Win+D. Also, isn't it easier to just replace Explorer by a custom shell which does pretty much nothing? After all, in a kiosk environment you don't need a taskbar, process switching, desktop icons, &c. – Joey Jan 08 '10 at 12:42
  • Actually, the users can still launch programs through the start menu, switch between them etc, so I think replacing Explorer entirely would be kind of overkill in this particular situation. I see your point though. – Anders Fjeldstad Jan 08 '10 at 12:51
  • Ah, ok, then disregard half of my points :). Still, the issue with Win+D and Win+M will probably remain (as well as right-clicking the task bar and selecting "minimize all windows"). – Joey Jan 08 '10 at 12:54
  • True, that's a valid point and another problem to address. (I'm actually almost hoping to be able to reject this whole assignment with a good explanation for the client of the various difficulties and hackishness of a solution that does what they want it to...) – Anders Fjeldstad Jan 08 '10 at 12:58
  • I always knew this question would find its way on here, lol. Thanks for asking. – jay_t55 May 27 '13 at 06:54

7 Answers7

22

This isn't directly supported by the .NET Form class, so you have two options:

1) Use the Win32 API SetWindowPos function.

pinvoke.net shows how to declare this for use in C#:

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

static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;

So in your code, call:

SetWindowPos(Handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);

As you commented, this moves the form to the bottom of the z-order but doesn't keep it there. The only workaround I can see for this is to call SetWindowPos from the Form_Load and Form_Activate events. If your application is maximized and the user is unable to move or minimise the form then you might get away with this approach, but it's still something of a hack. Also the user might see a slight "flicker" if the form gets brought to the front of the z-order before the SetWindowPos call gets made.


2) subclass the form, override the WndProc function and intercept the WM_WINDOWPOSCHANGING Windows message, setting the SWP_NOZORDER flag (taken from this page).

Community
  • 1
  • 1
Richard Ev
  • 52,939
  • 59
  • 191
  • 278
  • Richard, thanks for your answer. As far as I can see, SetWindowPos allows me to send the form to the back, but it gets pushed to the front whenever it is given focus. I would like it to stay in the back, even when the user is interacting with it. – Anders Fjeldstad Jan 08 '10 at 12:49
  • 1
    I'm marking your answer as accepted since it's "close enough" - from what I've found so far I don't think I can solve this in any less hackish way... Thanks for your time! – Anders Fjeldstad Jan 08 '10 at 18:30
  • I wouldn't consider subclassing the form to be a hackish approach, it's just a little fiddly. – Richard Ev Jan 09 '10 at 16:17
  • 2
    You're right - I actually overlooked option 2, my comment was aimed at the first one. The second works great. Thanks! – Anders Fjeldstad Jan 15 '10 at 14:29
  • For other Electron developers out there: it took a while, but I figured out how to implement options 1 and 2 in NodeJS/Electron as well: https://stackoverflow.com/a/58473299/2441655 – Venryx Oct 20 '19 at 13:47
4

I think the best way to do so is using the activated event handler and SendToBack method, like so:

private void Form1_Activated(object sender, EventArgs e)
{
    this.SendToBack();
}
Professor of programming
  • 2,978
  • 3
  • 30
  • 48
3

Set your window to be a child window of the desktop (the "Program Manager" or "progman" process). I've succeeded with this method in Windows XP (x86) and Windows Vista (x64).

I stumbled on this method while searching for a way to make a screensaver display as if it were wallpaper. It turns out, this is sort of built in to the system's .scr handler. You use screensaver.scr /p PID, where PID is the process id of another program to attach to. So write a program to find progman's handle, then invoke the .scr with that as the /p argument, and you have screensaver wallpaper!

The project I'm playing with now is desktop status display (shows the time, some tasks, mounted disks, etc), and it's built on Strawberry Perl and plain Win32 APIS (mainly the Win32::GUI and Win32::API modules), so the code is easy to port to or understand any dynamic language with similar Win32 API bindings or access to Windows' Scripting Host (eg, ActivePerl, Python, JScript, VBScript). Here's a relevant portion of the class that produces the window:

do { Win32::API->Import(@$_) or die "Win32::API can't import @$_ ($^E)" } for
    [user32 => 'HWND FindWindow(LPCTSTR lpClassName, LPCTSTR lpWindowName)'],
    [user32 => 'HWND SetParent(HWND hWndChild, HWND hWndNewParent)'],

sub __screen_x {
    Win32::GUI::GetSystemMetrics(SM_CXSCREEN)
}
sub __screen_y {
    Win32::GUI::GetSystemMetrics(SM_CYSCREEN)
}
sub _create_window { # create window that covers desktop
    my $self = shift;
    my $wnd = $$self{_wnd} = Win32::GUI::Window->new(
        -width   => __screen_x(), -left => 0,
        -height  => __screen_y(), -top  => 0,
    ) or die "can't create window ($^E)";

    $wnd->SetWindowLong(GWL_STYLE,
        WS_VISIBLE
        | WS_POPUP # popup: no caption or border
    );
    $wnd->SetWindowLong(GWL_EXSTYLE, 
        WS_EX_NOACTIVATE # noactivate: doesn't activate when clicked
        | WS_EX_NOPARENTNOTIFY # noparentnotify: doesn't notify parent window when created or destroyed
        | WS_EX_TOOLWINDOW # toolwindow: hide from taskbar
    );
    SetParent($$wnd{-handle}, # pin window to desktop (bottommost)
        (FindWindow('Progman', 'Program Manager') or die "can't find desktop window ($^E)")
    ) or die "can't pin to desktop ($^E)";
    Win32::GUI::DoEvents; # allow sizing and styling to take effect (otherwise DC bitmaps are the wrong size)
}

This program buffers output to prevent flickering, which you'll probably want to do as well. I create a DC (device context) and PaintDesktop to it (you could use any bitmap with only a couple more lines -- CreateCompatibleBitmap, read in a file, and select the bitmap's handle as a brush), then create a holding buffer to keep a clean copy of that background and a working buffer to assemble the pieces -- on each loop, copy in background, then draw lines and brush bitmaps and use TextOut -- which is then copied to the original DC, at which time it appears on screen.

Anonymous
  • 49,213
  • 1
  • 25
  • 19
  • What would be the process that hosts the Desktop in Windows 8. I tried DWM but my Window didn't attach to it. – bitdisaster Jan 22 '14 at 23:25
  • Making the window a child of Desktop (`progman`) works great. The C# equivalent would be `IntPtr DesktopWindow = FindWindow("Progman", "Program Manager");` to get a pointer to the desktop and `if (DesktopWindow != IntPtr.Zero) { SetParent(hMyWindow, DesktopWindow); }` to make it appear on top of the desktop, but below the task bar. – ingenuine Oct 30 '17 at 10:26
0

Yes, function SetWindowPos with flag HWND_BOTTOM should help you. But, from my experience: even after calling SetWindowPos as result of some user operations your window may bring to front.

Andrii Shvydkyi
  • 2,236
  • 1
  • 14
  • 19
0

subclass the form, override the WndProc function and intercept the Windows message(s) that are responsible for moving it up the z-order when it gets activated.

john
  • 1
0

Create a Panel that cover your form, but what ever you want on that Panel, then in the Panel's Click-Event write this.sendback .

Motta
  • 1
-2

I've managed to get rid of the flickering when using setwindowpos...

const UInt32 SWP_NOSIZE = 0x0001;
const UInt32 SWP_NOMOVE = 0x0002;
const UInt32 SWP_NOACTIVATE = 0x0010;
const UInt32 SWP_NOZORDER = 0x0004;
const int WM_ACTIVATEAPP = 0x001C;
const int WM_ACTIVATE = 0x0006;
const int WM_SETFOCUS = 0x0007;
static readonly IntPtr HWND_BOTTOM = new IntPtr(1);
const int WM_WINDOWPOSCHANGING = 0x0046;

[DllImport("user32.dll")]
static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int X,
   int Y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr DeferWindowPos(IntPtr hWinPosInfo, IntPtr hWnd,
   IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
[DllImport("user32.dll")]
static extern IntPtr BeginDeferWindowPos(int nNumWindows);
[DllImport("user32.dll")]
static extern bool EndDeferWindowPos(IntPtr hWinPosInfo);

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    IntPtr hWnd = new WindowInteropHelper(this).Handle;
    SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);

    IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
    HwndSource src = HwndSource.FromHwnd(windowHandle);
    src.AddHook(new HwndSourceHook(WndProc));
}

private IntPtr WndProc(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    if (msg == WM_SETFOCUS)
    {
        IntPtr hWnd = new WindowInteropHelper(this).Handle;
        SetWindowPos(hWnd, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE);
        handled = true;
    }
    return IntPtr.Zero;
}

private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    IntPtr windowHandle = (new WindowInteropHelper(this)).Handle;
    HwndSource src = HwndSource.FromHwnd(windowHandle);
    src.RemoveHook(new HwndSourceHook(this.WndProc));
}
HrejWaltz
  • 45
  • 1
  • 2
  • You're using a WindowInteropHelper but don't provide it, this is worthless. You shouldn't make a user have to go out and find a wrapper class hoping it's the right one you used. Provide everything. – ScottN Nov 16 '15 at 23:25
  • FYI, this is code for WPF apps. WindowInteropHelper is in `using System.Windows.Interop;` – javon27 Apr 11 '18 at 23:01