移除和恢复窗口边框

3
我希望您能用C#去除另一个进程的窗口边框;我使用RemoveMenu方法去除了边框,它基本上可以工作,但还有两个问题需要解决:
  • 我需要两次删除边框,第一次菜单栏仍然存在。
  • 我无法恢复菜单。

这是我已经写的内容:

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并稍后恢复,但没有奏效。


1
дҪ йңҖиҰҒдҪҝз”ЁRDW_FRAMECHANGEDйҮҚж–°з»ҳеҲ¶зӘ—еҸЈд»ҘжҳҫзӨәж–°зҡ„иҫ№жЎҶгҖӮйҡҫйҒ“дҪ дёҚеә”иҜҘдҪҝз”ЁFormBorderStyleеұһжҖ§иҖҢдёҚжҳҜзӣҙжҺҘдҝ®ж”№ж ·ејҸеҗ—пјҹ - Raymond Chen
1
糟糕,我的内存出了问题。应该是SWP_FRAMECHANGED。但是你为什么要干扰属于另一个进程的窗口呢?这有点不礼貌。 - Raymond Chen
1
我并不是在说这个是否合法,而是说这样做不礼貌。也许应用程序在你搞乱它之后重置了其样式。也许 SWP_FRAMECHANGED 在跨进程时无法工作。也许你弄错了常量。(例如,你没有说明你是编译为32位还是64位。) - Raymond Chen
3
在64位操作系统中,GetWindowLong的参数与32位不同。由于这并不是预期完成的任务,因此没有更好的方法来完成它。 - Raymond Chen
啊,原来如此。感谢您的大力帮助。 - Laurence
显示剩余3条评论
3个回答

1
我无法恢复菜单栏,这是因为您的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不是局部变量,并引用定义在RemoveBorders方法之外的MenuHandle字段。IntelliSense仍将识别它为一个字段,并且不会警告你未定义的错误。

如果MenuHandle不是静态的,则可以在删除MenuHandle之前添加this关键字(换句话说,可以将IntPtr替换为this.),以便记住MenuHandle不再是局部变量,因此垃圾回收程序不会在RemoveBorders完成任务后删除它。

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

当RemoveBorders第一次完成时,MenuHandle不会被删除,并保存窗口菜单的先前状态,在所有项目被移除之前。 因此,当您第二次调用RemoveBorders以恢复菜单时,执行程序将到达if(Remove)代码并立即跳转到else代码,因为remove = false,在那里您调用SetMenu函数,当您给出窗口菜单的先前状态自从第一次调用RemoveBorders。 这样,您最终将能够恢复窗口菜单。
我仍然不明白为什么需要两次删除边框,第一次菜单栏仍然存在。 我也想帮助您解决这个问题,但是我没有任何想法。 在这种情况下,您的代码是正确的。很抱歉,但我希望其他人可以为您解决这个问题并为您提供解决方案。

0

试试这个。这对我有效。在这个例子中,边框和菜单的移除是在应用程序内部完成的。但是通过微小的调整,您可以使其适用于外部窗口。

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



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


希望这能有所帮助。

0

我解决了你关于以下问题的疑问:

我无法恢复菜单

关于你在另一个问题上的困扰:

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

对于这个问题,我没有解决方案,但我希望其他人能够提供帮助。

请删除你在问题中发布的定义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)。由于这个原因,remove参数被替换了,所以在我的新版本中不存在了,代码行if (remove)被更改为

if (MenuHandle == IntPtr.Zero)

它检查您是否没有将任何菜单句柄传递给窗口,然后您想要删除其边框。该操作在if语句内完成。如果您返回窗口的菜单,则MenuHandle不是NULL(即IntPtr.Zero),然后代码将进入else语句进行恢复。最后一个非常重要的更改是将第一行代码移到if语句块内,在调用GetMenuItemCount函数之前调用删除IntPtr并调用GetMenu函数的位置,因为我们需要获取窗口的菜单并保留它,然后再删除其边框,而不是总是这样做。如果没有这个更改,您将无法恢复窗口的边框,因为第一行代码将“丢弃”您返回的窗口菜单,因为当窗口没有菜单时,您调用GetMenu函数,因此此函数的返回值将为NULL(在C#中为IntPtr.Zero),因此在else块中,您将窗口的菜单设置为IntPtr.Zero(在C++中表示NULL)。通过这个更改,新的RemoveBorders方法应该正常工作,并允许您恢复窗口的边框。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接