65.9K
CodeProject 正在变化。 阅读更多。
Home

如何使用 PInvoke 禁用其他应用程序窗口的关闭按钮

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (9投票s)

2013年6月25日

CPOL

6分钟阅读

viewsIcon

25177

downloadIcon

649

借助 PInvoke,可以操作属于其他应用程序的窗体。

引言

我曾经为一家公司工作,该公司开发了一个应用程序,该应用程序作为 OS/2 进程在 Windows 服务器上的控制台窗口中运行。应用程序的用户不习惯这种类型的窗口,偶尔会有人通过点击“x”关闭按钮来关闭窗口。现在,该应用程序必须正确终止,否则会导致各种文件和数据库条目损坏,因此每次发生这种情况时,都会给我们支持团队带来大量工作。

所以我用 VB6 编写了一个小型应用程序(是的,那已经是很久以前的事了),它使用 PInvoke 来禁用 OS/2 窗口上的关闭按钮以避免这种情况。

昨天,在问答区有一个关于类似场景的问题,所以我认为我应该借此机会查看我以前的代码并将其改编为 .NET,这次是用 C#。

背景

那么,PInvoke 到底是什么?我在 Dave Amenta 的博客上找到了一个非常好的解释。而且,既然我无法更好地解释它,我将直接从博客中复制该解释。

P/Invoke,或 Pinvoke,代表 Platform Invocation Services。PInvoke 是 Microsoft .NET Framework 的一项功能,允许开发人员调用动态链接库 (DLL) 内的本机代码。在 PInvoke 时,.NET Framework(或公共语言运行时)将加载 DLL 并自动处理类型转换。P/Invoke 最常见的用途是使用 Win32 API 中包含的 Windows 功能。Windows API 非常广泛,只有部分功能被封装在 .NET 库中。例如,Form.Show(); 实际上是 shell32.dll 中 ShowWindow() API 的包装器。

请访问他的博客了解更多详情。

.NET 框架现在已经包装了许多 PInvoke 方法,不再需要直接访问它们。但是,有些功能仍然需要,在这种情况下,知道“旧”方法仍然有效是很有用的。

Using the Code

为了实现我们的目标并禁用另一个窗口上的关闭按钮,我们首先需要找到窗口句柄。

在 .NET 中,列出系统上运行的进程并查找它们的句柄非常容易。实际上,在过去,您甚至需要使用 PInvoke 来完成此操作,所以这里有一个 .NET 框架中已包装的示例,可帮助开发人员。

在我附带的示例应用程序中,我枚举了所有正在运行的进程,并将那些拥有窗口句柄(因为那些是我们可以操作的具有 UI 窗口的进程)的进程列在一个 ListView 中。

using System.Diagnostics;
Process[] processlist = Process.GetProcesses();
foreach (Process process in processlist)
{
    //Add process to listview
}   

Process 对象包含我们需要操作窗口的 MainWindowHandle

现在,要禁用关闭按钮,我们实际上并不直接操作按钮本身。我们所做的是操作窗口的系统菜单。系统菜单是在点击标题栏左上角的应用程序图标时弹出的菜单。

这是记事本的系统菜单(抱歉,是瑞典语)。

系统菜单中的最后一个菜单项是“关闭”。如果我们禁用(或完全移除)该项,我们将同时禁用关闭按钮。这实际上很好,因为我们不希望禁用关闭按钮,却让用户可以通过系统菜单上的关闭菜单项来关闭窗口。

当我们获得窗口句柄后,我们需要使用它来查找系统菜单句柄。首先,我们需要声明所需的 PInvoke 方法。

[DllImport("user32.dll")]
static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert); 

然后我们可以使用它来查找系统菜单句柄。

IntPtr sysMenuPtr = GetSystemMenu(mainWindowPtr, false); 

有了这个,我们就可以禁用它了。首先,我们需要定义另外两个 PInvoke 方法。

[DllImport("user32.dll")]
static extern bool EnableMenuItem(IntPtr hMenu, uint uIDEnableItem, uint uEnable);

[DllImport("user32.dll")]
static extern bool DrawMenuBar(IntPtr hWnd); 

第一个设置菜单项的启用/禁用状态,第二个在更改后刷新菜单。

EnableMenuItem 接受一些参数。第一个参数 hMenu 是要更改的菜单的句柄。

最后一个参数(先说这个),uEnable - 就像你可能以为的那样 - 并非仅仅是一个启用或未启用的布尔值。它是一个标志变量,用于向方法发送有关您实际想做什么的信息。

第二个参数 uIDEnableItem 标识我们要更改的 menuitem。它可以是已知的常量 ID,也可以是菜单中索引标识的项。在我们的例子中,我们已经知道我们要禁用“关闭”项,所以我们可以直接指定该项,而不管它在菜单中的位置。

我们通过指定其常量 ID SC_CLOSE 来做到这一点,并在 uIDEnableItem 中使用菜单项 ID 调用 EnableMenuItem,并将 MF_BYCOMMANDMF_GRAYED 作为 uEnable 标志发送,以让方法知道我们指定了菜单项 ID 而不是其索引,并将其灰色显示并禁用该项。

private const int MF_BYCOMMAND = 0x0;
private const int MF_BYPOSITION = 0x400;
private const int MF_REMOVE = 0x1000;
private const int MF_ENABLED = 0x0;
private const int MF_GRAYED = 0x1;
private const int MF_DISABLED = 0x2;
private const int SC_CLOSE = 0xF060; 

要禁用该项,我们然后调用:

EnableMenuItem(CurrentSystemMenuHandle, SC_CLOSE, MF_BYCOMMAND | MF_GRAYED);
DrawMenuBar(CurrentSystemMenuHandle);  

如果我们愿意,我们也可以通过索引来禁用它。在这种情况下,我们将调用:

EnableMenuItem(CurrentSystemMenuHandle, 6, MF_BYPOSITION | MF_GRAYED);  

其中 6 是关闭项的索引(即使是分隔符也有索引)。

当然,如果有人操纵和重新排列了菜单项,这就会失效……

注意:这将禁用系统菜单中的“关闭”菜单项以及右上角的“关闭”按钮。但通常,应用程序会有一个可能包含关闭功能的菜单。那个不会被禁用。我们也不希望那样做,因为那个提供了用户正确关闭应用程序的选项。

记事本的文件菜单,您可以在其中正确关闭应用程序(也是瑞典语 - 抱歉!)。

Notepad File Menu

我创建了一个演示应用程序,它可以启用和禁用关闭功能,如果您愿意,还可以完全移除该选项。它还展示了如何获取关闭菜单项的当前状态。

PInvoke 可用于以许多有趣的方式操作其他窗口,如其大小、位置、最小化和最大化属性、Zorder,并使它们保持在最前面或不保持。

在演示应用程序中,我还展示了如何将窗口设置为最顶层并再次将其移除。

有用

在使用 PInvoke 时,这是一个有用的网站,它描述了大多数方法: http://www.pinvoke.net/

请注意

本文的目的是完全按照我上面描述的方式进行。您可以自由添加新功能,但请不要发送给我大量建议,例如“您应该真的添加这个和那个功能……” - 这只是对一些方法的演示,我不会向本文添加任何其他功能。

历史

  • 2013 年 6 月 25 日:版本 1.00 - 首次发布
© . All rights reserved.