Skip to content

Instantly share code, notes, and snippets.

@MovGP0
Created October 3, 2024 23:41
Show Gist options
  • Save MovGP0/e9608fa3990b779593846ec0e8ab7a24 to your computer and use it in GitHub Desktop.
Save MovGP0/e9608fa3990b779593846ec0e8ab7a24 to your computer and use it in GitHub Desktop.
DSL for aligning WinForms controls
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Windows.Forms;
using System.Reactive.Disposables;
public static class ControlExtensions
{
public static IDisposable IsRightOf(this Control control, Control target, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Right);
target.VisibleChanged += handler;
UpdatePosition(control, target, spacing, behavior, Direction.Right);
return Disposable.Create(() => target.VisibleChanged -= handler);
}
public static IDisposable IsBelow(this Control control, Control target, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Below);
target.VisibleChanged += handler;
UpdatePosition(control, target, spacing, behavior, Direction.Below);
return Disposable.Create(() => target.VisibleChanged -= handler);
}
public static IDisposable IsLeftOf(this Control control, Control target, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Left);
target.VisibleChanged += handler;
UpdatePosition(control, target, spacing, behavior, Direction.Left);
return Disposable.Create(() => target.VisibleChanged -= handler);
}
public static IDisposable IsAbove(this Control control, Control target, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Above);
target.VisibleChanged += handler;
UpdatePosition(control, target, spacing, behavior, Direction.Above);
return Disposable.Create(() => target.VisibleChanged -= handler);
}
public static IDisposable IsRightOf(this Control control, Control[] targets, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
var disposables = new CompositeDisposable();
foreach (var target in targets)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Right);
target.VisibleChanged += handler;
disposables.Add(Disposable.Create(() => target.VisibleChanged -= handler));
}
UpdatePosition(control, targets, spacing, behavior, Direction.Right);
return disposables;
}
public static IDisposable IsBelow(this Control control, Control[] targets, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
var disposables = new CompositeDisposable();
foreach (var target in targets)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Below);
target.VisibleChanged += handler;
disposables.Add(Disposable.Create(() => target.VisibleChanged -= handler));
}
UpdatePosition(control, targets, spacing, behavior, Direction.Below);
return disposables;
}
public static IDisposable IsLeftOf(this Control control, Control[] targets, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
var disposables = new CompositeDisposable();
foreach (var target in targets)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Left);
target.VisibleChanged += handler;
disposables.Add(Disposable.Create(() => target.VisibleChanged -= handler));
}
UpdatePosition(control, targets, spacing, behavior, Direction.Left);
return disposables;
}
public static IDisposable IsAbove(this Control control, Control[] targets, int spacing = 0, VisibilityBehavior behavior = VisibilityBehavior.MaintainSpacing)
{
var disposables = new CompositeDisposable();
foreach (var target in targets)
{
EventHandler handler = (sender, args) => UpdatePosition(control, target, spacing, behavior, Direction.Above);
target.VisibleChanged += handler;
disposables.Add(Disposable.Create(() => target.VisibleChanged -= handler));
}
UpdatePosition(control, targets, spacing, behavior, Direction.Above);
return disposables;
}
private static void UpdatePosition(Control control, Control target, int spacing, VisibilityBehavior behavior, Direction direction)
{
if (!target.Visible && behavior == VisibilityBehavior.Collapse)
{
return;
}
switch (direction)
{
case Direction.Right:
control.Left = target.Right + spacing + target.Margin.Right + control.Margin.Left;
break;
case Direction.Below:
control.Top = target.Bottom + spacing + target.Margin.Bottom + control.Margin.Top;
break;
case Direction.Left:
control.Left = target.Left - control.Width - spacing - target.Margin.Left - control.Margin.Right;
break;
case Direction.Above:
control.Top = target.Top - control.Height - spacing - target.Margin.Top - control.Margin.Bottom;
break;
}
}
private static void UpdatePosition(Control control, Control[] targets, int spacing, VisibilityBehavior behavior, Direction direction)
{
foreach (var target in targets)
{
if (target.Visible || behavior != VisibilityBehavior.Collapse)
{
UpdatePosition(control, target, spacing, behavior, direction);
break;
}
}
}
public static void AlignTopWith(this Control control, Control target)
{
control.Top = target.Top;
}
public static void AlignLeftWith(this Control control, Control target)
{
control.Left = target.Left;
}
}
public enum VisibilityBehavior
{
Collapse,
MaintainSpacing
}
private enum Direction
{
Right,
Below,
Left,
Above
}
public class Example
{
public void SetupLayout()
{
Panel somePanel = new Panel();
SomeUserControl controlA = new SomeUserControl();
SomeUserControl controlB = new SomeUserControl();
SomeUserControl controlC = new SomeUserControl();
somePanel.Controls.Add(controlA);
somePanel.Controls.Add(controlB);
somePanel.Controls.Add(controlC);
using (controlB.IsRightOf(controlA, 10, VisibilityBehavior.Collapse))
using (controlC.IsBelow(controlA, 5))
{
// Layout setup
}
}
}
[TestClass]
public class ControlExtensionsTests
{
[TestMethod]
public void IsRightOf_SetsCorrectPosition()
{
var controlA = new Control { Width = 100, Height = 50, Left = 10, Top = 10 };
var controlB = new Control { Width = 50, Height = 50 };
using (controlB.IsRightOf(controlA, 10))
{
controlA.Visible = true;
controlB.Left.ShouldBe(120);
}
}
[TestMethod]
public void IsBelow_SetsCorrectPosition()
{
var controlA = new Control { Width = 100, Height = 50, Left = 10, Top = 10 };
var controlB = new Control { Width = 50, Height = 50 };
using (controlB.IsBelow(controlA, 10))
{
controlA.Visible = true;
controlB.Top.ShouldBe(70);
}
}
[TestMethod]
public void IsLeftOf_SetsCorrectPosition()
{
var controlA = new Control { Width = 100, Height = 50, Left = 200, Top = 10 };
var controlB = new Control { Width = 50, Height = 50 };
using (controlB.IsLeftOf(controlA, 10))
{
controlA.Visible = true;
controlB.Left.ShouldBe(140);
}
}
[TestMethod]
public void IsAbove_SetsCorrectPosition()
{
var controlA = new Control { Width = 100, Height = 50, Left = 10, Top = 100 };
var controlB = new Control { Width = 50, Height = 50 };
using (controlB.IsAbove(controlA, 10))
{
controlA.Visible = true;
controlB.Top.ShouldBe(40);
}
}
[TestMethod]
public void IsRightOf_WithMultipleTargets_SetsCorrectPosition()
{
var controlA = new Control { Width = 100, Height = 50, Left = 10, Top = 10 };
var controlB = new Control { Width = 50, Height = 50 };
var controlC = new Control { Width = 50, Height = 50, Visible = false };
using (controlB.IsRightOf(new[] { controlA, controlC }, 10, VisibilityBehavior.Collapse))
{
controlA.Visible = true;
controlB.Left.ShouldBe(120);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment