【问题标题】:Removing and restoring Window borders删除和恢复窗口边框
【发布时间】:2012-04-26 16:38:00
【问题描述】:

我想在 C# 中删除另一个进程的窗口边框;我使用 RemoveMenu 删除边框。它几乎可以工作,但我还有 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 并稍后恢复它,但这不起作用。

【问题讨论】:

  • 您需要使用RedrawWindow 和RDW_FRAMECHANGED 重新绘制新边框。你不应该使用FormBorderStyle 属性而不是敲打样式吗?
  • 谢谢,但我没有使用该功能恢复边界。我用过:“RedrawWindowFlags.Invalidate 和 RedrawWindowFlags.Frame”
  • 糟糕,我记错了。应该是SWP_FRAMECHANGED。但是你为什么要搞砸属于另一个进程的窗口呢?这有点粗鲁。
  • 我不是说它是否合法。我说这不礼貌。也许应用程序在你弄乱它之后正在重置它的样式。也许SWP_FRAMECHANGED 不能跨进程工作。也许你弄错了常数。 (例如,你没有说你是编译为 32 位还是 64 位。)
  • GetWindowLong 的参数在 64 位上是不同的。没有更好的方法来实现这一点,因为这不应该实现。

标签: c# windows winapi border


【解决方案1】:
  • 我无法恢复菜单的

这是因为您的 MenuHandle 是本地变量。

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

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

结果:

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

我给你的建议是让 MenuHandle global 变量,并在 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 field,由 RemoveBorders 方法定义。 IntelliSense 仍会将其识别为字段,并且不会提醒您未定义的错误。

如果 MenuHandle 是 not static,那么您也可以添加 this。删除 MenuHandle 之前的 IntPtr 后的关键字(换句话说,您可以将 IntPtr 替换为 this.),以记住 MenuHandle 不再是 not local 变量,所以垃圾收集器不会在 RemoveBorders 完成工作时将其删除。

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

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

所以当你调用 RemoveBorders 第二次 时间以恢复菜单时,执行器将到达if (Remove) 代码并立即跳转到else 代码,因为remove = false,并且在那里你调用 SetMenu 函数,当你给它 previous 窗口菜单的状态,因为 first 调用 RemoveBorders。 这样你就可以最终恢复窗口的菜单了。

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

【讨论】:

    【解决方案2】:

    试试这个。这对我有用。在这个例子中,边框和菜单的移除是在它自己的应用程序内部完成的。但只需稍作调整,您就可以使其适用于外部窗口。

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

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

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

      我解决了您关于以下几点的问题:

      我无法恢复菜单的

      关于你在另一点的问题

      我需要去掉两次边框,第一次菜单栏还在 存在。

      抱歉,我没有解决方案,但我希望其他人能提供帮助。

      删除定义您在问题中发布的 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语句中进行恢复。最后非常重要的更改是在调用 GetMenuItemCount 函数之前,在 if 语句块内移动第一行代码,删除 IntPtr 和调用 GetMenu 函数的位置,因为我们需要获取窗口的菜单并在其边框被移除之前保留它,并且并非总是。如果没有这个更改,您将无法恢复窗口的边框,因为第一行代码将“丢弃”您返回的窗口菜单,因为您在窗口没有菜单时调用 GetMenu 函数,所以该函数的返回值为 NULL(C# 中的 IntPtr.Zero),因此在 else 块中,您将窗口的菜单设置为 IntPtr.Zero(这意味着 C++ 中的 NULL)。通过此更改,新的 RemoveBorders 方法应该可以正常工作,并允许您恢复窗口的边框。

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-02-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多