Created
October 3, 2024 23:41
-
-
Save MovGP0/e9608fa3990b779593846ec0e8ab7a24 to your computer and use it in GitHub Desktop.
DSL for aligning WinForms controls
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public enum VisibilityBehavior | |
{ | |
Collapse, | |
MaintainSpacing | |
} | |
private enum Direction | |
{ | |
Right, | |
Below, | |
Left, | |
Above | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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