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