diff --git a/src/Common/tests/InternalUtilitiesForTests/InternalUtilitiesForTests.csproj b/src/Common/tests/InternalUtilitiesForTests/InternalUtilitiesForTests.csproj
index 2ab244ba1b4..cb833482993 100644
--- a/src/Common/tests/InternalUtilitiesForTests/InternalUtilitiesForTests.csproj
+++ b/src/Common/tests/InternalUtilitiesForTests/InternalUtilitiesForTests.csproj
@@ -15,6 +15,7 @@
+
diff --git a/src/Common/tests/InternalUtilitiesForTests/InternalsVisibleTo.cs b/src/Common/tests/InternalUtilitiesForTests/InternalsVisibleTo.cs
new file mode 100644
index 00000000000..eefe7821763
--- /dev/null
+++ b/src/Common/tests/InternalUtilitiesForTests/InternalsVisibleTo.cs
@@ -0,0 +1,12 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime.CompilerServices;
+
+// Awkward, but necessary to expose Interop based internals to other test libaries
+[assembly: InternalsVisibleTo("System.Windows.Forms.Primitives.Tests, PublicKey=00000000000000000400000000000000")]
+[assembly: InternalsVisibleTo("System.Windows.Forms.Tests, PublicKey=00000000000000000400000000000000")]
+[assembly: InternalsVisibleTo("System.Windows.Forms.Design.Tests, PublicKey=00000000000000000400000000000000")]
+[assembly: InternalsVisibleTo("WinformsControlsTest, PublicKey=00000000000000000400000000000000")]
+[assembly: InternalsVisibleTo("MauiListViewTests, PublicKey=00000000000000000400000000000000")]
diff --git a/src/Common/tests/InternalUtilitiesForTests/src/WindowClass.cs b/src/Common/tests/InternalUtilitiesForTests/src/WindowClass.cs
new file mode 100644
index 00000000000..1e603fd05d4
--- /dev/null
+++ b/src/Common/tests/InternalUtilitiesForTests/src/WindowClass.cs
@@ -0,0 +1,203 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System;
+using System.ComponentModel;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.InteropServices;
+using static Interop;
+
+namespace System
+{
+ internal class WindowClass
+ {
+ [DllImport(Libraries.User32, SetLastError = true, CharSet = CharSet.Unicode, ExactSpelling = true)]
+ public unsafe static extern IntPtr LoadIconW(
+ IntPtr hInstance,
+ IntPtr lpIconName);
+
+ private const int CW_USEDEFAULT = unchecked((int)0x80000000);
+ private const uint IDI_APPLICATION = 32512;
+ private const uint IDC_ARROW = 32512;
+ private const int COLOR_WINDOW = 5;
+
+ private static RECT DefaultBounds => new RECT(CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT);
+
+ // Stash the delegate to keep it from being collected
+ private readonly User32.WNDPROC _windowProcedure;
+ private User32.WNDCLASS _wndClass;
+ private readonly string _className;
+ private readonly string _menuName;
+
+ public ushort Atom { get; private set; }
+ public IntPtr MainWindow { get; private set; }
+ public IntPtr ModuleInstance { get; }
+
+ ///
+ /// Constructor.
+ ///
+ /// Name, or default will be generated.
+ /// Module to associate with the window. The entry assembly is the default.
+ /// Use (IntPtr)(-1) for no background brush.
+ /// Use (IntPtr)(-1) for no icon.
+ /// Use (IntPtr)(-1) for no cursor.
+ /// Menu name, can not set with .
+ /// Menu id, can not set with .
+ public unsafe WindowClass(
+ string className = default,
+ IntPtr moduleInstance = default,
+ User32.CS classStyle = User32.CS.HREDRAW | User32.CS.VREDRAW,
+ IntPtr backgroundBrush = default,
+ IntPtr icon = default,
+ IntPtr cursor = default,
+ string menuName = null,
+ int menuId = 0,
+ int classExtraBytes = 0,
+ int windowExtraBytes = 0)
+ {
+ // Handle default values
+ className ??= Guid.NewGuid().ToString();
+
+ if (backgroundBrush == default)
+ {
+ backgroundBrush = User32.GetSysColorBrush(COLOR_WINDOW);
+ }
+ else if (backgroundBrush == (IntPtr)(-1))
+ {
+ backgroundBrush = default;
+ }
+
+ if (icon == default)
+ {
+ icon = LoadIconW(IntPtr.Zero, (IntPtr)IDI_APPLICATION);
+ }
+ else if (icon == (IntPtr)(-1))
+ {
+ icon = default;
+ }
+
+ if (cursor == default)
+ {
+ cursor = User32.LoadCursorW(IntPtr.Zero, (IntPtr)User32.CursorResourceId.IDC_ARROW);
+ }
+ else if (cursor == (IntPtr)(-1))
+ {
+ cursor = default;
+ }
+
+ if (moduleInstance == IntPtr.Zero)
+ Marshal.GetHINSTANCE(Assembly.GetCallingAssembly().Modules.First());
+
+ if (menuId != 0 && menuName != null)
+ throw new ArgumentException($"Can't set both {nameof(menuName)} and {nameof(menuId)}.");
+
+ _windowProcedure = WNDPROC;
+ ModuleInstance = moduleInstance;
+
+ _className = className;
+ _menuName = menuName ?? string.Empty;
+
+ _wndClass = new User32.WNDCLASS
+ {
+ style = classStyle,
+ lpfnWndProc = Marshal.GetFunctionPointerForDelegate(_windowProcedure),
+ cbClsExtra = classExtraBytes,
+ cbWndExtra = windowExtraBytes,
+ hInstance = moduleInstance,
+ hIcon = icon,
+ hCursor = cursor,
+ hbrBackground = backgroundBrush,
+ lpszMenuName = (char*)menuId
+ };
+ }
+
+ public bool IsRegistered => Atom != 0;
+
+ public unsafe WindowClass Register()
+ {
+ fixed (char* name = _className)
+ fixed (char* menuName = _menuName)
+ {
+ _wndClass.lpszClassName = name;
+ if (!string.IsNullOrEmpty(_menuName))
+ _wndClass.lpszMenuName = menuName;
+
+ ushort atom = User32.RegisterClassW(ref _wndClass);
+ if (atom == 0)
+ {
+ throw new Win32Exception();
+ }
+ Atom = atom;
+ return this;
+ }
+ }
+
+ public IntPtr CreateWindow(
+ string windowName = null,
+ User32.WS style = User32.WS.OVERLAPPED,
+ User32.WS_EX extendedStyle = default,
+ bool isMainWindow = false,
+ IntPtr parentWindow = default,
+ IntPtr parameters = default,
+ IntPtr menuHandle = default)
+ {
+ return CreateWindow(
+ DefaultBounds,
+ windowName,
+ style,
+ extendedStyle,
+ isMainWindow,
+ parentWindow,
+ parameters,
+ menuHandle);
+ }
+
+ public unsafe IntPtr CreateWindow(
+ RECT bounds,
+ string windowName = null,
+ User32.WS style = User32.WS.OVERLAPPED,
+ User32.WS_EX extendedStyle = default,
+ bool isMainWindow = false,
+ IntPtr parentWindow = default,
+ IntPtr parameters = default,
+ IntPtr menuHandle = default)
+ {
+ if (!IsRegistered)
+ throw new ArgumentException("Window class must be registered before using.");
+
+ IntPtr window = User32.CreateWindowExW(
+ dwExStyle: extendedStyle,
+ lpClassName: (char*)Atom,
+ lpWindowName: windowName,
+ dwStyle: style,
+ X: bounds.X,
+ Y: bounds.Y,
+ nWidth: bounds.Width,
+ nHeight: bounds.Height,
+ hWndParent: parentWindow,
+ hMenu: menuHandle,
+ hInst: IntPtr.Zero,
+ lpParam: parameters);
+
+ if (isMainWindow)
+ MainWindow = window;
+
+ return window;
+ }
+
+ protected virtual IntPtr WNDPROC(IntPtr hWnd, User32.WM msg, IntPtr wParam, IntPtr lParam)
+ {
+ switch (msg)
+ {
+ case User32.WM.DESTROY:
+ if (hWnd == MainWindow)
+ User32.PostQuitMessage(0);
+ return (IntPtr)0;
+ }
+
+ return User32.DefWindowProcW(hWnd, msg, wParam, lParam);
+ }
+ }
+}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/Interop.RECT.cs b/src/System.Windows.Forms.Primitives/src/Interop/Interop.RECT.cs
index 170c179b2c7..a9df9aa129e 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/Interop.RECT.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/Interop.RECT.cs
@@ -37,7 +37,17 @@ public static implicit operator Rectangle(RECT r)
public static implicit operator RECT(Rectangle r)
=> new RECT(r);
+ public int X => left;
+
+ public int Y => top;
+
+ public int Width
+ => right - left;
+
+ public int Height
+ => bottom - top;
+
public Size Size
- => new Size(right - left, bottom - top);
+ => new Size(Width, Height);
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CS.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CS.cs
index 4df92454d90..097234ce981 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CS.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CS.cs
@@ -14,6 +14,8 @@ internal static partial class User32
[Flags]
public enum CS : uint
{
+ VREDRAW = 0x0001,
+ HREDRAW = 0x0002,
DBLCLKS = 0x0008,
DROPSHADOW = 0x00020000,
SAVEBITS = 0x0800
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CreateWindowExW.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CreateWindowExW.cs
index 1204d35c3f9..279346d4e82 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CreateWindowExW.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.CreateWindowExW.cs
@@ -10,11 +10,11 @@ internal static partial class Interop
internal static partial class User32
{
[DllImport(Libraries.User32, CharSet = CharSet.Unicode, SetLastError = true)]
- public static extern IntPtr CreateWindowExW(
- int dwExStyle,
- string lpClassName,
+ public unsafe static extern IntPtr CreateWindowExW(
+ WS_EX dwExStyle,
+ char* lpClassName,
string lpWindowName,
- int dwStyle,
+ WS dwStyle,
int X,
int Y,
int nWidth,
@@ -23,5 +23,25 @@ public static extern IntPtr CreateWindowExW(
IntPtr hMenu,
IntPtr hInst,
[MarshalAs(UnmanagedType.AsAny)] object lpParam);
+
+ public unsafe static IntPtr CreateWindowExW(
+ WS_EX dwExStyle,
+ string lpClassName,
+ string lpWindowName,
+ WS dwStyle,
+ int X,
+ int Y,
+ int nWidth,
+ int nHeight,
+ IntPtr hWndParent,
+ IntPtr hMenu,
+ IntPtr hInst,
+ object lpParam)
+ {
+ fixed(char* c = lpClassName)
+ {
+ return CreateWindowExW(dwExStyle, c, lpWindowName, dwStyle, X, Y, nWidth, nHeight, hWndParent, hMenu, hInst, lpParam);
+ }
+ }
}
}
diff --git a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.LoadCursorW.cs b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.LoadCursorW.cs
index 993a092260c..e95d26edf2d 100644
--- a/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.LoadCursorW.cs
+++ b/src/System.Windows.Forms.Primitives/src/Interop/User32/Interop.LoadCursorW.cs
@@ -9,6 +9,11 @@ internal static partial class Interop
{
internal static partial class User32
{
+ // The Cursor class has an IntPtr constructor that takes a handle
+ // to an existing cursor. The int constructor does the LoadCursorW
+ // call. To avoid accidental use of the IntPtr constructor this
+ // set of defines should be left as int, even though they ultimately
+ // need converted to IntPtr.
public static class CursorResourceId
{
public const int IDC_ARROW = 32512;
@@ -28,6 +33,6 @@ public static class CursorResourceId
}
[DllImport(Libraries.User32, ExactSpelling = true)]
- public static extern IntPtr LoadCursorW(IntPtr hInst, int iconId);
+ public static extern IntPtr LoadCursorW(IntPtr hInstance, IntPtr lpCursorName);
}
}
diff --git a/src/System.Windows.Forms.Primitives/tests/Interop/User32/GetWindowTextTests.cs b/src/System.Windows.Forms.Primitives/tests/Interop/User32/GetWindowTextTests.cs
index 1f40c99f426..87035132bbd 100644
--- a/src/System.Windows.Forms.Primitives/tests/Interop/User32/GetWindowTextTests.cs
+++ b/src/System.Windows.Forms.Primitives/tests/Interop/User32/GetWindowTextTests.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using Xunit;
-using static Interop;
using static Interop.User32;
namespace System.Windows.Forms.Primitives.Tests.Interop.User32
@@ -29,26 +28,23 @@ private void CallGetWindowText(bool useBeforeGetTextLengthCallback)
// Use a long string that exceeds the initial buffer size (16).
string longText = new string('X', 50);
- using var form = new ChangeWindowTextForm()
- {
- Text = shortText
- };
-
- // Creating the handle causes GetWindowText to be called,
- // so do it before setting the delegates.
- IntPtr formHandle = form.Handle;
+ var windowClass = new ChangeWindowTextClass();
+ windowClass.Register();
+ IntPtr windowHandle = windowClass.CreateWindow(shortText);
- form.BeforeGetTextCallback = () => longText;
+ windowClass.BeforeGetTextCallback = () => longText;
if (useBeforeGetTextLengthCallback)
{
- form.BeforeGetTextLengthCallback = () => shortText;
+ windowClass.BeforeGetTextLengthCallback = () => shortText;
}
- string result = GetWindowText(formHandle);
+ string result = GetWindowText(windowHandle);
+ DestroyWindow(windowHandle);
+
Assert.Equal(longText, result);
}
- private class ChangeWindowTextForm : Form
+ private class ChangeWindowTextClass : WindowClass
{
public Func BeforeGetTextLengthCallback
{
@@ -62,26 +58,27 @@ public Func BeforeGetTextCallback
set;
}
- protected override void WndProc(ref Message m)
+ protected override IntPtr WNDPROC(IntPtr hWnd, WM msg, IntPtr wParam, IntPtr lParam)
{
- if (m.Msg == (int)WM.GETTEXTLENGTH)
- {
- string text = BeforeGetTextLengthCallback?.Invoke();
- if (text != null)
- {
- SetWindowTextW(m.HWnd, text);
- }
- }
- else if (m.Msg == (int)WM.GETTEXT)
+ switch (msg)
{
- string text = BeforeGetTextCallback?.Invoke();
- if (text != null)
- {
- SetWindowTextW(m.HWnd, text);
- }
+ case WM.GETTEXTLENGTH:
+ string text = BeforeGetTextLengthCallback?.Invoke();
+ if (text != null)
+ {
+ SetWindowTextW(hWnd, text);
+ }
+ break;
+ case WM.GETTEXT:
+ text = BeforeGetTextCallback?.Invoke();
+ if (text != null)
+ {
+ SetWindowTextW(hWnd, text);
+ }
+ break;
}
- base.WndProc(ref m);
+ return base.WNDPROC(hWnd, msg, wParam, lParam);
}
}
}
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs b/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs
index 1a141ab52bd..1943d9f0e90 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/Cursor.cs
@@ -40,7 +40,7 @@ internal Cursor(int nResourceId)
// We don't delete stock cursors.
_ownHandle = false;
_resourceId = nResourceId;
- _handle = User32.LoadCursorW(IntPtr.Zero, nResourceId);
+ _handle = User32.LoadCursorW(IntPtr.Zero, (IntPtr)nResourceId);
}
///
diff --git a/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs b/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs
index 4a31d8525f7..24bb15f192c 100644
--- a/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs
+++ b/src/System.Windows.Forms/src/System/Windows/Forms/NativeWindow.cs
@@ -454,10 +454,10 @@ public virtual void CreateHandle(CreateParams cp)
}
createResult = User32.CreateWindowExW(
- cp.ExStyle,
+ (User32.WS_EX)cp.ExStyle,
windowClass._windowClassName,
cp.Caption,
- cp.Style,
+ (User32.WS)cp.Style,
cp.X,
cp.Y,
cp.Width,