#nullable enable using System; using System.Runtime.InteropServices; using System.Text; using Vanara.Extensions; using static Vanara.PInvoke.Gdi32; using static Vanara.PInvoke.User32; namespace Vanara.PInvoke { /// Interface identifying a class that can subclass a window proceedure. public interface IWindowInit { /// /// Method called on WM_NCCREATE which takes control of the window procedure. The window must provide a GCHandle pointer to itself in /// the lpParam parameter of CreateWindowEx. This method implentation should confirm it's HWND value against /// and then using SetWindowLongPtr with GWLP_WNDPROC to update the window procedure. /// /// A handle to the window. /// Always WM_NCCREATE. /// A pointer to the window class' WindowProc delegate. /// A pointer to a instance with the window creation paramters. /// The return should be IntPtr(1) to indicate success. Any other value will stop the completion of CreateWindowEx. IntPtr InitWndProcOnNCCreate(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam); } /// Encapsulates a window class. public class WindowClass { /// The instance of the populated for the window class. public readonly WNDCLASSEX wc; private static readonly uint cbSize = (uint)Marshal.SizeOf(typeof(WNDCLASSEX)); private static SafeHICON? appIcon; private static SafeHCURSOR? arrowCursor; private readonly WindowProc instProc; private readonly WindowProc? wndProc; /// Initializes a new instance of the class and registers the class name. /// /// /// A string that specifies the window class name. The class name can be any name registered with RegisterClass or RegisterClassEx, /// or any of the predefined control-class names. /// /// /// The maximum length for lpszClassName is 256. If lpszClassName is greater than the maximum length, the /// RegisterClassEx function will fail. /// /// /// A handle to the instance that contains the window procedure for the class. /// /// A pointer to the window procedure. You must use the CallWindowProc function to call the window procedure. For more information, /// see WindowProc. /// /// The class style(s). This member can be any combination of the Class Styles. /// /// A handle to the class icon. This member must be a handle to an icon resource. If this member is NULL, the system provides /// a default icon. /// /// /// A handle to a small icon that is associated with the window class. If this member is NULL, the system searches the icon /// resource specified by the hIcon member for an icon of the appropriate size to use as the small icon. /// /// /// A handle to the class cursor. This member must be a handle to a cursor resource. If this member is NULL, an application /// must explicitly set the cursor shape whenever the mouse moves into the application's window. /// /// /// A handle to the class background brush. This member can be a handle to the brush to be used for painting the background, or it /// can be a color value. A color value must be one of the following standard system colors (the value 1 must be added to the chosen color). /// /// The system automatically deletes class background brushes when the class is unregistered by using . /// An application should not delete these brushes. /// /// /// When this member is NULL, an application must paint its own background whenever it is requested to paint in its client /// area. To determine whether the background must be painted, an application can either process the WM_ERASEBKGND message or test /// the fErase member of the PAINTSTRUCT structure filled by the BeginPaint function. /// /// /// /// A string that specifies the resource name of the class menu, as the name appears in the resource file. If you use an integer to /// identify the menu, use the MAKEINTRESOURCE macro. If this member is NULL, windows belonging to this class have no default menu. /// /// /// The number of extra bytes to allocate following the window-class structure. The system initializes the bytes to zero. /// /// /// The number of extra bytes to allocate following the window instance. The system initializes the bytes to zero. If an application /// uses WNDCLASSEX to register a dialog box created by using the CLASS directive in the resource file, it must set /// this member to DLGWINDOWEXTRA. /// public WindowClass(string? className = null, HINSTANCE hInst = default, WindowProc? wndProc = default, WindowClassStyles styles = 0, HICON hIcon = default, HICON hSmIcon = default, HCURSOR hCursor = default, HBRUSH hbrBkgd = default, string? menuName = null, int extraBytes = 0, int extraWinBytes = 0) : this() { this.wndProc = wndProc ?? DefWindowProc; wc = new WNDCLASSEX { cbSize = cbSize, lpfnWndProc = instProc, hInstance = hInst.IsNull ? Kernel32.GetModuleHandle() : hInst, lpszClassName = className ?? Guid.NewGuid().ToString("N"), style = styles, hIcon = hIcon, hIconSm = hSmIcon, hCursor = hCursor, hbrBackground = hbrBkgd, lpszMenuName = menuName, cbClsExtra = extraBytes, cbWndExtra = extraWinBytes, }; Win32Error.ThrowLastErrorIfNull(Macros.MAKEINTATOM(RegisterClassEx(wc))); } /// Initializes a new instance of the class using a instance. /// The instance. /// if set to , is used to register the class. public WindowClass(in WNDCLASSEX wcx, bool register = true) : this() { wc = wcx; if (register) { wndProc = wc.lpfnWndProc ?? DefWindowProc; wc.lpfnWndProc = instProc; Win32Error.ThrowLastErrorIfNull(Macros.MAKEINTATOM(RegisterClassEx(wc))); } } 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. public static HBRUSH MdiWindowBrush => (HBRUSH)(SystemColorIndex.COLOR_APPWORKSPACE + 1); /// /// Gets a handle to a null brush (GetStockObject(NULL_BRUSH)). Use this for the background of non-displayable windows or to prevent /// flicker on custom drawn backgrounds. /// /// The null background brush handle. public static HBRUSH NullBrush => GetStockObject(StockObjectType.NULL_BRUSH); /// Gets a handle to the standard application icon (IDI_APPLICATION). /// The standard application icon handle. public static HICON StdAppIcon => appIcon ??= LoadIcon(default, IDI_APPLICATION); /// Gets a handle to the standard arrow cursor (IDC_ARROW). /// The standard arrow cursor handle. public static HCURSOR StdArrowCursor => arrowCursor ??= LoadCursor(default, IDC_ARROW); /// Gets a handle to the brush representing the standard window background (COLOR_WINDOW). /// The standard window background brush handle. public static HBRUSH WindowBrush => (HBRUSH)(SystemColorIndex.COLOR_WINDOW + 1); /// Gets the windows class name. public string ClassName => wc.lpszClassName; /// Gets the class style(s). public WindowClassStyles Styles => wc.style; /// Gets the that is executed by this class. /// The executing . public WindowProc WndProc => wndProc ?? DefWindowProc; /// Gets a instance associated with a window handle. /// The window handle to examine. /// /// If the function finds a matching window and successfully copies the data, the return value is a /// instance. If not, is returned. /// public static WindowClass? GetInstanceFromWindow(HWND hWnd) => GetNamedInstance(GetClassName(hWnd), GetClassLong(hWnd, GetClassLongFlag.GCL_HMODULE)); /// Gets a instance by looking up the name. /// /// The class name. The name must be that of a preregistered class or a class registered by a previous call to the RegisterClass or /// RegisterClassEx function. /// /// /// 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. /// /// /// If the function finds a matching class and successfully copies the data, the return value is a /// instance. If not, is returned. /// public static WindowClass? GetNamedInstance(string className, HINSTANCE hInst) { if (string.IsNullOrEmpty(className)) throw new ArgumentException($"'{nameof(className)}' cannot be null or empty.", nameof(className)); return GetClassInfoEx(hInst, className, out var wcx) ? new(wcx, false) : null; } /// Creates a instance that uses common settings for a displayed window. /// /// /// A string that specifies the window class name. The class name can be any name registered with RegisterClass or RegisterClassEx, /// or any of the predefined control-class names. /// /// /// The maximum length for lpszClassName is 256. If lpszClassName is greater than the maximum length, the /// RegisterClassEx function will fail. /// /// /// A handle to the instance that contains the window procedure for the class. /// /// A pointer to the window procedure. You must use the CallWindowProc function to call the window procedure. For more information, /// see WindowProc. /// /// /// A new instance of with indicated parameters, standard app icon, arrow cursor, and window system color background. /// public static WindowClass MakeVisibleWindowClass(string? className, WindowProc? wndProc, HINSTANCE hInst = default) => new(className, hInst.IsNull ? Kernel32.GetModuleHandle() : hInst, wndProc, 0, StdAppIcon, default, StdArrowCursor, WindowBrush); /// Unregisters this window class. /// /// If the function succeeds, the return value is . /// /// If the class could not be found or if a window still exists that was created with the class, the return value is . To get extended error information, call GetLastError. /// /// public bool Unregister() => UnregisterClass(wc.lpszClassName, wc.hInstance); /// An class function that processes messages sent to this class instance. /// A handle to the window. /// The MSG. /// 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 PrimaryClassWndProc(HWND hwnd, uint msg, IntPtr wParam, IntPtr lParam) { WindowBase.DebugWriteMessageInfo(msg); if (msg == (uint)WindowMessage.WM_NCCREATE) { 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 ?? throw new NullReferenceException()), lParam); } catch { } } return wndProc?.Invoke(hwnd, msg, wParam, lParam) ?? IntPtr.Zero; } private static string GetClassName(HWND hwnd) { StringBuilder sb = new(257); Win32Error.ThrowLastErrorIf(User32.GetClassName(hwnd, sb, sb.Capacity), i => i == 0); return sb.ToString(); } } }