From 177688f19645f9f4d19f5b2e54d199ac606cca0b Mon Sep 17 00:00:00 2001 From: David Hall Date: Thu, 26 Jan 2023 18:05:19 -0700 Subject: [PATCH] Lots of work on WindowBase, VisibleWindow, WindowClass and MessagePump --- PInvoke/User32/MessagePump.cs | 4 +- PInvoke/User32/VisibleWindow.cs | 679 +++++++++++++++++++++++++++++++ PInvoke/User32/WindowBase.cs | 597 +++++---------------------- PInvoke/User32/WindowClass.cs | 18 +- UnitTests/PInvoke/User32/WrapperTests.cs | 29 +- 5 files changed, 813 insertions(+), 514 deletions(-) create mode 100644 PInvoke/User32/VisibleWindow.cs diff --git a/PInvoke/User32/MessagePump.cs b/PInvoke/User32/MessagePump.cs index 84fd9d1b..f5dad42b 100644 --- a/PInvoke/User32/MessagePump.cs +++ b/PInvoke/User32/MessagePump.cs @@ -50,7 +50,7 @@ public class MessagePump : IMessagePump /// public int Run(IWindowInstance mainWindow = null) { - if (mainWindow is not null) + if (mainWindow is not null and not WindowBase) mainWindow.Destroyed += onDestroy; try @@ -59,7 +59,7 @@ public class MessagePump : IMessagePump } finally { - if (mainWindow is not null) + if (mainWindow is not null and not WindowBase) mainWindow.Destroyed -= onDestroy; } static void onDestroy() => PostQuitMessage(0); diff --git a/PInvoke/User32/VisibleWindow.cs b/PInvoke/User32/VisibleWindow.cs new file mode 100644 index 00000000..e8cc0988 --- /dev/null +++ b/PInvoke/User32/VisibleWindow.cs @@ -0,0 +1,679 @@ +#nullable enable +using System; +using System.Text; +using static Vanara.PInvoke.Kernel32; +using static Vanara.PInvoke.User32; + +namespace Vanara.PInvoke; + +/// A wrapper for a visible window. +/// +public class VisibleWindow : WindowBase +{ + /// Initializes an uninitialized and uncreated instance of the class. + public VisibleWindow() { } + + /// Initializes a new instance of the class. + /// The window class. If , a new window class is created that is unique to this window. + /// + /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title + /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the + /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or + /// identifier. To specify an identifier, use the syntax "#num". + /// + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// + /// + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. + /// + /// + /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then + /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls + /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window + /// manager calls ShowWindow with that value as the nCmdShow parameter. + /// + /// + /// + /// Type: DWORD + /// + /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles + /// indicated in the Remarks section. + /// + /// + /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. + /// + /// + /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid + /// window handle. This parameter is optional for pop-up windows. + /// + /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. + /// + /// + /// Type: HMENU + /// + /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu + /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu + /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The + /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. + /// + /// + public VisibleWindow(WindowClass? wc = null, string? text = null, SIZE? size = default, POINT? position = default, WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, + WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) => + CreateHandle(wc, text, size, position, style, exStyle, parent, hMenu); + + /// Initializes a new instance of the class. + /// The window class name. It is created using default values if it doesn't exist. + /// + /// A handle to the instance of the application that created the class. To retrieve information about classes defined by the system (such + /// as buttons or list boxes), set this parameter to NULL. + /// + /// + /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title + /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the + /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or + /// identifier. To specify an identifier, use the syntax "#num". + /// + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// + /// + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. + /// + /// + /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then + /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls + /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window + /// manager calls ShowWindow with that value as the nCmdShow parameter. + /// + /// + /// + /// Type: DWORD + /// + /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles + /// indicated in the Remarks section. + /// + /// + /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. + /// + /// + /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid + /// window handle. This parameter is optional for pop-up windows. + /// + /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. + /// + /// + /// Type: HMENU + /// + /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu + /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu + /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The + /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. + /// + /// + public VisibleWindow(string className, HINSTANCE hInst, string? text = null, SIZE? size = default, POINT? position = default, + WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) : + this(WindowClass.GetNamedInstance(className, hInst), text, size, position, style, exStyle, parent, hMenu) + { + } + + /// Initializes a new instance of the class. + /// The window procedure override delegate. + /// + /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title + /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the + /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or + /// identifier. To specify an identifier, use the syntax "#num". + /// + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// + /// + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. + /// + /// + /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then + /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls + /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window + /// manager calls ShowWindow with that value as the nCmdShow parameter. + /// + /// + /// + /// Type: DWORD + /// + /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles + /// indicated in the Remarks section. + /// + /// + /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. + /// + /// Type: HMENU + /// + /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu + /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu + /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The + /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. + /// + /// + /// + /// + /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid + /// window handle. This parameter is optional for pop-up windows. + /// + /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. + /// + public VisibleWindow(WindowProc wndProcOverride, string? text = null, SIZE? size = default, POINT? position = default, + WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, WindowStylesEx exStyle = 0, HMENU hMenu = default, HWND parent = default) : + base(wndProcOverride) => CreateHandle(null, text, size, position, style, exStyle, parent, hMenu); + + /// + /// Gets or sets the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that + /// are relative to the upper-left corner of the screen. + /// + /// A RECT structure with the screen coordinates of the upper-left and lower-right corners of the window. + public RECT Bounds + { + get { Win32Error.ThrowLastErrorIfFalse(GetWindowRect(Handle, out RECT r)); return r; } + set => SetPosition(value.Location, value.Size); + } + + /// + /// Gets the coordinates of a window's client area. The client coordinates specify the upper-left and lower-right corners of the client + /// area. Because client coordinates are relative to the upper-left corner of a window's client area, the coordinates of the upper-left + /// corner are (0,0). + /// + /// + /// A RECT structure with the client coordinates. The left and top members are zero. The right and bottom members contain the width and + /// height of the window. + /// + public RECT ClientRect + { + get { Win32Error.ThrowLastErrorIfFalse(GetClientRect(Handle, out RECT r)); return r; } + } + + /// Gets a value indicating whether this is enabled. + /// if enabled; otherwise, . + public bool Enabled => IsWindowEnabled(Handle); + + /// Gets a value indicating whether the window has input focus. + /// if focused; otherwise, . + public bool Focused => GetFocus().Equals(Handle); + + /// + /// Gets or sets the position of the window. The dimensions are given in screen coordinates that are relative to the upper-left corner of + /// the screen. + /// + /// The position of window. + public POINT Position + { + get + { + Win32Error.ThrowLastErrorIfFalse(GetWindowRect(Handle, out RECT r)); + return r.Location; + } + set => Win32Error.ThrowLastErrorIfFalse(SetWindowPos(Handle, default, value.X, value.Y, -1, -1, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOZORDER)); + } + + /// Gets or sets the window's show state. + /// The show state. + public ShowWindowCommand ShowState + { + get + { + if (!Visible) + { + return ShowWindowCommand.SW_HIDE; + } + + if (IsIconic(Handle)) + { + return ShowWindowCommand.SW_MINIMIZE; + } + + return IsZoomed(Handle) ? ShowWindowCommand.SW_MAXIMIZE : ShowWindowCommand.SW_NORMAL; + } + set => Win32Error.ThrowLastErrorIfFalse(ShowWindow(Handle, value)); + } + + /// Gets or sets the size of the window. + /// The size of window. + public SIZE Size + { + get + { + Win32Error.ThrowLastErrorIfFalse(GetWindowRect(Handle, out RECT r)); + return r.Size; + } + set => Win32Error.ThrowLastErrorIfFalse(SetWindowPos(Handle, default, -1, -1, value.cx, value.cy, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOZORDER)); + } + + /// Gets or sets the value of the style bit for the window. + /// The styles value. + public WindowStyles Styles + { + get => (WindowStyles)Param[WindowLongFlags.GWL_STYLE].ToInt32(); + set => Param[WindowLongFlags.GWL_STYLE] = (IntPtr)(int)value; + } + + /// Gets or sets the value of the extended style bit for the window. + /// The extended styles value. + public WindowStylesEx StylesEx + { + get => (WindowStylesEx)Param[WindowLongFlags.GWL_EXSTYLE].ToInt32(); + set => Param[WindowLongFlags.GWL_EXSTYLE] = (IntPtr)(int)value; + } + + /// Gets or sets the text of the window's title bar (if it has one). + /// The text of the title bar. + public string Text + { + get + { + if (!Handle.IsNull) + { + int len = GetWindowTextLength(Handle); + if (len > 0) + { + StringBuilder sb = new(len + 1); + if (GetWindowText(Handle, sb, len) > 0) + { + return sb.ToString(); + } + } + } + return string.Empty; + } + set => Win32Error.ThrowLastErrorIfFalse(SetWindowText(Handle, value)); + } + + /// Gets a value indicating whether this window is visible. + /// if visible; otherwise, . + public bool Visible => IsWindowVisible(Handle); + + /// + /// Creates a new instance of the class using the parameters, displays the window, and executes a simple + /// message pump. + /// + /// The window class. If , a new window class is created that is unique to this window. + /// + /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title + /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the + /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or + /// identifier. To specify an identifier, use the syntax "#num". + /// + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// + /// + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. + /// + /// + /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then + /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls + /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window + /// manager calls ShowWindow with that value as the nCmdShow parameter. + /// + /// + /// + /// Type: DWORD + /// + /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles + /// indicated in the Remarks section. + /// + /// + /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. + /// + /// + /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid + /// window handle. This parameter is optional for pop-up windows. + /// + /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. + /// + /// + /// Type: HMENU + /// + /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu + /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu + /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The + /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. + /// + /// + public static void Run(WindowClass? wc = null, string? text = null, SIZE? size = default, POINT? position = default, WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, + WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) where TWin : VisibleWindow, new() + { + using TWin win = new(); + win.CreateHandle(wc, text, size, position, style, exStyle, parent, hMenu); + win.Show(); + new MessagePump().Run(win); + } + + /// + /// Creates a new instance of the class using the parameters, displays the window, and executes a simple + /// message pump. + /// + /// + /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title + /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the + /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or + /// identifier. To specify an identifier, use the syntax "#num". + /// + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// + /// + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. + /// + /// + /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then + /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls + /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window + /// manager calls ShowWindow with that value as the nCmdShow parameter. + /// + /// + /// + /// Type: DWORD + /// + /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles + /// indicated in the Remarks section. + /// + /// + /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. + /// + /// + /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid + /// window handle. This parameter is optional for pop-up windows. + /// + /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. + /// + /// + /// Type: HMENU + /// + /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu + /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu + /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The + /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. + /// + /// + public static void Run(string? text = null, SIZE? size = default, POINT? position = default, WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, + WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) where TWin : VisibleWindow, new() + { + using TWin win = new(); + win.CreateHandle(null, text, size, position, style, exStyle, parent, hMenu); + win.Show(); + new MessagePump().Run(win); + } + + /// + /// Creates a new instance of the class using the parameters, displays the window, and executes a simple + /// message pump. + /// + /// The window procedure delegate. + /// + /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title + /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the + /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or + /// identifier. To specify an identifier, use the syntax "#num". + /// + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// + /// + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. + /// + /// + /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then + /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls + /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window + /// manager calls ShowWindow with that value as the nCmdShow parameter. + /// + /// + /// + /// Type: DWORD + /// + /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles + /// indicated in the Remarks section. + /// + /// + /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. + /// + /// + /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid + /// window handle. This parameter is optional for pop-up windows. + /// + /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. + /// + /// + /// Type: HMENU + /// + /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu + /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu + /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The + /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. + /// + /// + public static void Run(WindowProc wndProc, string? text = null, SIZE? size = default, POINT? position = default, WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, + WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) + { + using VisibleWindow win = new(wndProc, text, size, position, style, exStyle, hMenu, parent); + win.Show(); + new MessagePump().Run(win); + } + + /// Converts a rectangle from this window's client coordinates to screen coordinates. + /// A in client coordinates. + /// The resulting in screen coordinates. + public virtual RECT ClientToScreen(in RECT clientRect) + { + RECT screenRect = clientRect; + SetLastError(0); + Win32Error.ThrowLastErrorIf(MapWindowPoints(Handle, IntPtr.Zero, ref screenRect), i => i == 0); + Win32Error.ThrowLastErrorIfFalse(AdjustWindowRectEx(ref screenRect, Styles, GetMenu(Handle) != IntPtr.Zero, StylesEx)); + return screenRect; + } + + /// Sets input focus to the window. + public void Focus() => Win32Error.ThrowLastErrorIf(SetFocus(Handle), h => h.IsNull); + + /// Hides the window (sets state to SW_HIDE). + public void Hide() => ShowState = ShowWindowCommand.SW_HIDE; + + /// + /// Invalidates the specified region of the windows (adds it to the window's update region, which is the area that will be repainted at + /// the next paint operation), and causes a paint message to be sent to the window. Optionally, invalidates the child windows assigned to + /// the window. + /// + /// to invalidate the window's child windows; otherwise, . + /// + /// A that represents the region to invalidate. This value can be , in which case the entire + /// client region is invalidated. + /// + public void Invalidate(bool erase = false, PRECT? pRect = null) => Win32Error.ThrowLastErrorIfFalse(InvalidateRect(Handle, pRect, erase)); + + /// Converts a rectangle from screen coordinates to this window's client coordinates. + /// A in screen coordinates. + /// The resulting in client coordinates. + public virtual RECT ScreenToClient(in RECT screenRect) + { + RECT clientRect = screenRect; + SetLastError(0); + Win32Error.ThrowLastErrorIf(MapWindowPoints(IntPtr.Zero, Handle, ref clientRect), i => i == 0); + RECT invRect = default; + Win32Error.ThrowLastErrorIfFalse(AdjustWindowRectEx(ref invRect, Styles, GetMenu(Handle) != IntPtr.Zero, StylesEx)); + SubtractRect(out clientRect, clientRect, invRect); + return clientRect; + } + + /// + /// Changes the size, position, and Z order of a child, pop-up, or top-level window. These windows are ordered according to their + /// appearance on the screen. The topmost window receives the highest rank and is the first window in the Z order. + /// + /// + /// Type: HWND + /// + /// A handle to the window to precede the positioned window in the Z order. This parameter must be a window handle or one of the + /// following values. + /// + /// + /// + /// Value + /// Meaning + /// + /// + /// HWND_BOTTOM (HWND)1 + /// + /// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window, the window loses its topmost + /// status and is placed at the bottom of all other windows. + /// + /// + /// + /// HWND_NOTOPMOST (HWND)-2 + /// + /// Places the window above all non-topmost windows (that is, behind all topmost windows). This flag has no effect if the window is + /// already a non-topmost window. + /// + /// + /// + /// HWND_TOP (HWND)0 + /// Places the window at the top of the Z order. + /// + /// + /// HWND_TOPMOST (HWND)-1 + /// Places the window above all non-topmost windows. The window maintains its topmost position even when it is deactivated. + /// + /// + /// For more information about how this parameter is used, see the following Remarks section. + /// + /// The new position of the window, in client coordinates. + /// The new width of the window, in pixels. + /// The window sizing and positioning flags. This parameter can be one of more values from . + /// + /// Type: Type: BOOL + /// If the function succeeds, the return value is nonzero. + /// If the function fails, the return value is zero. To get extended error information, call GetLastError. + /// + /// + /// + /// As part of the Vista re-architecture, all services were moved off the interactive desktop into Session 0. hwnd and window manager + /// operations are only effective inside a session and cross-session attempts to manipulate the hwnd will fail. For more information, see + /// The Windows Vista Developer Story: Application Compatibility Cookbook. + /// + /// + /// If you have changed certain window data using SetWindowLong, you must call SetWindowPos for the changes to take effect. Use + /// the following combination for uFlags: . + /// + /// + /// A window can be made a topmost window either by setting the hWndInsertAfter parameter to HWND_TOPMOST and ensuring that the + /// SWP_NOZORDER flag is not set, or by setting a window's position in the Z order so that it is above any existing topmost + /// windows. When a non-topmost window is made topmost, its owned windows are also made topmost. Its owners, however, are not changed. + /// + /// + /// If neither the SWP_NOACTIVATE nor SWP_NOZORDER flag is specified (that is, when the application requests that a window + /// be simultaneously activated and its position in the Z order changed), the value specified in hWndInsertAfter is used only in the + /// following circumstances. + /// + /// + /// + /// Neither the HWND_TOPMOST nor HWND_NOTOPMOST flag is specified in hWndInsertAfter. + /// + /// + /// The window identified by hWnd is not the active window. + /// + /// + /// + /// An application cannot activate an inactive window without also bringing it to the top of the Z order. Applications can change an + /// activated window's position in the Z order without restrictions, or it can activate a window and then move it to the top of the + /// topmost or non-topmost windows. + /// + /// + /// If a topmost window is repositioned to the bottom ( HWND_BOTTOM) of the Z order or after any non-topmost window, it is no + /// longer topmost. When a topmost window is made non-topmost, its owners and its owned windows are also made non-topmost windows. + /// + /// + /// A non-topmost window can own a topmost window, but the reverse cannot occur. Any window (for example, a dialog box) owned by a + /// topmost window is itself made a topmost window, to ensure that all owned windows stay above their owner. + /// + /// If an application is not in the foreground, and should be in the foreground, it must call the SetForegroundWindow function. + /// To use SetWindowPos to bring a window to the top, the process that owns the window must have SetForegroundWindow permission. + /// + public void SetPosition(POINT position, SIZE size, HWND hWndInsertAfter = default, SetWindowPosFlags flags = SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER) => + Win32Error.ThrowLastErrorIfFalse(SetWindowPos(Handle, hWndInsertAfter, position.X, position.Y, size.cx, size.cy, flags)); + + /// Shows the window (sets state to SW_SHOWNORMAL). + public void Show() => ShowState = ShowWindowCommand.SW_SHOWNORMAL; + + /// + /// The Validate function validates the client area within a rectangle by removing the rectangle from the update region of the + /// this window. + /// + /// + /// A structure that contains the client coordinates of the rectangle to be removed from the update region. If this + /// parameter is , the entire client area is removed. + /// + public void Validate(PRECT? pRect = null) => Win32Error.ThrowLastErrorIfFalse(ValidateRect(Handle, pRect)); +} \ No newline at end of file diff --git a/PInvoke/User32/WindowBase.cs b/PInvoke/User32/WindowBase.cs index f8a74e1e..20c68ac1 100644 --- a/PInvoke/User32/WindowBase.cs +++ b/PInvoke/User32/WindowBase.cs @@ -5,6 +5,7 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Text; using Vanara.Extensions; +using Vanara.InteropServices; using static Vanara.PInvoke.Kernel32; using static Vanara.PInvoke.User32; @@ -19,433 +20,18 @@ public interface IWindowHandle : IHandle HWND Handle { get; } } -/// A wrapper for a visible window. -/// -public class VisibleWindow : WindowBase -{ - /// Initializes a new instance of the class. - /// The window class. If , a new window class is created that is unique to this window. - /// - /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title - /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the - /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or - /// identifier. To specify an identifier, use the syntax "#num". - /// - /// - /// The initial position and size of the window. - /// - /// For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the window's upper-left corner, in screen - /// coordinates. For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of - /// the parent window's client area. If x is set to CW_USEDEFAULT, the system selects the default position for the window's - /// upper-left corner and ignores the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a - /// pop-up or child window, the x and y parameters are set to zero. - /// - /// - /// The initial vertical position of the window. For an overlapped or pop-up window, the y parameter is the initial y-coordinate of the - /// window's upper-left corner, in screen coordinates. For a child window, y is the initial y-coordinate of the upper-left corner of the - /// child window relative to the upper-left corner of the parent window's client area. For a list box y is the initial y-coordinate of - /// the upper-left corner of the list box's client area relative to the upper-left corner of the parent window's client area. - /// - /// - /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then - /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls - /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window - /// manager calls ShowWindow with that value as the nCmdShow parameter. - /// - /// - /// The width, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or - /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default - /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial - /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is - /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. - /// - /// - /// The height, in device units, of the window. For overlapped windows, nHeight is the window's height, in screen coordinates. If the - /// nWidth parameter is set to CW_USEDEFAULT, the system ignores nHeight. - /// - /// - /// - /// Type: DWORD - /// - /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles - /// indicated in the Remarks section. - /// - /// - /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. - /// - /// - /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid - /// window handle. This parameter is optional for pop-up windows. - /// - /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. - /// - /// - /// Type: HMENU - /// - /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu - /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu - /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The - /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. - /// - /// - public VisibleWindow(WindowClass? wc = null, string? text = null, RECT? bounds = default, WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, - WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) => - CreateHandle(wc, text, bounds, style, exStyle, parent, hMenu); - - /// Initializes a new instance of the class. - /// The window class name. It is created using default values if it doesn't exist. - /// - /// A handle to the instance of the application that created the class. To retrieve information about classes defined by the system (such - /// as buttons or list boxes), set this parameter to NULL. - /// - /// - /// The window name. If the window style specifies a title bar, the window title pointed to by lpWindowName is displayed in the title - /// bar. When using CreateWindow to create controls, such as buttons, check boxes, and static controls, use lpWindowName to specify the - /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or - /// identifier. To specify an identifier, use the syntax "#num". - /// - /// - /// The initial position and size of the window. - /// - /// For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the window's upper-left corner, in screen - /// coordinates. For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of - /// the parent window's client area. If x is set to CW_USEDEFAULT, the system selects the default position for the window's - /// upper-left corner and ignores the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a - /// pop-up or child window, the x and y parameters are set to zero. - /// - /// - /// The initial vertical position of the window. For an overlapped or pop-up window, the y parameter is the initial y-coordinate of the - /// window's upper-left corner, in screen coordinates. For a child window, y is the initial y-coordinate of the upper-left corner of the - /// child window relative to the upper-left corner of the parent window's client area. For a list box y is the initial y-coordinate of - /// the upper-left corner of the list box's client area relative to the upper-left corner of the parent window's client area. - /// - /// - /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then - /// the y parameter determines how the window is shown. If the y parameter is CW_USEDEFAULT, then the window manager calls - /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window - /// manager calls ShowWindow with that value as the nCmdShow parameter. - /// - /// - /// The width, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or - /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default - /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial - /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is - /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. - /// - /// - /// The height, in device units, of the window. For overlapped windows, nHeight is the window's height, in screen coordinates. If the - /// nWidth parameter is set to CW_USEDEFAULT, the system ignores nHeight. - /// - /// - /// - /// Type: DWORD - /// - /// The style of the window being created. This parameter can be a combination of the window style values, plus the control styles - /// indicated in the Remarks section. - /// - /// - /// The extended window style of the window being created. For a list of possible values,see Extended Window Styles. - /// - /// - /// A handle to the parent or owner window of the window being created. To create a child window or an owned window, supply a valid - /// window handle. This parameter is optional for pop-up windows. - /// - /// To create a message-only window, supply HWND_MESSAGE or a handle to an existing message-only window. - /// - /// - /// Type: HMENU - /// - /// A handle to a menu, or specifies a child-window identifier, depending on the window style. For an overlapped or pop-up window, hMenu - /// identifies the menu to be used with the window; it can be NULL if the class menu is to be used. For a child window, hMenu - /// specifies the child-window identifier, an integer value used by a dialog box control to notify its parent about events. The - /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. - /// - /// - public VisibleWindow(string className, HINSTANCE hInst, string? text = null, RECT? bounds = default, WindowStyles style = WindowStyles.WS_OVERLAPPEDWINDOW, - WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) : - this(WindowClass.GetNamedInstance(className, hInst), text, bounds, style, exStyle, parent, hMenu) - { - } - - /// - /// Gets or sets the dimensions of the bounding rectangle of the specified window. The dimensions are given in screen coordinates that - /// are relative to the upper-left corner of the screen. - /// - /// A RECT structure with the screen coordinates of the upper-left and lower-right corners of the window. - public RECT Bounds - { - get { Win32Error.ThrowLastErrorIfFalse(GetWindowRect(Handle, out RECT r)); return r; } - set => SetPosition(value.Location, value.Size); - } - - /// - /// Gets the coordinates of a window's client area. The client coordinates specify the upper-left and lower-right corners of the client - /// area. Because client coordinates are relative to the upper-left corner of a window's client area, the coordinates of the upper-left - /// corner are (0,0). - /// - /// - /// A RECT structure with the client coordinates. The left and top members are zero. The right and bottom members contain the width and - /// height of the window. - /// - public RECT ClientRect - { - get { Win32Error.ThrowLastErrorIfFalse(GetClientRect(Handle, out RECT r)); return r; } - } - - /// Gets a value indicating whether this is enabled. - /// if enabled; otherwise, . - public bool Enabled => IsWindowEnabled(Handle); - - /// Gets a value indicating whether the window has input focus. - /// if focused; otherwise, . - public bool Focused => GetFocus().Equals(Handle); - - /// - /// Gets or sets the position of the window. The dimensions are given in screen coordinates that are relative to the upper-left corner of - /// the screen. - /// - /// The position of window. - public POINT Position - { - get - { - Win32Error.ThrowLastErrorIfFalse(GetWindowRect(Handle, out RECT r)); - return r.Location; - } - set => Win32Error.ThrowLastErrorIfFalse(SetWindowPos(Handle, default, value.X, value.Y, -1, -1, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOSIZE | SetWindowPosFlags.SWP_NOZORDER)); - } - - /// Gets or sets the window's show state. - /// The show state. - public ShowWindowCommand ShowState - { - get - { - if (!Visible) - return ShowWindowCommand.SW_HIDE; - if (IsIconic(Handle)) - return ShowWindowCommand.SW_MINIMIZE; - if (IsZoomed(Handle)) - return ShowWindowCommand.SW_MAXIMIZE; - return ShowWindowCommand.SW_NORMAL; - } - set => Win32Error.ThrowLastErrorIfFalse(ShowWindow(Handle, value)); - } - - /// Gets or sets the size of the window. - /// The size of window. - public SIZE Size - { - get - { - Win32Error.ThrowLastErrorIfFalse(GetWindowRect(Handle, out RECT r)); - return r.Size; - } - set => Win32Error.ThrowLastErrorIfFalse(SetWindowPos(Handle, default, -1, -1, value.cx, value.cy, SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOMOVE | SetWindowPosFlags.SWP_NOZORDER)); - } - - /// Gets or sets the value of the style bit for the window. - /// The styles value. - public WindowStyles Styles - { - get => (WindowStyles)Param[WindowLongFlags.GWL_STYLE].ToInt32(); - set => Param[WindowLongFlags.GWL_STYLE] = (IntPtr)(int)value; - } - - /// Gets or sets the value of the extended style bit for the window. - /// The extended styles value. - public WindowStylesEx StylesEx - { - get => (WindowStylesEx)Param[WindowLongFlags.GWL_EXSTYLE].ToInt32(); - set => Param[WindowLongFlags.GWL_EXSTYLE] = (IntPtr)(int)value; - } - - /// Gets or sets the text of the window's title bar (if it has one). - /// The text of the title bar. - public string Text - { - get - { - if (!Handle.IsNull) - { - int len = GetWindowTextLength(Handle); - if (len > 0) - { - StringBuilder sb = new(len + 1); - if (GetWindowText(Handle, sb, len) > 0) - return sb.ToString(); - } - } - return string.Empty; - } - set => Win32Error.ThrowLastErrorIfFalse(SetWindowText(Handle, value)); - } - - /// Gets a value indicating whether this window is visible. - /// if visible; otherwise, . - public bool Visible => IsWindowVisible(Handle); - - /// Converts a rectangle from this window's client coordinates to screen coordinates. - /// A in client coordinates. - /// The resulting in screen coordinates. - public virtual RECT ClientToScreen(in RECT clientRect) - { - RECT screenRect = clientRect; - SetLastError(0); - Win32Error.ThrowLastErrorIf(MapWindowPoints(Handle, IntPtr.Zero, ref screenRect), i => i == 0); - Win32Error.ThrowLastErrorIfFalse(AdjustWindowRectEx(ref screenRect, Styles, GetMenu(Handle) != IntPtr.Zero, StylesEx)); - return screenRect; - } - - /// Sets input focus to the window. - public void Focus() => Win32Error.ThrowLastErrorIf(SetFocus(Handle), h => h.IsNull); - - /// Hides the window (sets state to SW_HIDE). - public void Hide() => ShowState = ShowWindowCommand.SW_HIDE; - - /// - /// Invalidates the specified region of the windows (adds it to the window's update region, which is the area that will be repainted at - /// the next paint operation), and causes a paint message to be sent to the window. Optionally, invalidates the child windows assigned to - /// the window. - /// - /// to invalidate the window's child windows; otherwise, . - /// - /// A that represents the region to invalidate. This value can be , in which case the entire - /// client region is invalidated. - /// - public void Invalidate(bool erase = false, PRECT? pRect = null) => Win32Error.ThrowLastErrorIfFalse(InvalidateRect(Handle, pRect, erase)); - - /// Converts a rectangle from screen coordinates to this window's client coordinates. - /// A in screen coordinates. - /// The resulting in client coordinates. - public virtual RECT ScreenToClient(in RECT screenRect) - { - RECT clientRect = screenRect; - SetLastError(0); - Win32Error.ThrowLastErrorIf(MapWindowPoints(IntPtr.Zero, Handle, ref clientRect), i => i == 0); - RECT invRect = default; - Win32Error.ThrowLastErrorIfFalse(AdjustWindowRectEx(ref invRect, Styles, GetMenu(Handle) != IntPtr.Zero, StylesEx)); - SubtractRect(out clientRect, clientRect, invRect); - return clientRect; - } - - /// - /// Changes the size, position, and Z order of a child, pop-up, or top-level window. These windows are ordered according to their - /// appearance on the screen. The topmost window receives the highest rank and is the first window in the Z order. - /// - /// - /// Type: HWND - /// - /// A handle to the window to precede the positioned window in the Z order. This parameter must be a window handle or one of the - /// following values. - /// - /// - /// - /// Value - /// Meaning - /// - /// - /// HWND_BOTTOM (HWND)1 - /// - /// Places the window at the bottom of the Z order. If the hWnd parameter identifies a topmost window, the window loses its topmost - /// status and is placed at the bottom of all other windows. - /// - /// - /// - /// HWND_NOTOPMOST (HWND)-2 - /// - /// Places the window above all non-topmost windows (that is, behind all topmost windows). This flag has no effect if the window is - /// already a non-topmost window. - /// - /// - /// - /// HWND_TOP (HWND)0 - /// Places the window at the top of the Z order. - /// - /// - /// HWND_TOPMOST (HWND)-1 - /// Places the window above all non-topmost windows. The window maintains its topmost position even when it is deactivated. - /// - /// - /// For more information about how this parameter is used, see the following Remarks section. - /// - /// The new position of the window, in client coordinates. - /// The new width of the window, in pixels. - /// The window sizing and positioning flags. This parameter can be one of more values from . - /// - /// Type: Type: BOOL - /// If the function succeeds, the return value is nonzero. - /// If the function fails, the return value is zero. To get extended error information, call GetLastError. - /// - /// - /// - /// As part of the Vista re-architecture, all services were moved off the interactive desktop into Session 0. hwnd and window manager - /// operations are only effective inside a session and cross-session attempts to manipulate the hwnd will fail. For more information, see - /// The Windows Vista Developer Story: Application Compatibility Cookbook. - /// - /// - /// If you have changed certain window data using SetWindowLong, you must call SetWindowPos for the changes to take effect. Use - /// the following combination for uFlags: . - /// - /// - /// A window can be made a topmost window either by setting the hWndInsertAfter parameter to HWND_TOPMOST and ensuring that the - /// SWP_NOZORDER flag is not set, or by setting a window's position in the Z order so that it is above any existing topmost - /// windows. When a non-topmost window is made topmost, its owned windows are also made topmost. Its owners, however, are not changed. - /// - /// - /// If neither the SWP_NOACTIVATE nor SWP_NOZORDER flag is specified (that is, when the application requests that a window - /// be simultaneously activated and its position in the Z order changed), the value specified in hWndInsertAfter is used only in the - /// following circumstances. - /// - /// - /// - /// Neither the HWND_TOPMOST nor HWND_NOTOPMOST flag is specified in hWndInsertAfter. - /// - /// - /// The window identified by hWnd is not the active window. - /// - /// - /// - /// An application cannot activate an inactive window without also bringing it to the top of the Z order. Applications can change an - /// activated window's position in the Z order without restrictions, or it can activate a window and then move it to the top of the - /// topmost or non-topmost windows. - /// - /// - /// If a topmost window is repositioned to the bottom ( HWND_BOTTOM) of the Z order or after any non-topmost window, it is no - /// longer topmost. When a topmost window is made non-topmost, its owners and its owned windows are also made non-topmost windows. - /// - /// - /// A non-topmost window can own a topmost window, but the reverse cannot occur. Any window (for example, a dialog box) owned by a - /// topmost window is itself made a topmost window, to ensure that all owned windows stay above their owner. - /// - /// If an application is not in the foreground, and should be in the foreground, it must call the SetForegroundWindow function. - /// To use SetWindowPos to bring a window to the top, the process that owns the window must have SetForegroundWindow permission. - /// - public void SetPosition(POINT position, SIZE size, HWND hWndInsertAfter = default, SetWindowPosFlags flags = SetWindowPosFlags.SWP_NOACTIVATE | SetWindowPosFlags.SWP_NOZORDER) => - Win32Error.ThrowLastErrorIfFalse(SetWindowPos(Handle, hWndInsertAfter, position.X, position.Y, size.cx, size.cy, flags)); - - /// Shows the window (sets state to SW_SHOWNORMAL). - public void Show() => ShowState = ShowWindowCommand.SW_SHOWNORMAL; - - /// - /// The Validate function validates the client area within a rectangle by removing the rectangle from the update region of the - /// this window. - /// - /// - /// A structure that contains the client coordinates of the rectangle to be removed from the update region. If this - /// parameter is , the entire client area is removed. - /// - public void Validate(PRECT? pRect = null) => Win32Error.ThrowLastErrorIfFalse(ValidateRect(Handle, pRect)); -} - /// Simple window wrapper. /// /// /// public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWindowInit, IWindowHandle { - private static object createWindowSyncObject = new(); - private readonly WeakReference weakThisPtr; + /// A window procedure override delegate. + protected WindowProc? customWndProc; + + private static readonly object createWindowSyncObject = new(); + private readonly WeakReference weakThisPtr; + private bool createdClass = false; private SafeHWND? hwnd; private bool isDisposed; private ParamIndexer? paramIndexer; @@ -456,6 +42,10 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin /// Initializes a new instance of the class without creating the window. public WindowBase() => weakThisPtr = new(this); + /// Initializes a new instance of the class and defines a window procedure to use. + /// The window procedure override delegate. + public WindowBase(WindowProc wndProcOverride) : this() => customWndProc = wndProcOverride; + internal WindowBase(HWND hwnd) : this() => wCls = WindowClass.GetInstanceFromWindow(hwnd) ?? throw Win32Error.GetLastError().GetException(); /// Finalizes an instance of the class. @@ -491,20 +81,22 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin /// text of the control. When creating a static control with the SS_ICON style, use lpWindowName to specify the icon name or /// identifier. To specify an identifier, use the syntax "#num". /// - /// - /// The initial position and size of the window. + /// + /// The width and height, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or + /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default + /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial + /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is + /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. + /// + /// /// - /// For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the window's upper-left corner, in screen - /// coordinates. For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of - /// the parent window's client area. If x is set to CW_USEDEFAULT, the system selects the default position for the window's - /// upper-left corner and ignores the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a - /// pop-up or child window, the x and y parameters are set to zero. - /// - /// - /// The initial vertical position of the window. For an overlapped or pop-up window, the y parameter is the initial y-coordinate of the - /// window's upper-left corner, in screen coordinates. For a child window, y is the initial y-coordinate of the upper-left corner of the - /// child window relative to the upper-left corner of the parent window's client area. For a list box y is the initial y-coordinate of - /// the upper-left corner of the list box's client area relative to the upper-left corner of the parent window's client area. + /// The initial vertical position of the window. For an overlapped or pop-up window, the x parameter is the initial x-coordinate of the + /// window's upper-left corner and the y parameter is the initial y-coordinate of the window's upper-left corner, in screen coordinates. + /// For a child window, x is the x-coordinate of the upper-left corner of the window relative to the upper-left corner of the parent + /// window's client area and y is the initial y-coordinate of the upper-left corner of the child window relative to the upper-left + /// corner. If x is set to CW_USEDEFAULT, the system selects the default position for the window's upper-left corner and ignores + /// the y parameter. CW_USEDEFAULT is valid only for overlapped windows; if it is specified for a pop-up or child window, the x + /// and y parameters are set to zero. /// /// /// If an overlapped window is created with the WS_VISIBLE style bit set and the x parameter is set to CW_USEDEFAULT, then @@ -512,17 +104,6 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin /// ShowWindow with the SW_SHOW flag after the window has been created. If the y parameter is some other value, then the window /// manager calls ShowWindow with that value as the nCmdShow parameter. /// - /// - /// The width, in device units, of the window. For overlapped windows, nWidth is the window's width, in screen coordinates, or - /// CW_USEDEFAULT. If nWidth is CW_USEDEFAULT, the system selects a default width and height for the window; the default - /// width extends from the initial x-coordinates to the right edge of the screen; the default height extends from the initial - /// y-coordinate to the top of the icon area. CW_USEDEFAULT is valid only for overlapped windows; if CW_USEDEFAULT is - /// specified for a pop-up or child window, the nWidth and nHeight parameter are set to zero. - /// - /// - /// The height, in device units, of the window. For overlapped windows, nHeight is the window's height, in screen coordinates. If the - /// nWidth parameter is set to CW_USEDEFAULT, the system ignores nHeight. - /// /// /// /// Type: DWORD @@ -548,7 +129,7 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin /// application determines the child-window identifier; it must be unique for all child windows with the same parent window. /// /// - public virtual void CreateHandle(WindowClass? wc = null, string? text = null, RECT? bounds = default, + public virtual void CreateHandle(WindowClass? wc = null, string? text = null, SIZE? size = default, POINT? position = default, WindowStyles style = 0, WindowStylesEx exStyle = 0, HWND parent = default, HMENU hMenu = default) { lock (this) @@ -559,16 +140,20 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin if (hwnd is not null) return; - wCls = wc ?? WindowClass.MakeVisibleWindowClass($"{GetType().Name}+{Guid.NewGuid()}", null); - bounds ??= new(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT); + if ((wCls = wc) is null) + { + wCls = WindowClass.MakeVisibleWindowClass($"{GetType().Name}+{Guid.NewGuid()}", null); + createdClass = true; + } + size ??= new(CW_USEDEFAULT, CW_USEDEFAULT); + position ??= new(CW_USEDEFAULT, CW_USEDEFAULT); GCHandle gcWnd = GCHandle.Alloc(this); - IntPtr lpParam = GCHandle.ToIntPtr(gcWnd); try { if (text?.Length > short.MaxValue) text = text.Substring(0, short.MaxValue); - hwnd = Win32Error.ThrowLastErrorIfInvalid(CreateWindowEx(exStyle, wCls.ClassName, text, style, bounds.Value.X, - bounds.Value.Y, bounds.Value.Width, bounds.Value.Height, parent, hMenu, wCls.wc.hInstance, lpParam)); + hwnd = Win32Error.ThrowLastErrorIfInvalid(CreateWindowEx(exStyle, wCls.ClassName, text, style, position.Value.X, + position.Value.Y, size.Value.Width, size.Value.Height, parent, hMenu, wCls.wc.hInstance, GCHandle.ToIntPtr(gcWnd))); } finally { @@ -591,10 +176,15 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin /// IntPtr IWindowInit.InitWndProcOnNCCreate(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) { - Attach(hwnd, true); + lock (this) + Attach(hwnd, true); return InternalWndProc(hwnd, msg, IntPtr.Zero, lParam); } + /// Writes message information to the debugger. + /// The message code. + /// The source file path. + /// The calling method. [Conditional("DEBUG")] internal static void DebugWriteMessageInfo(uint msg, [CallerFilePath] string sourceFilePath = "", [CallerMemberName] string? caller = "") { @@ -604,49 +194,35 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin Debug.WriteLine($"{caller}={msg:x} ({sourceFilePath})"); } + /// Writes method information to the debugger. + /// The source file path. + /// The calling method. [Conditional("DEBUG")] internal static void DebugWriteMethod([CallerFilePath] string sourceFilePath = "", [CallerMemberName] string? caller = "") => Debug.WriteLine($"{caller} ({sourceFilePath})"); - internal void Attach(HWND hwnd, bool own) + /// + /// Attaches the specified window handle to this instance and associates this instance's handlers using GWL_WNDPROC. + /// + /// The window handle. + /// if set to , the window will be destroyed and handle released on disposal. + protected virtual void Attach(HWND hwnd, bool own) { - lock (this) - { - CheckDisposed(); - Debug.Assert(!hwnd.IsNull, "Attempt assigning invalid handle."); - CheckDetached(); + CheckDisposed(); + Debug.Assert(!hwnd.IsNull, "Attempt assigning invalid handle."); + CheckDetached(); - // Set handle - this.hwnd = new((IntPtr)hwnd, own); + // Set handle + this.hwnd = new((IntPtr)hwnd, own); - // Set handle's WNDPROC to this class' - userDefWndProc ??= DefWindowProc; - defWndProc = Param[WindowLongFlags.GWLP_WNDPROC]; - wndProc = InternalWndProc; - Param[WindowLongFlags.GWLP_WNDPROC] = Marshal.GetFunctionPointerForDelegate(wndProc); - Debug.Assert(defWndProc != Param[WindowLongFlags.GWLP_WNDPROC], "Subclassed ourself!"); + // Set handle's WNDPROC to this class' + userDefWndProc ??= DefWindowProc; + defWndProc = Param[WindowLongFlags.GWLP_WNDPROC]; + wndProc = InternalWndProc; + Param[WindowLongFlags.GWLP_WNDPROC] = Marshal.GetFunctionPointerForDelegate(wndProc); + Debug.Assert(defWndProc != Param[WindowLongFlags.GWLP_WNDPROC], "Subclassed ourself!"); - OnHandleChanged(hwnd); - } - } - - internal void Detach() - { - if (hwnd is null) return; - - lock (this) - { - if (prevWndProcPtr != IntPtr.Zero && !Handle.IsNull) - { - Param[WindowLongFlags.GWLP_WNDPROC] = prevWndProcPtr; - prevWndProcPtr = IntPtr.Zero; - } - - hwnd.Dispose(); - hwnd = null; - - OnHandleChanged(HWND.NULL); - } + OnHandleChanged(hwnd); } /// Invokes the default window procedure associated with this window. @@ -662,6 +238,26 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin protected virtual IntPtr DefWndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) => defWndProc == default ? DefWindowProc(hwnd, msg, wParam, lParam) : CallWindowProc(defWndProc, hwnd, msg, wParam, lParam); + /// + /// Detaches this instance's handlers using GWL_WNDPROC, restoring previous assignment, and destroys the window + /// and closes the handle if owned. + /// + protected virtual void Detach() + { + if (hwnd is null) return; + + if (!hwnd.IsInvalid && prevWndProcPtr != IntPtr.Zero) + { + Param[WindowLongFlags.GWLP_WNDPROC] = prevWndProcPtr; + prevWndProcPtr = IntPtr.Zero; + } + + hwnd.Dispose(); + hwnd = null; + + OnHandleChanged(HWND.NULL); + } + /// Releases unmanaged and - optionally - managed resources. /// /// to release both managed and unmanaged resources; to release only unmanaged resources. @@ -670,7 +266,9 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin { if (isDisposed) return; - Detach(); + lock (this) + Detach(); + if (createdClass) wCls?.Unregister(); isDisposed = true; } @@ -710,6 +308,20 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin if (isDisposed) throw new ObjectDisposedException(nameof(WindowBase)); } + /// + /// This is the real instance for the window. It calls either + /// or depending on status. + /// It is responsible for setting and calling and . + /// + /// A handle to the window procedure that received the message. + /// The message. + /// + /// Additional message information. The content of this parameter depends on the value of the parameter. + /// + /// + /// Additional message information. The content of this parameter depends on the value of the parameter. + /// + /// The return value is the result of the message processing and depends on the message. private IntPtr InternalWndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) { DebugWriteMessageInfo(msg); @@ -721,8 +333,8 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin try { - if (weakThisPtr.TryGetTarget(out _)) - return WndProc(hwnd, msg, wParam, lParam); + if (weakThisPtr.IsAlive && weakThisPtr.Target != null) + return (customWndProc ?? WndProc)(hwnd, msg, wParam, lParam); else return DefWndProc(hwnd, msg, wParam, lParam); } @@ -740,6 +352,7 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin Created?.Invoke(); break; case WindowMessage.WM_NCDESTROY: + PostQuitMessage(0); Destroyed?.Invoke(); break; } @@ -754,8 +367,8 @@ public class WindowBase : MarshalByRefObject, IDisposable, IWindowInstance, IWin public IntPtr this[WindowLongFlags flag] { - get => GetWindowLongAuto(win.Handle, flag); - set => SetWindowLong(win.Handle, flag, value); + get => win.Handle != HWND.NULL ? GetWindowLongAuto(win.Handle, flag) : throw new ObjectDisposedException(nameof(IWindowInstance)); + set { if (win.Handle != HWND.NULL) SetWindowLong(win.Handle, flag, value); else throw new ObjectDisposedException(nameof(IWindowInstance)); } } } } \ No newline at end of file diff --git a/PInvoke/User32/WindowClass.cs b/PInvoke/User32/WindowClass.cs index 7884a521..2e14e788 100644 --- a/PInvoke/User32/WindowClass.cs +++ b/PInvoke/User32/WindowClass.cs @@ -1,6 +1,5 @@ #nullable enable using System; -using System.Diagnostics; using System.Runtime.InteropServices; using System.Text; using Vanara.Extensions; @@ -34,8 +33,8 @@ namespace Vanara.PInvoke private static readonly uint cbSize = (uint)Marshal.SizeOf(typeof(WNDCLASSEX)); private static SafeHICON? appIcon; private static SafeHCURSOR? arrowCursor; - private readonly WindowProc? wndProc; private readonly WindowProc instProc; + private readonly WindowProc? wndProc; /// Initializes a new instance of the class and registers the class name. /// @@ -127,7 +126,7 @@ namespace Vanara.PInvoke } } - private WindowClass() => instProc = InstanceWndProc; + private WindowClass() => instProc = PrimaryClassWndProc; /// Gets a handle to the brush representing the standard MDI window background (COLOR_APPWORKSPACE). /// The standard MDI window background brush handle. @@ -229,15 +228,18 @@ namespace Vanara.PInvoke /// Additional message information. The contents of this parameter depend on the value of the uMsg parameter. /// Additional message information. The contents of this parameter depend on the value of the uMsg parameter. /// The return value is the result of the message processing and depends on the message sent. - protected virtual IntPtr InstanceWndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) + protected virtual IntPtr PrimaryClassWndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) { WindowBase.DebugWriteMessageInfo(msg); if (msg == (uint)WindowMessage.WM_NCCREATE) { - var wParamForNcCreate = Marshal.GetFunctionPointerForDelegate(wndProc); - var cp = lParam.ToStructure().lpCreateParams; - if (cp != IntPtr.Zero && GCHandle.FromIntPtr(cp).Target is IWindowInit wnd) - return wnd.InitWndProcOnNCCreate(hwnd, msg, Marshal.GetFunctionPointerForDelegate(wndProc), lParam); + try + { + var cp = lParam.ToStructure().lpCreateParams; + if (cp != IntPtr.Zero && GCHandle.FromIntPtr(cp).Target is IWindowInit wnd) + return wnd.InitWndProcOnNCCreate(hwnd, msg, Marshal.GetFunctionPointerForDelegate(wndProc), lParam); + } + catch { } } return wndProc?.Invoke(hwnd, msg, wParam, lParam) ?? IntPtr.Zero; } diff --git a/UnitTests/PInvoke/User32/WrapperTests.cs b/UnitTests/PInvoke/User32/WrapperTests.cs index c649fcbf..f0b83521 100644 --- a/UnitTests/PInvoke/User32/WrapperTests.cs +++ b/UnitTests/PInvoke/User32/WrapperTests.cs @@ -1,4 +1,5 @@ -using NUnit.Framework; +using ICSharpCode.Decompiler.IL; +using NUnit.Framework; using System; using System.Linq; using System.Reflection; @@ -62,23 +63,27 @@ public partial class User32Tests [Test] public void WindowPumpTest() { - HWND hwnd = default; - TestWin wnd = new("Hello", new(0, 0, 300, 200)); - wnd.Show(); - new MessagePump().Run(wnd); - wnd.Dispose(); - Assert.IsFalse(IsWindowVisible(hwnd)); - Assert.IsTrue(wnd.gotMsg); + VisibleWindow.Run(WndProc, "Hello"); + + static IntPtr WndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) + { + System.Diagnostics.Debug.WriteLine($"TestWndProc={(WindowMessage)msg} (WrapperTests.cs)"); + if (msg == (uint)WindowMessage.WM_CREATE) MessageBox(hwnd, "Got it!"); + return DefWindowProc(hwnd, msg, wParam, lParam); + } } - private class TestWin : VisibleWindow + [Test] + public void WindowRunTest() { - public bool gotMsg = false; + VisibleWindow.Run(null, "Hello"); + } - public TestWin(string text, RECT bounds) : base(null, text, bounds) { } + public class MyWin : VisibleWindow + { protected override IntPtr WndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) { - gotMsg = true; + if (msg == (uint)WindowMessage.WM_CREATE) MessageBox(hwnd, "Got it!"); return base.WndProc(hwnd, msg, wParam, lParam); } }