删除和恢复窗口边框

本文关键字:边框 窗口 恢复 删除 | 更新日期: 2023-09-27 18:32:25

我想删除 C# 中另一个进程的窗口边框;我使用删除菜单来删除边框。它几乎可以工作,但我还有 2 个问题:

  • 我需要删除边框两次,第一次菜单栏仍然存在。
  • 我无法恢复菜单的

这是我已经写过的:

public void RemoveBorders(IntPtr WindowHandle, bool Remove)
    {
        IntPtr MenuHandle = GetMenu(WindowHandle);
        if (Remove)
        {
            int count = GetMenuItemCount(MenuHandle);
            for (int i = 0; i < count; i++)
                RemoveMenu(MenuHandle, 0, (0x40 | 0x10));
        }
        else
        {
            SetMenu(WindowHandle,MenuHandle);
        }
        int WindowStyle = GetWindowLong(WindowHandle, -16);
        //Redraw
        DrawMenuBar(WindowHandle);
        SetWindowLong(WindowHandle, -16, (WindowStyle & ~0x00080000));
        SetWindowLong(WindowHandle, -16, (WindowStyle & ~0x00800000 | 0x00400000));
    }

有人可以告诉我我做错了什么吗?我已经尝试保存菜单句柄并在以后恢复它,但这不起作用。

删除和恢复窗口边框

  • 我无法恢复菜单的

这是因为您的 MenuHandle 是局部变量。

当对方法 RemoveBorders 的第一次调用结束时,垃圾回收器将删除 MenuHandle 并释放内存。

第二次调用 RemoveBorders,MenuHandle 重新创建为新的局部变量,并重新分配给窗口菜单的当前状态 - 没有菜单项的菜单。

结果:

MenuHandle 不会保存窗口菜单的先前状态,这解释了为什么您无法还原窗口的菜单。

我给你的建议是制作 MenuHandle 全局变量,并从 RemoveBorders 方法定义中定义它。

您可以将其定义为私有、受保护或公共字段,也可以为其定义另一个属性,但这是可选的,不是必需的。您也可以将其定义为静态,如果此属性更适合您。

以下是 MenuHandle 定义的一些示例:

private IntPtr MenuHandle;
//or
IntPtr MenuHandle; //Defined outside of RemoveBorders, which is defined below this line, to show that MenuHandle is not local variable.
public void RemoveBorders(IntPtr WindowHandle, bool Remove)
//or
protected IntPtr MenuHandle;
//or
public IntPtr MenuHandle
//or
private static IntPtr MenuHandle
//or
static IntPtr MenuHandle
//etc...

您必须移动该行:

IntPtr MenuHandle = GetMenu(WindowHandle);

里面:

if (Remove)

以及在调用 GetMenuItemCount 函数之前。

您还必须修改该行,并至少删除 IntPtr,以声明 MenuHandle 不是局部变量,并引用 MenuHandle 字段,该字段是从 RemoveBorders 方法中定义的。智能感知仍会将其识别为字段,并且不会提醒您未定义的错误。

如果 MenuHandle

不是静态的,那么您也可以在 MenuHandle 之前删除 IntPtr 后添加 this . 关键字(换句话说,您可以将 IntPtr 替换为 this. ),以记住 MenuHandle 不再是局部变量,因此垃圾回收器不会在 RemoveBorders 完成作业时删除它。

当您启动程序时,MenuHandle 将作为默认值分配给IntPtr.Zero。首次调用 RemoveBorders 时,MenuHandle 的值将设置为 if (Remove) 中 GetMenu 函数的返回值。

当 RemoveBorders 首次完成时,不会删除 MenuHandle,而是在删除所有项目之前保存窗口菜单的先前状态。

因此,当您第二次调用 RemoveBorders 以恢复菜单时,执行器将到达if (Remove)代码并立即跳转到else代码,因为 remove = false,并且在那里你调用 SetMenu 函数,当你给它自第一次调用 RemoveBorders 以来窗口菜单的先前状态时。这样,您最终将能够恢复窗口的菜单。

我仍然没有意识到为什么您需要两次删除边框,第一次菜单栏仍然存在。我也想帮助你解决这个问题,但不知道。在这种情况下,您的代码是正确的。对不起,但我希望其他人可以为您解决这个问题,并为您提供解决方案。

试试这个。这对我有用。在此示例中,边框和菜单删除是在它自己的应用程序内完成的。但是通过细微的调整,您可以使其适用于外部窗口。

这些是我在代码中声明的一些常量


    const uint WS_BORDER = 0x00800000;
    const uint WS_DLGFRAME = 0x00400000;
    const uint WS_THICKFRAME = 0x00040000;
    const uint WS_CAPTION = WS_BORDER | WS_DLGFRAME;
    const uint WS_MINIMIZE = 0x20000000;
    const uint WS_MAXIMIZE = 0x01000000;
    const uint WS_SYSMENU = 0x00080000;
    const uint WS_VISIBLE = 0x10000000;
    const int GWL_STYLE = -16;

对于窗口边框


Point originallocation = this.Location;
Size originalsize = this.Size;
public void RemoveBorder(IntPtr windowHandle, bool removeBorder)
{
    uint currentstyle = (uint)GetWindowLongPtr(this.Handle, GWL_STYLE).ToInt64();
    uint[] styles = new uint[] { WS_CAPTION, WS_THICKFRAME, WS_MINIMIZE, WS_MAXIMIZE, WS_SYSMENU };
    foreach (uint style in styles)
    {
        if ((currentstyle & style) != 0)
        {
            if(removeBorder)
            {
                currentstyle &= ~style;
            }
            else
            {
                currentstyle |= style;
            }
        }
    }
    SetWindowLongPtr(windowHandle, GWL_STYLE, (IntPtr)(currentstyle));
    //this resizes the window to the client area and back. Also forces the window to redraw.
    if(removeBorder)
    {
        SetWindowPosPtr(this.Handle, (IntPtr)0, this.PointToScreen(this.ClientRectangle.Location).X, this.PointToScreen(this.ClientRectangle.Location).Y, this.ClientRectangle.Width, this.ClientRectangle.Height, 0);
    }
    else
    {
        SetWindowPosPtr(this.Handle, (IntPtr)0, originallocation.X, originallocation.Y, originalsize.Width, originalsize.Height, 0);
    }
}

对于菜单,您可以执行此操作。


    public void RemoveMenu(IntPtr menuHandle, bool removeMenu)
    {
        uint menustyle = (uint)GetWindowLongPtr(menuStrip1.Handle, GWL_STYLE).ToInt64();
        SetWindowLongPtr(menuStrip1.Handle, GWL_STYLE, (IntPtr)(menustyle^WS_VISIBLE));
        // forces the window to redraw (makes the menu visible or not)
        this.Refresh();
    }

另请注意,我使用 GetWindowLongPtr、SetWindowLongPtr 和 SetWindowPosPtr 和 IntPtr 作为参数,而不是 GetWindowLong、SetWindowLong 和 SetWindowPos int/uint。这是因为 x86/x64 兼容性。

这是我如何导入GetWindowLongPtr


    [DllImport("user32.dll", EntryPoint = "GetWindowLong")]
    public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
    [DllImport("user32.dll", EntryPoint = "GetWindowLongPtr")]
    public static extern IntPtr GetWindowLong64(IntPtr hWnd, int nIndex);
    public static IntPtr GetWindowLongPtr(IntPtr hWnd, int nIndex)
    {
        if (IntPtr.Size == 8)
        {
            return GetWindowLong64(hWnd, nIndex);
        }
        else
        {
            return new IntPtr(GetWindowLong(hWnd, nIndex));
        }
    }

希望这有帮助。

我解决了您在以下几点上的问题:

我无法恢复菜单的

关于你在另一点的问题

我需要删除边框两次,第一次菜单栏仍然 存在。

对此抱歉,我没有解决方案但我希望其他人会对此有所帮助。

删除您在问题中发布的所有定义 RemoveBorders 方法的代码,然后选择我在下面发布的所有以下代码(如果有效,请使用 Ctrl + A),复制它(鼠标右键单击 =>选择"复制"只是按 Ctrl + C 更快),然后粘贴它(鼠标右键单击 => 选择"粘贴"只需按 按Ctrl + V 更快)到您的代码。在粘贴新代码之前,请确保光标在代码编辑器中的位置位于定义 RemoveBorder 方法的旧代码所在的正确位置。我重新定义RemoveBorders的新代码是:

public IntPtr RemoveBorders(IntPtr WindowHandle, IntPtr MenuHandle)
{
    if (MenuHandle == IntPtr.Zero)
    {
        MenuHandle = GetMenu(WindowHandle);
        int count = GetMenuItemCount(MenuHandle);
        for (int i = 0; i < count; i++)
            RemoveMenu(MenuHandle, 0, (0x40 | 0x10));
    }
    else
    {
        SetMenu(WindowHandle,MenuHandle);
    }
    int WindowStyle = GetWindowLong(WindowHandle, -16);
    //Redraw
    DrawMenuBar(WindowHandle);
    SetWindowLong(WindowHandle, -16, (WindowStyle & ~0x00080000));
    SetWindowLong(WindowHandle, -16, (WindowStyle & ~0x00800000 | 0x00400000));
    return MenuHandle;
}

从旧版本到新版本的 RemoveBorders 方法所做的更改:

首先,该方法的返回值从 void 变为 IntPtr,因此代码行

return MenuHandle;

添加到您上次调用SetWindowLong函数下方。此更改的目的是对 RemoveBorders 进行编程,以返回属于窗口的菜单句柄(在 C# 中为 IntPtr 类型),然后从窗口中删除其边框。这很重要,因为下次再次调用RemoveBorders以恢复窗口的边框时,您需要返回其菜单的句柄。所以这就是为什么RemoveBorders(bool remove)的第二个参数被更改为IntPtr MenuHandle,以允许您返回窗口的菜单并恢复其边框。因此,我不得不删除第一行代码中 MenuHandle 之前的IntPtr,以声明 MenuHandle 不再是局部变量,但现在它是一个参数。每当要删除窗口的边框时,将此参数设置为 IntPtr.Zero(表示C++中的NULL)。因此,删除参数被替换,因此在我的新版本中不再存在,代码行if (remove)更改为

if (MenuHandle == IntPtr.Zero)

它会检查您是否没有为窗口提供任何菜单句柄,然后您要删除其边框。该操作在 if 语句中完成。如果您返回窗口的菜单,则 MenuHandle 不会NULL(即 IntPtr.Zero ),然后将代码引入 else 语句以执行恢复。最后一个非常重要的更改是在调用 GetMenuItemCount 函数之前,在 if 语句块内移动第一个代码行,其中删除了 IntPtr 以及调用 GetMenu 函数的位置,因为我们需要获取窗口的菜单并保留它,在其边框被删除之前,并非总是如此。如果没有此更改,您将无法恢复窗口的边框,因为第一行代码将"丢弃"您返回的窗口菜单,因为您在窗口没有菜单时调用 GetMenu 函数,因此此函数的返回值将为 NULL(C# 中的 IntPtr.Zero),因此在 else 块中, 将窗口的菜单设置为IntPtr.Zero(这意味着C++中的NULL)。通过此更改,新的 RemoveBorders 方法应该可以正常工作,并允许您恢复窗口的边框。

从现在开始,您应该使用我的新版本来删除边框而不是旧版本,以便能够恢复窗口的边框。我希望你理解我在这个变化中的想法。说明很简单:定义类型为 IntPtr 的新变量,并且每当调用 RemoveBorders 以删除窗口的边框时,将该变量分配给方法的返回值,以便在删除边框后保留并保存窗口的菜单。稍后您再次调用 RemoveBorders,但现在要恢复窗口的边框,因此您将第二个参数设置为保留窗口菜单的变量,即上次调用 RemoveBorders 方法的返回值。希望对您有所帮助!