Skip to content

Commit 1bd452e

Browse files
committed
Port Beeper and LineDemo from WInterop
1 parent 7ece471 commit 1bd452e

13 files changed

+352
-40
lines changed

.editorconfig

+3
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,9 @@ csharp_style_prefer_not_pattern = true:suggestion
402402
# SYSLIB1096: Convert to 'GeneratedComInterface'
403403
dotnet_diagnostic.SYSLIB1096.severity = none
404404

405+
# SA1117: Parameters should be on same line or separate lines
406+
dotnet_diagnostic.SA1117.severity = silent
407+
405408
[*.{cs,vb}]
406409
dotnet_style_coalesce_expression = true:suggestion
407410
dotnet_style_null_propagation = true:silent
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
</PropertyGroup>
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\..\..\thirtytwo\thirtytwo.csproj" />
9+
</ItemGroup>
10+
</Project>
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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+
using Windows.Win32.Graphics.Gdi;
9+
using Windows.Win32.UI.WindowsAndMessaging;
10+
11+
namespace Beeper;
12+
13+
internal class Beeper1 : MainWindow
14+
{
15+
private bool _flipFlop = false;
16+
private nuint _timerId;
17+
18+
public Beeper1(string title) : base(title) { }
19+
20+
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
21+
{
22+
switch (message)
23+
{
24+
case MessageType.Create:
25+
_timerId = window.SetTimer(1000);
26+
return (LRESULT)0;
27+
case MessageType.Timer:
28+
Interop.MessageBeep(MESSAGEBOX_STYLE.MB_OK);
29+
_flipFlop = !_flipFlop;
30+
window.Invalidate();
31+
return (LRESULT)0;
32+
case MessageType.Paint:
33+
using (DeviceContext dc = window.BeginPaint())
34+
{
35+
using HBRUSH brush = HBRUSH.CreateSolid(_flipFlop ? Color.Red : Color.Blue);
36+
dc.FillRectangle(window.GetClientRectangle(), brush);
37+
}
38+
return (LRESULT)0;
39+
case MessageType.Destroy:
40+
window.KillTimer(_timerId);
41+
break;
42+
}
43+
44+
return base.WindowProcedure(window, message, wParam, lParam);
45+
}
46+
}
+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
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+
using Windows.Win32.Graphics.Gdi;
9+
using Windows.Win32.UI.WindowsAndMessaging;
10+
11+
namespace Beeper;
12+
13+
internal class Beeper2 : MainWindow
14+
{
15+
private bool _flipFlop = false;
16+
private nuint _timerId;
17+
private TimerProcedure? _timerCallback;
18+
19+
public Beeper2(string title) : base(title) { }
20+
21+
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
22+
{
23+
switch (message)
24+
{
25+
case MessageType.Create:
26+
_timerCallback = TimerProcedure;
27+
_timerId = window.SetTimer(1000, callback: _timerCallback);
28+
return (LRESULT)0;
29+
case MessageType.Destroy:
30+
window.KillTimer(_timerId);
31+
break;
32+
}
33+
34+
return base.WindowProcedure(window, message, wParam, lParam);
35+
}
36+
37+
private void TimerProcedure(HWND window, MessageType message, nuint timerId, uint time)
38+
{
39+
Interop.MessageBeep(MESSAGEBOX_STYLE.MB_OK);
40+
_flipFlop = !_flipFlop;
41+
using DeviceContext dc = window.GetDeviceContext();
42+
using HBRUSH brush = HBRUSH.CreateSolid(_flipFlop ? Color.Red : Color.Blue);
43+
dc.FillRectangle(window.GetClientRectangle(), brush);
44+
}
45+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
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 Windows;
5+
6+
namespace Beeper;
7+
8+
/// <summary>
9+
/// Samples from Programming Windows, 5th Edition.
10+
/// Original (c) Charles Petzold, 1998
11+
/// </summary>
12+
internal static class Program
13+
{
14+
[STAThread]
15+
private static void Main()
16+
{
17+
// Figure 8-1, Pages 331-333.
18+
Application.Run(new Beeper1("Timer on Message Loop"));
19+
20+
// Figure 8-2, Pages 335-337.
21+
Application.Run(new Beeper2("Timer Procedure"));
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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+
</PropertyGroup>
7+
<ItemGroup>
8+
<ProjectReference Include="..\..\..\..\thirtytwo\thirtytwo.csproj" />
9+
</ItemGroup>
10+
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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.Foundation;
7+
8+
namespace LineDemo;
9+
10+
/// <summary>
11+
/// Sample from Programming Windows, 5th Edition.
12+
/// Original (c) Charles Petzold, 1998
13+
/// Figure 5-14, Pages 153-155.
14+
/// </summary>
15+
internal static class Program
16+
{
17+
[STAThread]
18+
private static void Main() => Application.Run(new LineDemo("LineDemo"));
19+
20+
private class LineDemo : MainWindow
21+
{
22+
private static int s_cxClient, s_cyClient;
23+
24+
public LineDemo(string title) : base(title) { }
25+
26+
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
27+
{
28+
switch (message)
29+
{
30+
case MessageType.Size:
31+
s_cxClient = lParam.LOWORD;
32+
s_cyClient = lParam.HIWORD;
33+
return (LRESULT)0;
34+
case MessageType.Paint:
35+
using (DeviceContext dc = window.BeginPaint())
36+
{
37+
dc.Rectangle(s_cxClient / 8, s_cyClient / 8, 7 * s_cxClient / 8, 7 * s_cyClient / 8);
38+
dc.MoveTo(0, 0);
39+
dc.LineTo(s_cxClient, s_cyClient);
40+
dc.MoveTo(0, s_cyClient);
41+
dc.LineTo(s_cxClient, 0);
42+
dc.Ellipse(s_cxClient / 8, s_cyClient / 8, 7 * s_cxClient / 8, 7 * s_cyClient / 8);
43+
dc.RoundRectangle(
44+
Rectangle.FromLTRB(s_cxClient / 4, s_cyClient / 4, 3 * s_cxClient / 4, 3 * s_cyClient / 4),
45+
new Size(s_cxClient / 4, s_cyClient / 4));
46+
}
47+
48+
return (LRESULT)0;
49+
}
50+
51+
return base.WindowProcedure(window, message, wParam, lParam);
52+
}
53+
}
54+
}

src/thirtytwo/DeviceContextExtensions.cs

+57-17
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,7 @@ namespace Windows;
1010
public static unsafe partial class DeviceContextExtensions
1111
{
1212
/// <inheritdoc cref="Interop.GetGraphicsMode(HDC)"/>
13-
public static GRAPHICS_MODE GetGraphicsMode<T>(this T context)
14-
where T : IHandle<HDC>
13+
public static GRAPHICS_MODE GetGraphicsMode<T>(this T context) where T : IHandle<HDC>
1514
{
1615
GRAPHICS_MODE mode = (GRAPHICS_MODE)Interop.GetGraphicsMode(context.Handle);
1716
GC.KeepAlive(context.Wrapper);
@@ -97,8 +96,8 @@ public static PolyFillMode SetPolyFillMode<T>(this T context, PolyFillMode mode)
9796
return result;
9897
}
9998

100-
public static bool Polygon<T>(this T context, params Point[] points)
101-
where T : IHandle<HDC> => Polygon(context, points.AsSpan());
99+
public static bool Polygon<T>(this T context, params Point[] points) where T : IHandle<HDC> =>
100+
Polygon(context, points.AsSpan());
102101

103102
public static bool Polygon<T>(this T context, ReadOnlySpan<Point> points)
104103
where T : IHandle<HDC>
@@ -147,7 +146,14 @@ private static (int Height, uint LengthDrawn, Rectangle Bounds) DrawTextHelper<T
147146
// The string won't be changed, we can just pin
148147
fixed (char* c = text)
149148
{
150-
int result = Interop.DrawTextEx(context.Handle, (PWSTR)c, text.Length, bounds, (DRAW_TEXT_FORMAT)format, dtp);
149+
int result = Interop.DrawTextEx(
150+
context.Handle,
151+
(PWSTR)c,
152+
text.Length,
153+
bounds,
154+
(DRAW_TEXT_FORMAT)format,
155+
dtp);
156+
151157
if (result == 0)
152158
{
153159
Error.ThrowIfLastErrorNot(WIN32_ERROR.ERROR_SUCCESS);
@@ -182,7 +188,14 @@ public static void DrawIcon<TDeviceContext, TIcon>(
182188
where TDeviceContext : IHandle<HDC>
183189
where TIcon : IHandle<HICON>
184190
{
185-
if (!Interop.DrawIconEx(context.Handle, location.X, location.Y, icon.Handle, size.Width, size.Height, 0, default, flags))
191+
if (!Interop.DrawIconEx(
192+
context.Handle,
193+
location.X, location.Y,
194+
icon.Handle,
195+
size.Width, size.Height,
196+
0,
197+
HBRUSH.Null,
198+
flags))
186199
{
187200
Error.ThrowLastError();
188201
}
@@ -204,8 +217,7 @@ public static DeviceContext CreateCompatibleDeviceContext<TDeviceContext>(this T
204217
return DeviceContext.Create(hdc, ownsHandle: true);
205218
}
206219

207-
public static Bitmap CreateCompatibleBitmap<T>(this T context, Size size)
208-
where T : IHandle<HDC>
220+
public static Bitmap CreateCompatibleBitmap<T>(this T context, Size size) where T : IHandle<HDC>
209221
{
210222
HBITMAP hbitmap = Interop.CreateCompatibleBitmap(context.Handle, size.Width, size.Height);
211223
if (hbitmap.IsNull)
@@ -242,26 +254,54 @@ public static RegionType SelectClippingRegion<T>(this T context, HRGN region)
242254
return type;
243255
}
244256

245-
public static bool MoveTo<T>(this T context, Point point)
246-
where T : IHandle<HDC>
247-
=> context.MoveTo(point.X, point.Y);
257+
public static bool MoveTo<T>(this T context, Point point) where T : IHandle<HDC> =>
258+
context.MoveTo(point.X, point.Y);
248259

249-
public static bool MoveTo<T>(this T context, int x, int y)
250-
where T : IHandle<HDC>
260+
public static bool MoveTo<T>(this T context, int x, int y) where T : IHandle<HDC>
251261
{
252262
bool result = Interop.MoveToEx(context.Handle, x, y, null);
253263
GC.KeepAlive(context.Wrapper);
254264
return result;
255265
}
256266

257-
public static bool LineTo<T>(this T context, Point point)
267+
public static bool LineTo<T>(this T context, Point point) where T : IHandle<HDC> =>
268+
context.LineTo(point.X, point.Y);
269+
270+
public static bool LineTo<T>(this T context, int x, int y) where T : IHandle<HDC>
271+
{
272+
bool success = Interop.LineTo(context.Handle, x, y);
273+
GC.KeepAlive(context.Wrapper);
274+
return success;
275+
}
276+
277+
public static bool Rectangle<T>(this T context, Rectangle rectangle) where T : IHandle<HDC> =>
278+
context.Rectangle(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom);
279+
280+
public static bool Rectangle<T>(this T context, int left, int top, int right, int bottom)
258281
where T : IHandle<HDC>
259-
=> context.LineTo(point.X, point.Y);
282+
{
283+
bool success = Interop.Rectangle(context.Handle, left, top, right, bottom);
284+
GC.KeepAlive(context.Wrapper);
285+
return success;
286+
}
287+
288+
public static bool RoundRectangle<T>(this T context, Rectangle rectangle, Size corner) where T : IHandle<HDC> =>
289+
context.RoundRectangle(rectangle.Left, rectangle.Top, rectangle.Right, rectangle.Bottom, corner.Width, corner.Height);
260290

261-
public static bool LineTo<T>(this T context, int x, int y)
291+
public static bool RoundRectangle<T>(this T context, int left, int top, int right, int bottom, int width, int height)
262292
where T : IHandle<HDC>
263293
{
264-
bool success = Interop.LineTo(context.Handle, x, y);
294+
bool success = Interop.RoundRect(context.Handle, left, top, right, bottom, width, height);
295+
GC.KeepAlive(context.Wrapper);
296+
return success;
297+
}
298+
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>
303+
{
304+
bool success = Interop.Ellipse(context.Handle, left, top, right, bottom);
265305
GC.KeepAlive(context.Wrapper);
266306
return success;
267307
}

src/thirtytwo/NativeMethods.txt

+9-1
Original file line numberDiff line numberDiff line change
@@ -300,4 +300,12 @@ VIRTUAL_KEY
300300
WIN32_ERROR
301301
WINDOWPOS
302302
WM_*
303-
XFORMCOORDS
303+
XFORMCOORDS
304+
FrameRect
305+
Rectangle
306+
Ellipse
307+
RoundRect
308+
MessageBeep
309+
CreateSolidBrush
310+
SetCoalescableTimer
311+
KillTimer

src/thirtytwo/Win32/Graphics/Gdi/HBRUSH.cs

+15-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using System.Drawing;
5+
using System.Runtime.CompilerServices;
6+
47
namespace Windows.Win32.Graphics.Gdi;
58

6-
public unsafe partial struct HBRUSH
9+
public unsafe partial struct HBRUSH : IDisposable
710
{
811
public static HBRUSH Invalid => new(-1);
912

13+
public static HBRUSH CreateSolid(Color color) => Interop.CreateSolidBrush((COLORREF)color);
14+
1015
public static implicit operator HBRUSH(SystemColor color) => Interop.GetSysColorBrush((SYS_COLOR_INDEX)color);
1116
public static implicit operator HBRUSH(SYS_COLOR_INDEX color) => Interop.GetSysColorBrush(color);
1217
public static implicit operator HBRUSH(StockBrush brush) => (HBRUSH)Interop.GetStockObject((GET_STOCK_OBJECT_FLAGS)brush);
@@ -16,4 +21,13 @@ public static explicit operator HBRUSH(HGDIOBJ handle)
1621
Debug.Assert(handle.IsNull || (OBJ_TYPE)Interop.GetObjectType(handle) == OBJ_TYPE.OBJ_BRUSH);
1722
return new(handle.Value);
1823
}
24+
25+
public void Dispose()
26+
{
27+
if (Value != 0 && Value != -1)
28+
{
29+
Interop.DeleteObject(this);
30+
Unsafe.AsRef(in this) = default;
31+
}
32+
}
1933
}

0 commit comments

Comments
 (0)