Vanara/Windows.Forms/Extenders/GlassExtenderProvider.cs

237 lines
8.3 KiB
C#

using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Windows.Forms;
namespace Vanara.Windows.Forms;
/// <summary>GlassExtenderProvider extends a <see cref="Form"/> and provides glass margins.</summary>
[ProvideProperty("GlassEnabled", typeof(Form))]
[ProvideProperty("GlassMarginMovesForm", typeof(Form))]
[ProvideProperty("GlassMargins", typeof(Form))]
[ToolboxItem(true), ToolboxBitmap(typeof(GlassExtenderProvider), "GlassExtenderProvider.bmp")]
[Description("Extender for a Form that adds Aero glass properties.")]
public sealed class GlassExtenderProvider : Component, IExtenderProvider
{
private readonly Dictionary<Form, GlassFormProperties> formProps = new();
/// <summary>Initializes a new instance of the <see cref="GlassExtenderProvider"/> class.</summary>
public GlassExtenderProvider()
{
}
/// <summary>Gets whether glass should be extended into the client space.</summary>
/// <param name="form">The <see cref="Form"/> to be extended.</param>
/// <returns><c>true</c> if the glass is enabled; otherwise <c>false</c>.</returns>
[DisplayName("GlassEnabled")]
[DefaultValue(true)]
[Category("Behavior")]
[Description("Indicates whether extending glass into the client area is enabled.")]
public bool GetGlassEnabled(Form form) => !formProps.TryGetValue(form, out var prop) || prop.GlassEnabled;
/// <summary>Gets a value indicating whether clicking and dragging within the top margin will move the form.</summary>
/// <param name="form">The <see cref="Form"/> to be extended.</param>
/// <returns><c>true</c> if clicking and dragging on the top margin moves the form; otherwise, <c>false</c>.</returns>
[DisplayName("GlassMarginMovesForm")]
[DefaultValue(true)]
[Category("Behavior")]
[Description("Specifies if clicking and dragging within the margin will move the form. ")]
public bool GetGlassMarginMovesForm(Form form) => !formProps.TryGetValue(form, out var prop) || prop.GlassMarginMovesForm;
/// <summary>Gets the glass margins.</summary>
/// <param name="form">The <see cref="Form"/> to be extended.</param>
/// <returns>The margins where the glass will be extended.</returns>
[DefaultValue(typeof(Padding), "0")]
[DisplayName("GlassMargins")]
[Description("Specifies the interior glass margin of the form. Set to -1 for full window glass.")]
[Category("Layout")]
public Padding GetGlassMargins(Form form) => formProps.TryGetValue(form, out var prop) ? prop.GlassMargins : Padding.Empty;
/// <summary>Set whether the glass should be extended into the client space.</summary>
/// <param name="form">The <see cref="Form"/> to be extended.</param>
/// <param name="value">The enabled value.</param>
public void SetGlassEnabled(Form form, bool value)
{
GetFormProperties(form).GlassEnabled = value;
GlassifyForm(form);
}
/// <summary>Sets a value indicating whether clicking and dragging within the margin will move the form.</summary>
/// <param name="form">The <see cref="Form"/> to be extended.</param>
/// <param name="value"><c>true</c> if clicking and dragging within the margin moves the form; otherwise, <c>false</c>.</param>
public void SetGlassMarginMovesForm(Form form, bool value) => GetFormProperties(form).GlassMarginMovesForm = value;
/// <summary>Sets the glass margins.</summary>
/// <param name="form">The <see cref="Form"/> to be extended.</param>
/// <param name="value">The margins where the glass will be extended.</param>
public void SetGlassMargins(Form form, Padding value)
{
if (form == null)
throw new ArgumentNullException(nameof(form));
var prop = GetFormProperties(form);
if (value == Padding.Empty)
{
prop.GlassMargins = Padding.Empty;
UnhookForm(form);
}
else
{
prop.GlassMargins = value;
form.Paint += form_Paint;
if (!form.IsDesignMode())
{
form.MouseDown += form_MouseDown;
form.MouseMove += form_MouseMove;
form.MouseUp += form_MouseUp;
form.Resize += form_Resize;
form.Shown += form_Shown;
}
}
form.Invalidate();
}
/// <summary>Specifies whether this object can provide its extender properties to the specified object.</summary>
/// <param name="form">The <see cref="T:System.Object"/> to receive the extender properties.</param>
/// <returns>true if this object can provide extender properties to the specified object; otherwise, false.</returns>
bool IExtenderProvider.CanExtend(object form) => !ReferenceEquals(form, this) && form is Form;
/// <summary>
/// Releases the unmanaged resources used by the <see cref="T:System.ComponentModel.Component"/> and optionally releases the managed resources.
/// </summary>
/// <param name="disposing">true to release both managed and unmanaged resources; false to release only unmanaged resources.</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
foreach (var form in formProps.Keys)
{
if (!form.IsDisposed)
{
UnhookForm(form);
}
}
}
base.Dispose(disposing);
}
private static Rectangle GetNonGlassArea(Form form, GlassFormProperties prop)
{
if (prop == null)
return form.ClientRectangle;
return new Rectangle(form.ClientRectangle.Left + prop.GlassMargins.Left, form.ClientRectangle.Top + prop.GlassMargins.Top,
form.ClientRectangle.Width - prop.GlassMargins.Horizontal, form.ClientRectangle.Height - prop.GlassMargins.Vertical);
}
private void form_MouseDown(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
var prop = GetFormProperties((Form)sender!);
if (prop.GlassMarginMovesForm)
{
prop.FormMoveTracking = true;
prop.FormMoveLastMousePos = ((Control)sender!).PointToScreen(e.Location);
}
}
}
private void form_MouseMove(object? sender, MouseEventArgs e)
{
var form = (Form)sender!;
var prop = GetFormProperties(form);
if (prop.FormMoveTracking && !GetNonGlassArea(form, prop).Contains(e.Location))
{
var screen = form.PointToScreen(e.Location);
var diff = new Point(screen.X - prop.FormMoveLastMousePos.X, screen.Y - prop.FormMoveLastMousePos.Y);
var loc = form.Location;
loc.Offset(diff);
form.Location = loc;
prop.FormMoveLastMousePos = screen;
}
}
private void form_MouseUp(object? sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
GetFormProperties((Form)sender!).FormMoveTracking = false;
}
}
private void form_Paint(object? sender, PaintEventArgs e) => GlassifyForm((Form)sender!, e.Graphics);
private void form_Resize(object? sender, EventArgs e)
{
var form = (Form)sender!;
if (DesktopWindowManager.CompositionEnabled && GetGlassEnabled(form) || form.IsDesignMode())
InvalidateNonGlassClientArea(form);
}
private void form_Shown(object? sender, EventArgs e) => GlassifyForm((Form)sender!);
private GlassFormProperties GetFormProperties(Form form)
{
if (!formProps.TryGetValue(form, out var prop))
formProps.Add(form, prop = new GlassFormProperties());
return prop;
}
private void GlassifyForm(Form form, Graphics? g = null)
{
if (!(DesktopWindowManager.CompositionEnabled && GetGlassEnabled(form)) && !form.IsDesignMode())
return;
g ??= form.CreateGraphics();
if (!formProps.TryGetValue(form, out var prop))
return;
// Paint the glass effect.
if (prop.GlassMargins == new Padding(-1))
g.FillRectangle(Brushes.Black, form.ClientRectangle);
else
{
using var r = new Region(form.ClientRectangle);
r.Exclude(GetNonGlassArea(form, prop));
g.FillRegion(Brushes.Black, r);
}
if (!form.IsDesignMode())
form.ExtendFrameIntoClientArea(prop.GlassMargins);
}
private void InvalidateNonGlassClientArea(Form form)
{
var glassMargin = GetGlassMargins(form);
if (glassMargin != Padding.Empty)
{
var rect = new Rectangle(glassMargin.Left, glassMargin.Top, form.ClientRectangle.Width - glassMargin.Right,
form.ClientRectangle.Height - glassMargin.Bottom);
form.Invalidate(rect, false);
}
}
private void UnhookForm(Form form)
{
form.MouseDown -= form_MouseDown;
form.MouseMove -= form_MouseMove;
form.MouseUp -= form_MouseUp;
form.Shown -= form_Shown;
form.Resize -= form_Resize;
form.Paint -= form_Paint;
}
/// <summary>Properties for each form that is extended.</summary>
private class GlassFormProperties
{
public Point FormMoveLastMousePos = Point.Empty;
public bool FormMoveTracking = false;
public bool GlassEnabled = true;
public bool GlassMarginMovesForm = true;
public Padding GlassMargins = Padding.Empty;
}
}