Skip to content

Commit

Permalink
Update Window creation
Browse files Browse the repository at this point in the history
A number of tweaks to Window creation.

- Add MainWindow derived class to set OverlappedWindow as default
- Allow passing a background brush to Window
- Have Application.Run dispose the run window by default
- Send messages during creation
- Move Get/SetFont handling to Window
- Clear HWND when destroyed as it is no longer valid

Also

- Add DisposableBase to handle double disposal
- Add inheritdoc to MessageType
  • Loading branch information
JeremyKuhne committed Feb 4, 2024
1 parent a106b8f commit a6f5533
Show file tree
Hide file tree
Showing 23 changed files with 840 additions and 263 deletions.
28 changes: 16 additions & 12 deletions src/samples/ClipboardViewer/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,16 @@ namespace LayoutSample;
internal class Program
{
[STAThread]
private static void Main()
{
Application.Run(new MainWindow("Clipboard Viewer Demo"));
}
private static void Main() => Application.Run(new ClipboardViewer("Clipboard Viewer Demo"));

private class MainWindow : Window
private class ClipboardViewer : MainWindow
{
private readonly EditControl _editControl;
private readonly TextLabelControl _textLabel;

public MainWindow(string title) : base(
DefaultBounds,
text: title,
style: WindowStyles.OverlappedWindow)
public ClipboardViewer(string title) : base(text: title)
{
_editControl = new EditControl(
DefaultBounds,
editStyle: EditControl.Styles.Multiline | EditControl.Styles.Left
| EditControl.Styles.AutoHorizontalScroll | EditControl.Styles.AutoVerticalScroll,
style: WindowStyles.Child | WindowStyles.Visible | WindowStyles.HorizontalScroll
Expand All @@ -35,9 +28,7 @@ public MainWindow(string title) : base(
_editControl.SetFont("Consolas", 14);

_textLabel = new TextLabelControl(
DefaultBounds,
text: "Recent clipboard text:",
style: WindowStyles.Child | WindowStyles.Visible,
parentWindow: this);

this.AddLayoutHandler(Layout.Horizontal(
Expand All @@ -61,10 +52,23 @@ protected override LRESULT WindowProcedure(HWND window, MessageType message, WPA
_editControl.Invalidate();
}
}

return (LRESULT)0;
}

return base.WindowProcedure(window, message, wParam, lParam);
}

protected override void Dispose(bool disposing)
{
if (!Handle.IsNull)
{
Clipboard.RemoveClipboardFormatListener(this);
}

base.Dispose(disposing);
_editControl.Dispose();
_textLabel.Dispose();
}
}
}
23 changes: 6 additions & 17 deletions src/samples/Layout/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,9 @@ namespace LayoutSample;
internal class Program
{
[STAThread]
private static void Main()
{
Application.Run(new MainWindow("Layout Demo"));
}
private static void Main() => Application.Run(new LayoutWindow("Layout Demo"));

private class MainWindow : Window
private class LayoutWindow : MainWindow
{
private readonly ReplaceableLayout _replaceableLayout;

Expand All @@ -24,14 +21,10 @@ private class MainWindow : Window
private readonly StaticControl _staticControl;
private readonly TextLabelControl _textLabel;

public MainWindow(string title) : base(
DefaultBounds,
text: title,
style: WindowStyles.OverlappedWindow)
public LayoutWindow(string title) : base(title)
{
_editControl = new EditControl(
DefaultBounds,
"Type text here...",
text: "Type text here...",
editStyle: EditControl.Styles.Multiline | EditControl.Styles.Left
| EditControl.Styles.AutoHorizontalScroll | EditControl.Styles.AutoVerticalScroll,
style: WindowStyles.Child | WindowStyles.Visible | WindowStyles.HorizontalScroll
Expand All @@ -41,23 +34,19 @@ public MainWindow(string title) : base(
_editControl.SetFont("Times New Roman", 24);

_buttonControl = new ButtonControl(
DefaultBounds,
text: "Push Me",
style: WindowStyles.Child | WindowStyles.Visible,
parentWindow: this);

_staticControl = new StaticControl(
DefaultBounds,
text: "You pushed it!",
style: WindowStyles.Child | WindowStyles.Visible,
parentWindow: this);

_textLabel = new TextLabelControl(
DefaultBounds,
text: "Text Label Control",
style: WindowStyles.Child | WindowStyles.Visible,
parentWindow: this);

_textLabel.SetFont("Segoe Print", 20);

var font = _buttonControl.GetFontHandle();
_staticControl.SetWindowText($"{font.GetFaceName()} {font.GetQuality()}");

Expand Down
6 changes: 3 additions & 3 deletions src/samples/Petzold/5th/Clover/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@ namespace Clover;
internal static class Program
{
[STAThread]
private static void Main() => Application.Run(new Clover(), "Draw a Clover");
private static void Main() => Application.Run(new Clover());
}

internal class Clover : WindowClass
internal class Clover : MainWindow
{
private int _cxClient, _cyClient;
private HRGN _hrgnClip;
private const double TWO_PI = Math.PI * 2;

public Clover() : base(backgroundBrush: StockBrush.White) { }
public Clover() : base(text: "Draw a Clover", backgroundBrush: StockBrush.White) { }

protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
{
Expand Down
7 changes: 3 additions & 4 deletions src/samples/Windows101/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,7 @@ private static void Main()

// The simplest way, however, is to derive from "Window", which wraps up the registration of a window class
// and the creation of a window instance, with the ability to set other appearance properties.
using HelloWindow helloWindow = new();
Application.Run(helloWindow);
Application.Run(new HelloWindow());
}

private class HelloWindowClass : WindowClass
Expand All @@ -67,9 +66,9 @@ protected override LRESULT WindowProcedure(HWND window, MessageType message, WPA
}
}

private class HelloWindow : Window
private class HelloWindow : MainWindow
{
public HelloWindow() : base(DefaultBounds, text: "HelloWindow", style: WindowStyles.OverlappedWindow)
public HelloWindow() : base("HelloWindow")
{
}

Expand Down
23 changes: 10 additions & 13 deletions src/thirtytwo/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,25 +89,15 @@ public static void Run(
string? windowTitle = null,
WindowStyles style = WindowStyles.OverlappedWindow,
ExtendedWindowStyles extendedStyle = ExtendedWindowStyles.Default,
HMENU menuHandle = default)
{
if (!windowClass.IsRegistered)
{
windowClass.Register();
}

using Window mainWindow = new(
HMENU menuHandle = default) => Run(new Window(
bounds,
windowTitle,
style,
extendedStyle,
windowClass: windowClass,
menuHandle: menuHandle);

Run(mainWindow);
}
menuHandle: menuHandle));

public static void Run(Window window)
public static void Run(Window window, bool disposeWindow = true)
{
try
{
Expand Down Expand Up @@ -135,6 +125,13 @@ public static void Run(Window window)
Interop.DestroyWindow(window);
throw;
}
finally
{
if (disposeWindow)
{
window.Dispose();
}
}
}

private static LRESULT? Window_QuitHandler(object obj, HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
Expand Down
9 changes: 9 additions & 0 deletions src/thirtytwo/Clipboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,13 @@ public static void AddClipboardFormatListener<T>(T window) where T : IHandle<HWN
Interop.AddClipboardFormatListener(window.Handle).ThrowLastErrorIfFalse();
GC.KeepAlive(window.Wrapper);
}

/// <summary>
/// Unregisters the given window from getting <see cref="MessageType.ClipboardUpdate"/> messages.
/// </summary>
public static void RemoveClipboardFormatListener<T>(T window) where T : IHandle<HWND>
{
Interop.RemoveClipboardFormatListener(window.Handle).ThrowLastErrorIfFalse();
GC.KeepAlive(window.Wrapper);
}
}
33 changes: 13 additions & 20 deletions src/thirtytwo/Components/ComponentBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,42 +9,35 @@ namespace Windows.Components;
/// Lighter weight replacement for <see cref="Component"/>.
/// </summary>
[DesignerCategory("Component")]
public class ComponentBase : IComponent
public class ComponentBase : DisposableBase.Finalizable, IComponent
{
private bool _disposedValue;
private event EventHandler? Disposed;
private event EventHandler? DisposedHandler;
private readonly object _lock = new();

ISite? IComponent.Site { get; set; }

event EventHandler? IComponent.Disposed
{
add => Disposed += value;
remove => Disposed -= value;
add => DisposedHandler += value;
remove => DisposedHandler -= value;
}

protected virtual void Dispose(bool disposing)
/// <summary>
/// Called when the component is being disposed or finalized.
/// </summary>
/// <param name="disposing">
/// <see langword="false"/> if called via a destructor on the finalizer queue. Do not access object fields
/// unless <see langword="true"/>.
/// </param>
protected override void Dispose(bool disposing)
{
if (_disposedValue)
{
return;
}

if (disposing)
{
lock (_lock)
{
((IComponent)this).Site?.Container?.Remove(this);
Disposed?.Invoke(this, EventArgs.Empty);
DisposedHandler?.Invoke(this, EventArgs.Empty);
}
}

_disposedValue = true;
}

public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
51 changes: 51 additions & 0 deletions src/thirtytwo/Components/DisposableBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Jeremy W. Kuhne. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

namespace Windows;

/// <summary>
/// Base class for implementing <see cref="IDisposable"/> with double disposal protection.
/// </summary>
public abstract class DisposableBase : IDisposable
{
private int _disposedValue;

protected bool Disposed => _disposedValue != 0;

/// <summary>
/// Called when the component is being disposed or finalized.
/// </summary>
/// <param name="disposing">
/// <see langword="false"/> if called via a destructor on the finalizer queue. Do not access object fields
/// unless <see langword="true"/>.
/// </param>
protected abstract void Dispose(bool disposing);

private void DisposeInternal(bool disposing)
{
// Want to ensure both paths are guarded against double disposal.
if (Interlocked.Exchange(ref _disposedValue, 1) == 1)
{
return;
}

Dispose(disposing);
}

/// <summary>
/// Disposes the component.
/// </summary>
public void Dispose()
{
DisposeInternal(disposing: true);
GC.SuppressFinalize(this);
}

/// <summary>
/// <see cref="DisposableBase"/> with a finalizer.
/// </summary>
public abstract class Finalizable : DisposableBase
{
~Finalizable() => DisposeInternal(disposing: false);
}
}
4 changes: 2 additions & 2 deletions src/thirtytwo/Controls/ButtonControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ public partial class ButtonControl : Window
private static readonly WindowClass s_buttonClass = new("Button");

public ButtonControl(
Rectangle bounds,
Rectangle bounds = default,
string? text = default,
Styles buttonStyle = Styles.PushButton,
WindowStyles style = WindowStyles.Overlapped,
WindowStyles style = WindowStyles.Overlapped | WindowStyles.Child | WindowStyles.Visible,
ExtendedWindowStyles extendedStyle = ExtendedWindowStyles.Default,
Window? parentWindow = default,
nint parameters = default) : base(
Expand Down
38 changes: 3 additions & 35 deletions src/thirtytwo/Controls/Control.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,51 +6,19 @@
namespace Windows;

/// <summary>
/// Base Window for custom controls.
/// Base <see cref="Window"/> for custom controls.
/// </summary>
/// <remarks>
/// <para>
/// This adds support for control-like semantics. Supports WM_GETFONT and WM_SETFONT.
/// </para>
/// </remarks>
public class Control : Window
{
// When I send a WM_GETFONT message to a window, why don't I get a font?
// https://devblogs.microsoft.com/oldnewthing/20140724-00/?p=413

// Who is responsible for destroying the font passed in the WM_SETFONT message?
// https://devblogs.microsoft.com/oldnewthing/20080912-00/?p=20893

private HFONT _font;

public Control(
Rectangle bounds,
Rectangle bounds = default,
string? text = default,
WindowStyles style = WindowStyles.Overlapped,
WindowStyles style = WindowStyles.Overlapped | WindowStyles.Child | WindowStyles.Visible,
ExtendedWindowStyles extendedStyle = ExtendedWindowStyles.Default,
Window? parentWindow = default,
WindowClass? windowClass = default,
nint parameters = 0,
HMENU menuHandle = default) : base(bounds, text, style, extendedStyle, parentWindow, windowClass, parameters, menuHandle)
{
}

protected override LRESULT WindowProcedure(HWND window, MessageType message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case MessageType.GetFont:
return (LRESULT)_font.Value;
case MessageType.SetFont:
_font = (HFONT)(nint)wParam.Value;
if ((BOOL)lParam.LOWORD)
{
this.Invalidate();
}

return (LRESULT)0;
}

return base.WindowProcedure(window, message, wParam, lParam);
}
}
Loading

0 comments on commit a6f5533

Please sign in to comment.