Skip to content

Commit 9d691ec

Browse files
committed
Start adding GDI+ support
Add GDI+ support to the level to port the Petzold Clock sample from WInterop.
1 parent 4ff4549 commit 9d691ec

18 files changed

+755
-8
lines changed
+11
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>app1.manifest</ApplicationManifest>
7+
</PropertyGroup>
8+
<ItemGroup>
9+
<ProjectReference Include="..\..\..\..\thirtytwo\thirtytwo.csproj" />
10+
</ItemGroup>
11+
</Project>
+173
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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+
// The GDI+ variant works, but is far from optimized. Creating and disposing a Graphics object every second
5+
// is clearly not optimal. Keeping things aligned with the sample to show the direct mapping.
6+
//
7+
#define GDIPLUS
8+
9+
#if GDIPLUS
10+
using Windows.Win32.Graphics.GdiPlus;
11+
using GdiPlusPen = Windows.Win32.Graphics.GdiPlus.Pen;
12+
using GdiPlusBrush = Windows.Win32.Graphics.GdiPlus.Brush;
13+
#endif
14+
15+
using Windows;
16+
using System.Drawing;
17+
using Windows.Win32.Foundation;
18+
using Windows.Win32;
19+
using Point = System.Drawing.Point;
20+
21+
namespace Clock;
22+
23+
/// <summary>
24+
/// Sample from Programming Windows, 5th Edition.
25+
/// Original (c) Charles Petzold, 1998
26+
/// Figure 8-5, Pages 346-350.
27+
/// </summary>
28+
internal static class Program
29+
{
30+
[STAThread]
31+
private static void Main() => Application.Run(new Clock("Analog Clock"));
32+
}
33+
34+
internal class Clock : MainWindow
35+
{
36+
#if GDIPLUS
37+
private readonly GdiPlusPen _blackPen = new(Color.Black);
38+
private static readonly GdiPlusBrush s_blackBrush = new SolidBrush(Color.Black);
39+
private readonly GdiPlusBrush _whiteBrush = new SolidBrush(Color.White);
40+
#endif
41+
42+
public Clock(string title) : base(title) { }
43+
44+
private void SetIsotropic(DeviceContext hdc)
45+
{
46+
hdc.SetMappingMode(MappingMode.Isotropic);
47+
hdc.SetWindowExtents(new Size(1000, 1000));
48+
hdc.SetViewportExtents(new Size(_clientSize.Width / 2, -_clientSize.Height / 2));
49+
hdc.SetViewportOrigin(new Point(_clientSize.Width / 2, _clientSize.Height / 2));
50+
}
51+
52+
private static void RotatePoint(Point[] pt, int iNum, int iAngle)
53+
{
54+
for (int i = 0; i < iNum; i++)
55+
{
56+
pt[i] = new Point
57+
(
58+
(int)(pt[i].X * Math.Cos(TWOPI * iAngle / 360) + pt[i].Y * Math.Sin(TWOPI * iAngle / 360)),
59+
(int)(pt[i].Y * Math.Cos(TWOPI * iAngle / 360) - pt[i].X * Math.Sin(TWOPI * iAngle / 360))
60+
);
61+
}
62+
}
63+
64+
private static void DrawClock(DeviceContext dc)
65+
{
66+
int iAngle;
67+
Point[] pt = new Point[3];
68+
69+
#if GDIPLUS
70+
using var graphics = new Graphics(dc);
71+
graphics.SetSmoothingMode(SmoothingMode.SmoothingModeHighQuality);
72+
#else
73+
dc.SelectObject(StockBrush.Black);
74+
#endif
75+
for (iAngle = 0; iAngle < 360; iAngle += 6)
76+
{
77+
pt[0].X = 0;
78+
pt[0].Y = 900;
79+
RotatePoint(pt, 1, iAngle);
80+
pt[2].X = pt[2].Y = iAngle % 5 != 0 ? 33 : 100;
81+
pt[0].X -= pt[2].X / 2;
82+
pt[0].Y -= pt[2].Y / 2;
83+
pt[1].X = pt[0].X + pt[2].X;
84+
pt[1].Y = pt[0].Y + pt[2].Y;
85+
#if GDIPLUS
86+
graphics.FillEllipse(s_blackBrush, pt[0].X, pt[0].Y, pt[1].X - pt[0].X, pt[1].Y - pt[0].Y);
87+
#else
88+
dc.Ellipse(Rectangle.FromLTRB(pt[0].X, pt[0].Y, pt[1].X, pt[1].Y));
89+
#endif
90+
}
91+
}
92+
93+
private void DrawHands(DeviceContext dc, SYSTEMTIME time, bool erase = false, bool drawHourAndMinuteHands = true)
94+
{
95+
int[] handAngles =
96+
[
97+
(time.wHour * 30) % 360 + time.wMinute / 2,
98+
time.wMinute * (360 / 60),
99+
time.wSecond * (360 / 60)
100+
];
101+
102+
Point[][] handPoints =
103+
[
104+
[new(0, -150), new(100, 0), new(0, 600), new(-100, 0), new(0, -150)],
105+
[new(0, -200), new(50, 0), new(0, 800), new(-50, 0), new(0, -200)],
106+
[new(0, 0), new(0, 0), new(0, 0), new(0, 0), new(0, 800)]
107+
];
108+
109+
#if GDIPLUS
110+
using var graphics = new Graphics(dc);
111+
if (erase)
112+
{
113+
graphics.FillEllipse(_whiteBrush, -830, -830, 1660, 1660);
114+
return;
115+
}
116+
117+
graphics.SetSmoothingMode(SmoothingMode.SmoothingModeHighQuality);
118+
#else
119+
dc.SelectObject(erase ? StockPen.White : StockPen.Black);
120+
#endif
121+
122+
for (int i = drawHourAndMinuteHands ? 0 : 2; i < 3; i++)
123+
{
124+
RotatePoint(handPoints[i], 5, handAngles[i]);
125+
126+
#if GDIPLUS
127+
graphics.DrawLines(_blackPen, handPoints[i]);
128+
#else
129+
dc.Polyline(handPoints[i]);
130+
#endif
131+
}
132+
}
133+
134+
private const int ID_TIMER = 1;
135+
private const double TWOPI = Math.PI * 2;
136+
private Size _clientSize;
137+
private SYSTEMTIME _previousTime;
138+
139+
protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
140+
{
141+
switch (message)
142+
{
143+
case MessageType.Create:
144+
window.SetTimer(ID_TIMER, 1000);
145+
Interop.GetLocalTime(out _previousTime);
146+
return (LRESULT)0;
147+
case MessageType.Size:
148+
_clientSize = new Message.Size(wParam, lParam).NewSize;
149+
return (LRESULT)0;
150+
case MessageType.Timer:
151+
Interop.GetLocalTime(out SYSTEMTIME time);
152+
bool drawAllHands = time.wHour != _previousTime.wHour || time.wMinute != _previousTime.wMinute;
153+
using (DeviceContext dc = window.GetDeviceContext())
154+
{
155+
SetIsotropic(dc);
156+
DrawHands(dc, _previousTime, erase: true, drawAllHands);
157+
DrawHands(dc, time);
158+
}
159+
_previousTime = time;
160+
return (LRESULT)0;
161+
case MessageType.Paint:
162+
using (DeviceContext dc = window.BeginPaint())
163+
{
164+
SetIsotropic(dc);
165+
DrawClock(dc);
166+
DrawHands(dc, _previousTime);
167+
}
168+
return (LRESULT)0;
169+
}
170+
171+
return base.WindowProcedure(window, message, wParam, lParam);
172+
}
173+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
3+
4+
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
5+
<application>
6+
<!-- Windows 10 -->
7+
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
8+
</application>
9+
</compatibility>
10+
11+
<application xmlns="urn:schemas-microsoft-com:asm.v3">
12+
<windowsSettings>
13+
<!-- Windows 10 1703 level of DPI awareness -->
14+
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness>
15+
<gdiScaling xmlns="http://schemas.microsoft.com/SMI/2017/WindowsSettings">true</gdiScaling>
16+
</windowsSettings>
17+
</application>
18+
19+
<!-- Enable themes for Windows common controls and dialogs (Windows XP and later) -->
20+
<dependency>
21+
<dependentAssembly>
22+
<assemblyIdentity
23+
type="win32"
24+
name="Microsoft.Windows.Common-Controls"
25+
version="6.0.0.0"
26+
processorArchitecture="*"
27+
publicKeyToken="6595b64144ccf1df"
28+
language="*"
29+
/>
30+
</dependentAssembly>
31+
</dependency>
32+
33+
</assembly>

src/thirtytwo/DeviceContextExtensions.cs

+79
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,20 @@ public static bool Polygon<T>(this T context, ReadOnlySpan<Point> points)
110110
}
111111
}
112112

113+
public static bool Polyline<T>(this T context, params Point[] points) where T : IHandle<HDC> =>
114+
Polyline(context, points.AsSpan());
115+
116+
public static bool Polyline<T>(this T context, ReadOnlySpan<Point> points)
117+
where T : IHandle<HDC>
118+
{
119+
fixed (Point* p = points)
120+
{
121+
bool result = Interop.Polyline(context.Handle, p, points.Length);
122+
GC.KeepAlive(context.Wrapper);
123+
return result;
124+
}
125+
}
126+
113127
public static unsafe (int Height, uint LengthDrawn, Rectangle Bounds) DrawText<T>(
114128
this T context,
115129
ReadOnlySpan<char> text,
@@ -229,6 +243,71 @@ public static Bitmap CreateCompatibleBitmap<T>(this T context, Size size) where
229243
return Bitmap.Create(hbitmap, ownsHandle: true);
230244
}
231245

246+
public static unsafe bool OffsetWindowOrigin<T>(this T context, int x, int y) where T : IHandle<HDC>
247+
{
248+
bool success = Interop.OffsetWindowOrgEx(context.Handle, x, y, null);
249+
GC.KeepAlive(context.Wrapper);
250+
return success;
251+
}
252+
253+
public static unsafe bool OffsetViewportOrigin<T>(this T context, int x, int y) where T : IHandle<HDC>
254+
{
255+
bool success = Interop.OffsetViewportOrgEx(context.Handle, x, y, null);
256+
GC.KeepAlive(context.Wrapper);
257+
return success;
258+
}
259+
260+
public static unsafe bool GetWindowExtents<T>(this T context, out Size size) where T : IHandle<HDC>
261+
{
262+
fixed (Size* s = &size)
263+
{
264+
bool success = Interop.GetWindowExtEx(context.Handle, (SIZE*)s);
265+
GC.KeepAlive(context.Wrapper);
266+
return success;
267+
}
268+
}
269+
270+
/// <summary>
271+
/// Sets the logical ("window") dimensions of the device context.
272+
/// </summary>
273+
public static unsafe bool SetWindowExtents<T>(this T context, Size size) where T : IHandle<HDC>
274+
{
275+
bool success = Interop.SetWindowExtEx(context.Handle, size.Width, size.Height, null);
276+
GC.KeepAlive(context.Wrapper);
277+
return success;
278+
}
279+
280+
public static unsafe bool GetViewportExtents<T>(this T context, out Size size) where T : IHandle<HDC>
281+
{
282+
fixed (Size* s = &size)
283+
{
284+
bool success = Interop.GetViewportExtEx(context.Handle, (SIZE*)s);
285+
GC.KeepAlive(context.Wrapper);
286+
return success;
287+
}
288+
}
289+
290+
public static unsafe bool SetViewportExtents<T>(this T context, Size size) where T : IHandle<HDC>
291+
{
292+
bool success = Interop.SetViewportExtEx(context.Handle, size.Width, size.Height, null);
293+
GC.KeepAlive(context.Wrapper);
294+
return success;
295+
}
296+
297+
public static MappingMode GetMappingMode<T>(this T context) where T : IHandle<HDC>
298+
{
299+
MappingMode result = (MappingMode)Interop.GetMapMode(context.Handle);
300+
GC.KeepAlive(context.Wrapper);
301+
return result;
302+
}
303+
304+
public static MappingMode SetMappingMode<T>(this T context, MappingMode mapMode) where T : IHandle<HDC>
305+
{
306+
MappingMode result = (MappingMode)Interop.SetMapMode(context.Handle, (HDC_MAP_MODE)mapMode);
307+
GC.KeepAlive(context.Wrapper);
308+
return result;
309+
}
310+
232311
public static unsafe Point GetViewportOrigin<T>(this T context, out bool success)
233312
where T : IHandle<HDC>
234313
{

0 commit comments

Comments
 (0)