取消钩子,使窗口恢复原始状态

3

I hook a window into a Panel using this code:

SetParent(win, pnlApp.Handle);
SetWindowLong(win, GWL_STYLE, WS_VISIBLE);
MoveWindow(win, 0, 0, pnlApp.Width, pnlApp.Height, true);

这里的win是窗口句柄,类型为IntPtr。以记事本为例,它可以很好地钩入到一个面板中。
但是当我尝试使用以下代码对其进行释放(取消钩取)时:

SetParent(win, (IntPtr)0);

它显示时没有窗口框架!我的意思是它会再次释放到桌面上,但它没有任何窗口框架。

如何解决这个问题?


2
你可能想阅读《是否合法拥有跨进程的父/子或所有者/拥有窗口关系》:https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683“是的,从技术上讲是合法的。但如果其中一个或两个窗口不知道自己正在参与跨进程窗口树,则它们变得几乎无法管理,就像玩杂耍锯一样。” - Damien_The_Unbeliever
请注意,由于记事本的简单性,人们认为它与记事本有点像,会假设其他所有应用程序也能正常工作。 - Damien_The_Unbeliever
我知道挂钩技术并不适用于所有类型的应用程序。但是我只想释放那些可以安全挂钩的应用程序。 - Inside Man
我的预测是,win 不是记事本的主窗口,而是它的子窗口,也就是你实际输入文本的窗口。 - Hans Passant
@HansPassant 不,我不这么认为。因为菜单栏也会被钩住。 - Inside Man
1个回答

6

关于问题中的代码:

  • SetWindowLong() 应被替换为 SetWindowLongPtr() - 请查看文档中的 warning 。后者在调用代码需要时会调用前者。

  • 您需要调用 GetWindowLongPtr() 来获取当前窗口样式,然后根据需要添加或删除样式;存储原始值:它将用于恢复先前的状态。
    ► 在示例中,删除了 WS_SYSMENU 样式,因此窗口将不显示系统菜单和通常位于标题中的所有按钮。这只是一个示例,以演示如何工作(对所有 Windows 可能无效)。

  • SetParent() 返回以前的父级句柄。存储该值,将需要将窗口恢复到其以前的所有者,不管是什么。如果窗口是顶级窗口,则先前的父级别似乎是桌面,因此您可以期望 IntPtr.Zero。它可能不是,您可以获取一个非 IntPtr.Zero 的句柄。只需存储返回的值,查看其内容(如果感兴趣):)

  • 您应该检查 GetWindowLongPtr()SetWindowLongPtr() 的返回值:如果为 0,则表示出现了错误。您可以使用 Marshal.GetLastWin32Error() 获取错误代码。请注意,SetWindowLongPtr() 不清除错误代码,因此必须自己调用 SetLastError(0)

  • 您可以使用 MoveWindow() 将窗口重新定位到新父窗口的边界内;我建议改用 SetWindowPos(),您将有更多选择。请查看此处的代码。

免责声明:请记住雷蒙德·陈(Raymond Chen)的博客文章中所述内容:在进程间是否合法拥有父/子或者所有者/拥有窗口关系。换句话说,这是一个“因为你问了”的事情 :)
设置以下字段(或适合的任何内容):
  • windowdHwnd 是要重新设置父级的窗口句柄
  • oldParent 是先前父级的句柄,由 SetParent() 返回
  • oldStyles 将存储先前窗口的样式,由 GetWWindowLongPtr() 返回
IntPtr windowdHwnd = IntPtr.Zero;
IntPtr oldParent = (IntPtr)(-1);
int oldStyles = 0;
// [...]

当您拥有所关注的窗口句柄时,请将windowdHwnd设置为该值。在此代码之后调用(可以是方法,传递窗口句柄和将成为新父级容器控件的容器控件 - 在此处,一个名为somePanel的控件)。

// Already parented, resets the previous styles, clean up and return
if (windowdHwnd != IntPtr.Zero && oldParent != (IntPtr)(-1)) {
    NativeMethods.SetParent(windowdHwnd, oldParent);
    NativeMethods.SetWindowLongPtr(windowdHwnd, NativeMethods.GWL_Flags.GWL_STYLE, new IntPtr(oldStyles));
    windowdHwnd = IntPtr.Zero;
    oldParent = (IntPtr)(-1);
    return;
}

// Store the existing Styles, to restore when the Window is dismissed
oldStyles = NativeMethods.GetWindowLongPtr(windowdHwnd, NativeMethods.GWL_Flags.GWL_STYLE);
if (oldStyles == IntPtr.Zero) {
    int error = Marshal.GetLastWin32Error();  // Show the error code or throw an exception
    return;
}

// Removes the System Menu from the Window: it will also remove the Buttons from the Caption
int newStyle = oldStyles.ToInt32()^ (int)NativeMethods.WinStyles.WS_SYSMENU;

NativeMethods.SetLastError(0);
// Sets the new Styles
IntPtr result = NativeMethods.SetWindowLongPtr(windowdHwnd, NativeMethods.GWL_Flags.GWL_STYLE, (IntPtr)newStyle);
if (result == IntPtr.Zero) {
    int error = Marshal.GetLastWin32Error();  // Show the error code or throw an exception
    return;
}

oldParent = NativeMethods.SetParent(windowdHwnd, somePanel.Handle);

// Repositions the Window and shows it, if needed
var flags = NativeMethods.SWP_Flags.SWP_ASYNCWINDOWPOS | NativeMethods.SWP_Flags.SWP_SHOWWINDOW;
NativeMethods.SetWindowPos(windowdHwnd, IntPtr.Zero, 0, 0, somePanel.Width, somePanel.Height, flags);

您可以这样写(这只是删除):
int newStyle = oldStyles &~(int)NativeMethods.WinStyles.WS_SYSMENU;

替代的选项(开/关):

int newStyle = oldStyles ^ (int)NativeMethods.WinStyles.WS_SYSMENU;

NativeMethods类:

using System.Runtime.InteropServices;

public class NativeMethods
{
    [Flags]
    public enum SWP_Flags : uint
    {
        /// <summary>Retains the current size (ignores the cx and cy parameters).</summary>
        SWP_NOSIZE = 0x0001,
        /// <summary>Retains the current position (ignores X and Y parameters).</summary>
        SWP_NOMOVE = 0x0002,
        /// <summary>Retains the current Z order (ignores the hWndInsertAfter parameter).</summary>
        SWP_NOZORDER = 0x0004,
        /// <summary>Does not redraw changes. If this flag is set, no repainting of any kind occurs. This applies to
        /// the client area, the nonclient area (including the title bar and scroll bars), and any part of the parent
        /// window uncovered as a result of the window being moved. When this flag is set, the application must
        /// explicitly invalidate or redraw any parts of the window and parent window that need redrawing.</summary>
        SWP_NOREDRAW = 0x0008,
        /// <summary>Does not activate the window. If this flag is not set, the window is activated and moved to the
        /// top of either the topmost or non-topmost group (depending on the setting of the hWndInsertAfter
        /// parameter).</summary>
        SWP_NOACTIVATE = 0x0010,
        /// <summary>Draws a frame (defined in the window's class description) around the window.</summary>
        SWP_DRAWFRAME = 0x0020,
        /// <summary>Applies new frame styles set using the SetWindowLong function. Sends a WM_NCCALCSIZE message to
        /// the window, even if the window's size is not being changed. If this flag is not specified, WM_NCCALCSIZE
        /// is sent only when the window's size is being changed.</summary>
        SWP_FRAMECHANGED = 0x0020,
        /// <summary>Displays the window.</summary>
        SWP_SHOWWINDOW = 0x0040,
        /// <summary>Hides the window.</summary>
        SWP_HIDEWINDOW = 0x0080,
        /// <summary>Discards the entire contents of the client area. If this flag is not specified, the valid
        /// contents of the client area are saved and copied back into the client area after the window is sized or
        /// repositioned.</summary>
        SWP_NOCOPYBITS = 0x0100,
        /// <summary>Does not change the owner window's position in the Z order.</summary>
        SWP_NOOWNERZORDER = 0x0200,
        /// <summary>Same as the SWP_NOOWNERZORDER flag.</summary>
        SWP_NOREPOSITION = 0x0200,
        /// <summary>Prevents the window from receiving the WM_WINDOWPOSCHANGING message.</summary>
        SWP_NOSENDCHANGING = 0x0400,
        /// <summary>Internal use.</summary>
        SWP_NOCLIENTSIZE = 0x0800,
        /// <summary>Internal use.</summary>
        SWP_NOCLIENTMOVE = 0x1000,
        /// <summary>Prevents generation of the WM_SYNCPAINT message.</summary>
        SWP_DEFERERASE = 0x2000,
        /// <summary>If the calling thread and the thread that owns the window are attached to different input queues,
        /// the system posts the request to the thread that owns the window. This prevents the calling thread from
        /// blocking its execution while other threads process the request.</summary>
        SWP_ASYNCWINDOWPOS = 0x4000
    }

    [Flags]
    public enum WinStyles : uint
    {
        WS_BORDER = 0x00800000,                     //The window has a thin-line border.
        WS_CAPTION = 0x00C00000,                    //The window has a title bar (includes the WS_BORDER style).
        WS_CHILD = 0x40000000,                      //The window is a child window. A window with this style cannot have a menu bar. This style cannot be used with the WS_POPUP style.
        WS_CHILDWINDOW = 0x40000000,                //Same as the WS_CHILD style.
        WS_CLIPCHILDREN = 0x02000000,               //Excludes the area occupied by child windows when drawing occurs within the parent window. This style is used when creating the parent window.
        WS_CLIPSIBLINGS = 0x04000000,               //Clips child windows relative to each other; that is, when a particular child window receives a WM_PAINT message, the WS_CLIPSIBLINGS style clips all other overlapping child windows out of the region of the child window to be updated. If WS_CLIPSIBLINGS is not specified and child windows overlap, it is possible, when drawing within the client area of a child window, to draw within the client area of a neighboring child window.
        WS_DISABLED = 0x08000000,                   //The window is initially disabled. A disabled window cannot receive input from the user. To change this after a window has been created, use the EnableWindow function.
        WS_DLGFRAME = 0x00400000,                   //The window has a border of a style typically used with dialog boxes. A window with this style cannot have a title bar.
        WS_GROUP = 0x00020000,                      //The window is the first control of a group of controls. The group consists of this first control and all controls defined after it, up to the next control with the WS_GROUP style. The first control in each group usually has the WS_TABSTOP style so that the user can move from group to group. The user can subsequently change the keyboard focus from one control in the group to the next control in the group by using the direction keys.
                                                    //You can turn this style on and off to change dialog box navigation. To change this style after a window has been created, use the SetWindowLong function.
        WS_HSCROLL = 0x00100000,                    //The window has a horizontal scroll bar.
        WS_ICONIC = 0x20000000,                     //The window is initially minimized. Same as the WS_MINIMIZE style.
        WS_MAXIMIZE = 0x01000000,                   //The window is initially maximized.
        WS_MAXIMIZEBOX = 0x00010000,                //The window has a maximize button. Cannot be combined with the WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified.
        WS_MINIMIZE = 0x20000000,                   //The window is initially minimized. Same as the WS_ICONIC style.
        WS_MINIMIZEBOX = 0x00020000,                //The window has a minimize button. Cannot be combined with the WS_EX_CONTEXTHELP style. The WS_SYSMENU style must also be specified.
        WS_OVERLAPPED = 0x00000000,                 //The window is an overlapped window. An overlapped window has a title bar and a border. Same as the WS_TILED style.
        WS_OVERLAPPEDWINDOW = WS_OVERLAPPED |       //The window is an overlapped window. Same as the WS_TILEDWINDOW style.
                              WS_CAPTION |
                              WS_SYSMENU |
                              WS_THICKFRAME |
                              WS_MINIMIZEBOX |
                              WS_MAXIMIZEBOX,
        WS_POPUP = 0x80000000,                      //The windows is a pop-up window. This style cannot be used with the WS_CHILD style.
        WS_POPUPWINDOW = WS_POPUP |                 //The window is a pop-up window. The WS_CAPTION and WS_POPUPWINDOW styles must be combined to make the window menu visible.
                         WS_BORDER |
                         WS_SYSMENU,
        WS_SIZEBOX = 0x00040000,                    //The window has a sizing border. Same as the WS_THICKFRAME style.
        WS_SYSMENU = 0x00080000,                    //The window has a window menu on its title bar. The WS_CAPTION style must also be specified.
        WS_TABSTOP = 0x00010000,                    //The window is a control that can receive the keyboard focus when the user presses the TAB key. Pressing the TAB key changes the keyboard focus to the next control with the WS_TABSTOP style.
                                                    //You can turn this style on and off to change dialog box navigation. To change this style after a window has been created, use the SetWindowLong function. For user-created windows and modeless dialogs to work with tab stops, alter the message loop to call the IsDialogMessage function.
        WS_THICKFRAME = 0x00040000,                 //The window has a sizing border. Same as the WS_SIZEBOX style.
        WS_TILED = 0x00000000,                      //The window is an overlapped window. An overlapped window has a title bar and a border. Same as the WS_OVERLAPPED style.
        WS_TILEDWINDOW = WS_OVERLAPPED |            //The window is an overlapped window. Same as the WS_OVERLAPPEDWINDOW style.
                         WS_CAPTION |
                         WS_SYSMENU |
                         WS_THICKFRAME |
                         WS_MINIMIZEBOX |
                         WS_MAXIMIZEBOX,
        WS_VISIBLE = 0x10000000,                   //The window is initially visible. This style can be turned on and off by using the ShowWindow or SetWindowPos function.
        WS_VSCROLL = 0x00200000,                   //The window has a vertical scroll bar.
    }

    public enum GWL_Flags : int
    {
        GWL_USERDATA = -21,
        GWL_EXSTYLE = -20,
        GWL_STYLE = -16,
        GWL_ID = -12,
        GWLP_HWNDPARENT = -8,
        GWLP_HINSTANCE = -6,
        GWL_WNDPROC = -4,
        DWLP_MSGRESULT = 0x0,
        DWLP_DLGPROC = 0x4,
        DWLP_USER = 0x8
    }


    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern IntPtr GetWindowLongPtr(IntPtr hWnd, GWL_Flags nIndex);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    internal static extern IntPtr SetWindowLongPtr(IntPtr hWnd, GWL_Flags nIndex, IntPtr dwNewLong);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);

    [DllImport("user32.dll", SetLastError = true)]
    internal static extern bool SetWindowPos(IntPtr hWnd, IntPtr hWndInsertAfter, int x, int y, int cx, int cy, SWP_Flags uFlags);

    [DllImport("kernel32.dll")]
    internal static extern void SetLastError(uint dwErrCode);
}

1
谢谢。你太棒了。你花时间提供这么详细的答案,我真的很感激你 :) - Inside Man

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