将Windows窗体设置为最底层

一些背景

我当前的一个客户运行一系列互联网点,客户通过PC访问网络:设置为“信息亭”(定制应用程序“锁定”计算机直到用户登录,并且正在运行的帐户是通过Windows组策略严格限制)。 目前,每台计算机都运行Windows XP并使用Active Desktop在后台显示广告。 但是,由于我的客户端每天都遇到Active Desktop崩溃的问题(除了通常会减慢计算机的速度),我还是被要求开发一个替换它的应用程序。

问题

我正在尝试调查是否可以构建一个始终保留在后台的Windows窗体应用程序(使用C#)。 应用程序应位于桌面上方(以便它覆盖任何图标,文件等),但始终位于所有其他正在运行的应用程序之后。 我想我真的在寻找Form类的BottomMost属性(当然不存在)。

任何有关如何实现这一目标的提示或指示都将受到高度赞赏。

这不是.NET Form类直接支持的,因此您有两个选择:

1)使用Win32 API SetWindowPos函数。

pinvoke.net显示了如何在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; 

所以在你的代码中,调用:

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

正如您所评论的那样,这会将表单移动到z顺序的底部,但不会将其保留在那里。 我能看到的唯一解决方法是从Form_LoadForm_Activate事件中调用SetWindowPos 。 如果您的应用程序最大化并且用户无法移动或最小化表单,那么您可能会使用这种方法,但它仍然是一种黑客攻击。 如果在进行SetWindowPos调用之前将表单放到z顺序的前面,用户也可能会看到轻微的“闪烁”。


2) 子类化表单 ,覆盖WndProc函数并拦截WM_WINDOWPOSCHANGING Windows消息,设置SWP_NOZORDER标志(取自此页面 )。

将窗口设置为桌面的子窗口 (“程序管理器”或“进程”过程)。 我在Windows XP(x86)和Windows Vista(x64)中成功使用了这种方法。

我偶然发现了这种方法,同时寻找一种方法来使屏幕保护程序显示就好像它是壁纸一样。 事实certificate,这是内置于系统的.scr处理程序。 您使用screensaver.scr /p PID ,其中PID是要附加到的另一个程序的进程ID。 所以写一个程序来找到progman的句柄,然后调用.scr作为/ p参数,你有屏幕保护程序壁纸!

我现在正在玩的项目是桌面状态显示(显示时间,一些任务,安装的磁盘等),它建立在Strawberry Perl和普通Win32 APIS(主要是Win32 :: GUI和Win32 :: API模块)上,因此代码很容易移植或理解任何具有类似Win32 API绑定或访问Windows脚本主机(例如,ActivePerl,Python,JScript,VBScript)的动态语言。 这是生成窗口的类的相关部分:

 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) } 

该程序缓冲输出以防止闪烁,您可能也想要这样做。 我创建了一个DC(设备上下文)和PaintDesktop(你可以使用任何位图只有几行 – CreateCompatibleBitmap,读入文件,并选择位图的句柄作为画笔),然后创建一个保持缓冲区来保持一个干净的副本背景和一个工作缓冲区来组装碎片 – 在每个循环上,在背景中复制,然后绘制线条和刷位图并使用TextOut – 然后将其复制到原始DC,此时它出现在屏幕。

我认为最好的方法是使用激活的事件处理程序和SendToBack方法,如下所示:

 private void Form1_Activated(object sender, EventArgs e) { this.SendToBack(); } 

是的,带有标志HWND_BOTTOM的函数SetWindowPo应该可以帮到你。 但是,根据我的经验:即使在调用SetWindowPos作为一些用户操作的结果后,您的窗口也可能会被带到前面。

子类化表单,覆盖WndProc函数并拦截负责在激活z-order时将其向上移动的Windows消息。

创建一个覆盖表单的面板,但是在Panel上你想要什么,然后在Panel的Click-Event中写下this.sendback。

使用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)); }