将Windows窗体设置为最底层

时间:2010-01-08 12:31:52

标签: c# .net winforms

一些背景

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

问题

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

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

7 个答案:

答案 0 :(得分:15)

.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顺序的底部,但不会将其保留在那里。我能看到的唯一解决方法是从SetWindowPosForm_Load事件中调用Form_Activate。如果您的应用程序最大化并且用户无法移动或最小化表单,那么您可能会使用这种方法,但它仍然是一种黑客攻击。如果在SetWindowPos调用之前将表单放到z顺序的前面,用户可能会看到轻微的“闪烁”。


2)subclass the form,覆盖WndProc函数并拦截WM_WINDOWPOSCHANGING Windows消息,设置SWP_NOZORDER标志(取自this page)。

答案 1 :(得分:3)

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

我在寻找一种方法使屏幕保护程序显示就好像是壁纸时偶然发现了这种方法。事实证明,这是内置于系统的.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,此时它出现在屏幕。

答案 2 :(得分:2)

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

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

答案 5 :(得分:0)

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

答案 6 :(得分:-2)

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