Skip to content

Commit 26657f4

Browse files
committed
Set up subclassing for common controls
Subclass existing controls so we can get initial messages (such as WM_CREATE). Port BtnLook sample from WInterop.
1 parent 85ea173 commit 26657f4

File tree

12 files changed

+393
-34
lines changed

12 files changed

+393
-34
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project Sdk="Microsoft.NET.Sdk">
3+
<PropertyGroup>
4+
<OutputType>WinExe</OutputType>
5+
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
6+
<ApplicationManifest>app.manifest</ApplicationManifest>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\..\..\thirtytwo\thirtytwo.csproj" />
10+
</ItemGroup>
11+
</Project>
+177
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System.Drawing;
5+
using Windows;
6+
using Windows.Win32;
7+
using Windows.Win32.Foundation;
8+
9+
namespace OwnDraw;
10+
11+
/// <summary>
12+
/// Sample from Programming Windows, 5th Edition.
13+
/// Original (c) Charles Petzold, 1998
14+
/// Figure 9-3, Pages 375-380.
15+
/// </summary>
16+
internal static class Program
17+
{
18+
[STAThread]
19+
private static void Main() => Application.Run(new OwnerDraw("Owner-Draw Button Demo"));
20+
21+
private class OwnerDraw : MainWindow
22+
{
23+
private HWND _hwndSmaller, _hwndLarger;
24+
private int _cxClient, _cyClient;
25+
private int _btnWidth, _btnHeight;
26+
private Size _baseUnits;
27+
private const int ID_SMALLER = 1;
28+
private const int ID_LARGER = 2;
29+
30+
public OwnerDraw(string title) : base(title)
31+
{
32+
}
33+
34+
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
35+
{
36+
switch (message)
37+
{
38+
case MessageType.Create:
39+
int baseUnits = Interop.GetDialogBaseUnits();
40+
_baseUnits = new(baseUnits & 0xFFFF, baseUnits >> 16);
41+
_btnWidth = _baseUnits.Width * 8;
42+
_btnHeight = _baseUnits.Height * 4;
43+
44+
// Create the owner-draw pushbuttons
45+
_hwndSmaller = new ButtonControl(
46+
style: WindowStyles.Child | WindowStyles.Visible,
47+
buttonStyle: ButtonControl.Styles.OwnerDrawn,
48+
parentWindow: this,
49+
buttonId: ID_SMALLER);
50+
_hwndLarger = new ButtonControl(
51+
style: WindowStyles.Child | WindowStyles.Visible,
52+
buttonStyle: ButtonControl.Styles.OwnerDrawn,
53+
parentWindow: this,
54+
buttonId: ID_LARGER);
55+
56+
return (LRESULT)0;
57+
58+
case MessageType.Size:
59+
_cxClient = lParam.LOWORD;
60+
_cyClient = lParam.HIWORD;
61+
62+
// Move the buttons to the new center
63+
_hwndSmaller.MoveWindow(
64+
new Rectangle(_cxClient / 2 - 3 * _btnWidth / 2, _cyClient / 2 - _btnHeight / 2, _btnWidth, _btnHeight),
65+
repaint: true);
66+
_hwndLarger.MoveWindow(
67+
new Rectangle(_cxClient / 2 + _btnWidth / 2, _cyClient / 2 - _btnHeight / 2, _btnWidth, _btnHeight),
68+
repaint: true);
69+
return (LRESULT)0;
70+
71+
case MessageType.Command:
72+
Rectangle rc = window.GetWindowRectangle();
73+
74+
// Make the window 10% smaller or larger
75+
switch ((int)(uint)wParam)
76+
{
77+
case ID_SMALLER:
78+
rc.Inflate(rc.Width / -10, rc.Height / -10);
79+
break;
80+
case ID_LARGER:
81+
rc.Inflate(rc.Width / 10, rc.Height / 10);
82+
break;
83+
}
84+
85+
window.MoveWindow(rc, repaint: true);
86+
return (LRESULT)0;
87+
88+
case MessageType.DrawItem:
89+
90+
var drawItemMessage = new Message.DrawItem(lParam);
91+
92+
// Fill area with white and frame it black
93+
using (DeviceContext dc = drawItemMessage.DeviceContext)
94+
{
95+
Rectangle rect = drawItemMessage.ItemRectangle;
96+
97+
dc.FillRectangle(rect, StockBrush.White);
98+
dc.FrameRectangle(rect, StockBrush.Black);
99+
100+
// Draw inward and outward black triangles
101+
int cx = rect.Right - rect.Left;
102+
int cy = rect.Bottom - rect.Top;
103+
104+
Point[] pt = new Point[3];
105+
106+
switch ((int)drawItemMessage.ControlId)
107+
{
108+
case ID_SMALLER:
109+
pt[0].X = 3 * cx / 8; pt[0].Y = 1 * cy / 8;
110+
pt[1].X = 5 * cx / 8; pt[1].Y = 1 * cy / 8;
111+
pt[2].X = 4 * cx / 8; pt[2].Y = 3 * cy / 8;
112+
Triangle(dc, pt);
113+
pt[0].X = 7 * cx / 8; pt[0].Y = 3 * cy / 8;
114+
pt[1].X = 7 * cx / 8; pt[1].Y = 5 * cy / 8;
115+
pt[2].X = 5 * cx / 8; pt[2].Y = 4 * cy / 8;
116+
Triangle(dc, pt);
117+
pt[0].X = 5 * cx / 8; pt[0].Y = 7 * cy / 8;
118+
pt[1].X = 3 * cx / 8; pt[1].Y = 7 * cy / 8;
119+
pt[2].X = 4 * cx / 8; pt[2].Y = 5 * cy / 8;
120+
Triangle(dc, pt);
121+
pt[0].X = 1 * cx / 8; pt[0].Y = 5 * cy / 8;
122+
pt[1].X = 1 * cx / 8; pt[1].Y = 3 * cy / 8;
123+
pt[2].X = 3 * cx / 8; pt[2].Y = 4 * cy / 8;
124+
Triangle(dc, pt);
125+
break;
126+
case ID_LARGER:
127+
pt[0].X = 5 * cx / 8; pt[0].Y = 3 * cy / 8;
128+
pt[1].X = 3 * cx / 8; pt[1].Y = 3 * cy / 8;
129+
pt[2].X = 4 * cx / 8; pt[2].Y = 1 * cy / 8;
130+
Triangle(dc, pt);
131+
pt[0].X = 5 * cx / 8; pt[0].Y = 5 * cy / 8;
132+
pt[1].X = 5 * cx / 8; pt[1].Y = 3 * cy / 8;
133+
pt[2].X = 7 * cx / 8; pt[2].Y = 4 * cy / 8;
134+
Triangle(dc, pt);
135+
pt[0].X = 3 * cx / 8; pt[0].Y = 5 * cy / 8;
136+
pt[1].X = 5 * cx / 8; pt[1].Y = 5 * cy / 8;
137+
pt[2].X = 4 * cx / 8; pt[2].Y = 7 * cy / 8;
138+
Triangle(dc, pt);
139+
pt[0].X = 3 * cx / 8; pt[0].Y = 3 * cy / 8;
140+
pt[1].X = 3 * cx / 8; pt[1].Y = 5 * cy / 8;
141+
pt[2].X = 1 * cx / 8; pt[2].Y = 4 * cy / 8;
142+
Triangle(dc, pt);
143+
break;
144+
}
145+
146+
// Invert the rectangle if the button is selected
147+
if (drawItemMessage.ItemState.HasFlag(Message.DrawItem.States.Selected))
148+
{
149+
dc.InvertRectangle(rect);
150+
}
151+
152+
if (drawItemMessage.ItemState.HasFlag(Message.DrawItem.States.Focus))
153+
{
154+
rect = Rectangle.FromLTRB(
155+
rect.Left + cx / 16,
156+
rect.Top + cy / 16,
157+
rect.Right - cx / 16,
158+
rect.Bottom - cy / 16);
159+
160+
dc.DrawFocusRectangle(rect);
161+
}
162+
}
163+
164+
return (LRESULT)0;
165+
}
166+
167+
static void Triangle(DeviceContext dc, Point[] pt)
168+
{
169+
dc.SelectObject(StockBrush.Black);
170+
dc.Polygon(pt);
171+
dc.SelectObject(StockBrush.White);
172+
}
173+
174+
return base.WindowProcedure(window, message, wParam, lParam);
175+
}
176+
}
177+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3+
<assemblyIdentity version="1.0.0.0" name="MyApplication.app"/>
4+
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
5+
<security>
6+
<requestedPrivileges xmlns="urn:schemas-microsoft-com:asm.v3">
7+
<!-- UAC Manifest Options
8+
If you want to change the Windows User Account Control level replace the
9+
requestedExecutionLevel node with one of the following.
10+
11+
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
12+
<requestedExecutionLevel level="requireAdministrator" uiAccess="false" />
13+
<requestedExecutionLevel level="highestAvailable" uiAccess="false" />
14+
15+
Specifying requestedExecutionLevel element will disable file and registry virtualization.
16+
Remove this element if your application requires this virtualization for backwards
17+
compatibility.
18+
-->
19+
<requestedExecutionLevel level="asInvoker" uiAccess="false" />
20+
</requestedPrivileges>
21+
</security>
22+
</trustInfo>
23+
24+
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
25+
<application>
26+
<!-- A list of the Windows versions that this application has been tested on and is
27+
is designed to work with. Uncomment the appropriate elements and Windows will
28+
automatically selected the most compatible environment. -->
29+
30+
<!-- Windows Vista -->
31+
<!--<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}" />-->
32+
33+
<!-- Windows 7 -->
34+
<!--<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}" />-->
35+
36+
<!-- Windows 8 -->
37+
<!--<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}" />-->
38+
39+
<!-- Windows 8.1 -->
40+
<!--<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}" />-->
41+
42+
<!-- Windows 10 -->
43+
<!--<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />-->
44+
45+
</application>
46+
</compatibility>
47+
48+
<!-- Indicates that the application is DPI-aware and will not be automatically scaled by Windows at higher
49+
DPIs. Windows Presentation Foundation (WPF) applications are automatically DPI-aware and do not need
50+
to opt in. Windows Forms applications targeting .NET Framework 4.6 that opt into this setting, should
51+
also set the 'EnableWindowsFormsHighDpiAutoResizing' setting to 'true' in their app.config. -->
52+
<!--
53+
<application xmlns="urn:schemas-microsoft-com:asm.v3">
54+
<windowsSettings>
55+
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true</dpiAware>
56+
</windowsSettings>
57+
</application>
58+
-->
59+
60+
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
61+
<dependency>
62+
<dependentAssembly>
63+
<assemblyIdentity
64+
type="win32"
65+
name="Microsoft.Windows.Common-Controls"
66+
version="6.0.0.0"
67+
processorArchitecture="*"
68+
publicKeyToken="6595b64144ccf1df"
69+
language="*"
70+
/>
71+
</dependentAssembly>
72+
</dependency>
73+
74+
</assembly>

src/thirtytwo/Controls/ButtonControl.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public ButtonControl(
1515
Styles buttonStyle = Styles.PushButton,
1616
WindowStyles style = WindowStyles.Overlapped | WindowStyles.Child | WindowStyles.Visible,
1717
ExtendedWindowStyles extendedStyle = ExtendedWindowStyles.Default,
18+
int buttonId = default,
1819
Window? parentWindow = default,
1920
nint parameters = default) : base(
2021
bounds,
@@ -23,7 +24,8 @@ public ButtonControl(
2324
extendedStyle,
2425
parentWindow,
2526
s_buttonClass,
26-
parameters)
27+
parameters,
28+
(HMENU)buttonId)
2729
{
2830
}
2931
}

src/thirtytwo/Controls/RichEditControl.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ namespace Windows;
88

99
public partial class RichEditControl : EditBase
1010
{
11-
private static readonly WindowClass s_richEditClass = new("RICHEDIT50W");
11+
private static readonly WindowClass s_richEditClass;
1212

1313
static RichEditControl()
1414
{
@@ -17,6 +17,8 @@ static RichEditControl()
1717
{
1818
Error.ThrowLastError();
1919
}
20+
21+
s_richEditClass = new("RICHEDIT50W");
2022
}
2123

2224
public RichEditControl(

src/thirtytwo/DeviceContextExtensions.cs

+40-17
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,29 @@ public static bool LineTo<T>(this T context, int x, int y) where T : IHandle<HDC
274274
return success;
275275
}
276276

277+
public static bool Ellipse<T>(this T context, Rectangle rectangle) where T : IHandle<HDC> =>
278+
context.Ellipse(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
279+
280+
public static bool Ellipse<T>(this T context, int left, int top, int right, int bottom) where T : IHandle<HDC>
281+
{
282+
bool success = Interop.Ellipse(context.Handle, left, top, right, bottom);
283+
GC.KeepAlive(context.Wrapper);
284+
return success;
285+
}
286+
287+
public static bool PolyBezier<T>(this T context, params Point[] points) where T : IHandle<HDC> =>
288+
PolyBezier(context, points.AsSpan());
289+
290+
public static bool PolyBezier<T>(this T context, ReadOnlySpan<Point> points) where T : IHandle<HDC>
291+
{
292+
fixed (Point* p = points)
293+
{
294+
bool success = Interop.PolyBezier(context.Handle, p, (uint)points.Length);
295+
GC.KeepAlive(context.Wrapper);
296+
return success;
297+
}
298+
}
299+
277300
public static bool Rectangle<T>(this T context, Rectangle rectangle) where T : IHandle<HDC> =>
278301
context.Rectangle(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
279302

@@ -296,34 +319,34 @@ public static bool RoundRectangle<T>(this T context, int left, int top, int righ
296319
return success;
297320
}
298321

299-
public static bool Ellipse<T>(this T context, Rectangle rectangle) where T : IHandle<HDC> =>
300-
context.Ellipse(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
301-
302-
public static bool Ellipse<T>(this T context, int left, int top, int right, int bottom) where T : IHandle<HDC>
322+
public static bool FillRectangle<T>(this T context, Rectangle rectangle, HBRUSH hbrush) where T : IHandle<HDC>
303323
{
304-
bool success = Interop.Ellipse(context.Handle, left, top, right, bottom);
324+
RECT rect = rectangle;
325+
bool success = (BOOL)Interop.FillRect(context.Handle, &rect, hbrush);
305326
GC.KeepAlive(context.Wrapper);
306327
return success;
307328
}
308329

309-
public static unsafe bool PolyBezier<T>(this T context, params Point[] points) where T : IHandle<HDC> =>
310-
PolyBezier(context, points.AsSpan());
330+
public static bool FrameRectangle<T>(this T context, Rectangle rectangle, HBRUSH brush) where T : IHandle<HDC>
331+
{
332+
RECT rect = rectangle;
333+
bool success = (BOOL)Interop.FrameRect(context.Handle, &rect, brush);
334+
GC.KeepAlive(context.Wrapper);
335+
return success;
336+
}
311337

312-
public static unsafe bool PolyBezier<T>(this T context, ReadOnlySpan<Point> points) where T : IHandle<HDC>
338+
public static bool InvertRectangle<T>(this T context, Rectangle rectangle) where T : IHandle<HDC>
313339
{
314-
fixed (Point* p = points)
315-
{
316-
bool success = Interop.PolyBezier(context.Handle, p, (uint)points.Length);
317-
GC.KeepAlive(context.Wrapper);
318-
return success;
319-
}
340+
RECT rect = rectangle;
341+
bool success = Interop.InvertRect(context.Handle, &rect);
342+
GC.KeepAlive(context.Wrapper);
343+
return success;
320344
}
321345

322-
public static bool FillRectangle<T>(this T context, Rectangle rectangle, HBRUSH hbrush)
323-
where T : IHandle<HDC>
346+
public static bool DrawFocusRectangle<T>(this T context, Rectangle rectangle) where T : IHandle<HDC>
324347
{
325348
RECT rect = rectangle;
326-
bool success = (BOOL)Interop.FillRect(context.Handle, &rect, hbrush);
349+
bool success = Interop.DrawFocusRect(context.Handle, &rect);
327350
GC.KeepAlive(context.Wrapper);
328351
return success;
329352
}

0 commit comments

Comments
 (0)