如何摆脱控制台 Window 的关闭按钮?

How to get rid of the Close Button of a Console Window?

我有一个客户正在使用一些旧的但仍然需要 32 位软件,它是 运行 在控制台 Window 中。有必要禁用关闭按钮,因为使用此按钮关闭控制台会导致此软件出现一些严重问题。

我想到了以下方法:

1) 找到活动控制台的句柄
2) 使用 GetSystemMenu 函数禁用关闭按钮

也许我完全错了,但到目前为止我还没有找到办法。

编辑:

问题只是关闭按钮。当然用户也可以通过Alt+F4或任务管理器退出程序,但他们不会那样做。他们确实使用关闭按钮,这就是我要禁用它的原因。

当然,最好的解决方案是禁用所有取消程序的方法,但禁用关闭按钮也可以。

要在 Windows 表单中启动程序,也可以通过一种可能的解决方案。

要与外国Window互动,您需要先找到it/verify它存在。

我们有不同的方法来找到 Window。我在这里考虑 FindWindowEx and Process.GetProcessesByName()UI Automation and EnumWindows 最终提供其他选项。

将 CMD Window 标题存储在某处,例如实例字段(它可以是项目设置或您可以在 run-time 访问的任何其他内容)。

Private cmdWindowTitle As String = "The Window Title"

FindWindowEx 如果您确切知道 Window 标题是什么并且它不会随时间改变,那么 FindWindowEx 会更有用。
Process.GetProcessesByName() 可用于使用进程名称查找 Window,然后验证 Process.MainWindowTitle.Contains() 是否至少是部分已知字符串。

如果控制台 Window 属于当前进程,您只需要:

Process.GetCurrentProcess().MainWindowHandle

' -- If the Console Window belongs to the current Process: --
Dim cmdWindowHandle = Process.GetCurrentProcess().MainWindowHandle
' -----------------------------------------------------------

' -- Find it when the exact Window title is known: --
Dim cmdWindowHandle As IntPtr = NativeMethods.GetCmdWindowByCaption(cmdWindowTitle)
' -----------------------------------------------------------

' -- Find it when only a partial caption is available: --
Dim cmdWindowHandle As IntPtr = IntPtr.Zero
Dim cmdProc = Process.GetProcessesByName("cmd").
    FirstOrDefault(Function(p) p.MainWindowTitle.Contains(cmdWindowTitle))
If cmdProc IsNot Nothing Then
    cmdWindowHandle = cmdProc.MainWindowHandle
End If
' -----------------------------------------------------------

' Choose one of the above, then, in any case:
If cmdWindowHanle <> IntPtr.Zero Then
    NativeMethods.WindowDisableSysMenu(cmdWindowHandle)
End If

注意:在这里,我假设进程名称是 cmd 并且 Window class 名字是 ConsoleWindowClass。它可能不是。根据需要更改这些。

由于现在 Window 没有系统菜单或关闭按钮(我们只是将它们全部隐藏),无法使用 ALT+F4 或任何其他方式关闭它,除了使用任务管理器(或等待它自然关闭)。
要从您的应用中关闭它,请发送 WM_CLOSE 消息:

' -- find the Window as described before --
Dim cmdWindowHandle As IntPtr = NativeMethods.GetCmdWindowByCaption(cmdWindowTitle)
If Not cmdWindowHandle.Equals(IntPtr.Zero) Then
    NativeMethods.SendCloseMessage(cmdWindowHandle)
End If

NativeMethods 声明:

Public Class NativeMethods

    Private Const WM_CLOSE As Integer = &H10

    Public Enum WinStyles As UInteger
        WS_MAXIMIZE = &H1000000
        WS_MAXIMIZEBOX = &H10000
        WS_MINIMIZE = &H20000000
        WS_MINIMIZEBOX = &H20000
        WS_SYSMENU = &H80000
    End Enum

    Public Enum GWL_Flags As Integer
        GWL_STYLE = -16
        GWL_EXSTYLE = -20
    End Enum

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Function SendMessage(hWnd As IntPtr, uMsg As WinMessage, wParam As Integer, lParam As Integer) As Integer
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Function FindWindowEx(hwndParent As IntPtr, hwndChildAfter As IntPtr, lpszClass As String, lpszWindow As String) As IntPtr
    End Function
    
    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Function GetWindowLong(hWnd As IntPtr, nIndex As GWL_Flags) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Function SetWindowLong(hWnd As IntPtr, nIndex As GWL_Flags, dwNewLong As IntPtr) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Function GetWindowLongPtr(hWnd As IntPtr, nIndex As GWL_Flags) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)>
    Private Shared Function SetWindowLongPtr(hWnd As IntPtr, nIndex As GWL_Flags, dwNewLong As IntPtr) As IntPtr
    End Function

    ' Public wrappers
    Public Shared Function GetWindowLongUni(hWnd As IntPtr, nIndex As GWL_Flags) As Integer
        If IntPtr.Size = 8 Then
            Return GetWindowLongPtr(hWnd, nIndex).ToInt32()
        Else
            Return GetWindowLong(hWnd, nIndex).ToInt32()
        End If
    End Function

    Public Shared Function SetWindowLongUni(hWnd As IntPtr, nIndex As GWL_Flags, dwNewLong As Integer) As Integer
        If IntPtr.Size = 8 Then
            Return SetWindowLongPtr(hWnd, nIndex, New IntPtr(dwNewLong)).ToInt32()
        Else
            Return SetWindowLong(hWnd, nIndex, New IntPtr(dwNewLong)).ToInt32()
        End If
    End Function

    Public Shared Function GetCmdWindowByCaption(cmdCaption As String) As IntPtr
        Return FindWindowEx(IntPtr.Zero, IntPtr.Zero, "ConsoleWindowClass", cmdCaption)
    End Function

    Public Shared Sub WindowDisableSysMenu(windowHandle As IntPtr)
        Dim styles As Integer = GetWindowLongUni(windowHandle, GWL_Flags.GWL_STYLE)
        styles = styles And Not CInt(WinStyles.WS_SYSMENU)
        SetWindowLongUni(windowHandle, GWL_Flags.GWL_STYLE, styles)
    End Sub

    Public Shared Sub SendCloseMessage(windowHandle As IntPtr)
        SendMessage(windowHandle, WM_CLOSE, 0, 0)
    End Sub
End Class