I've coded a small sample app that does its own control framework by painting over a form using .Net C#. Just something simple with this result:
I've done the IsSelected by recursively disabling all controls and toggling the clicked one.
See the part with window.MouseUp += (sender, arg) =>
.
Selection can go through mouse or the Tab key.
The code approach should be portable to other languages, and online-translatable to VB.Net.
Relevant snippet of code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
namespace CustomGUI
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form window = new Form1();
window.BackColor = Color.Gray;
Graphics api = window.CreateGraphics();
GUIControl form = new GUIControl();
form.Location = new Point(30,30);
form.Size = new Size(200, 300);
GUIControl control1 = new GUIControl();
control1.Location = new Point(0, 0);
control1.Size = new Size(200, 130);
control1.Background = Color.Blue;
GUIControl control11 = new GUIControl();
control11.Location = new Point(140, 30);
control11.Size = new Size(30, 30);
control11.Background = Color.Red;
GUIControl control12 = new GUIControl();
control12.Location = new Point(30, 30);
control12.Size = new Size(30, 30);
control12.Background = Color.Red;
control12.BorderColor = Color.Green;
control12.BorderWidth = 5;
GuiLabel control2 = new GuiLabel();
control2.Location = new Point(10, 200);
control2.Size = new Size(180, 30);
control2.Background = Color.Green;
control2.Text = "Hello World!";
control1.AddChild(control11);
control1.AddChild(control12);
form.AddChild(control1);
form.AddChild(control2);
window.MouseUp += (sender, arg) =>
{
// hit test the control where the mouse has landed
IGUIContainer control = form.HitTest(arg.Location);
if (control != null)
{
// recursive on all controls
foreach (var ct in (new IGUIContainer[] { form }).Traverse(c => c.Controls))
{
//deselecting all others
if (ct != control) ct.IsSelected = false;
}
control.IsSelected = !control.IsSelected;
}
window.Invalidate(); // force paint
};
window.KeyUp += (sender, key) =>
{
if (key.KeyCode == Keys.Tab && key.Modifiers == Keys.None)
{
var selected = (new IGUIContainer[] { form }).Traverse(c => c.Controls).FirstOrDefault(c => c.IsSelected);
IGUIContainer parent;
if (selected == null)
{
parent = form;
}
else
{
parent = selected;
}
IGUIContainer control;
if (parent.Controls.Count > 0)
{
control = parent.Controls[0];
}
else
{
control = GUIControl.Next(parent);
}
if (control == null) control = form;
foreach (var ct in (new IGUIContainer[] { form }).Traverse(c => c.Controls))
{
if (ct != control) ct.IsSelected = false;
}
control.IsSelected = true;
window.Invalidate();
}
};
window.Paint += (sender, args) =>
{
form.Draw(api, new Point(0,0));
};
Application.Run(window);
}
}
}
All the needed classes and interfaces:
IDrawable:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
public interface IDrawable
{
Point Location { get; set; }
Size Size { get; set; }
Rectangle GetRealRect(Point origin);
void Draw(Graphics gfxApi, Point origin);
}
}
IGUIContainer:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
delegate void SelectionChangedHandler(object sender, bool newIsSelected);
interface IGUIContainer : IUIElement
{
IGUIContainer Parent { get; set; }
List<IGUIContainer> Controls { get; }
void AddChild(IGUIContainer child);
bool IsSelected { get; set; }
event SelectionChangedHandler SelectionChanged;
IGUIContainer HitTest(Point mouseCoord);
}
}
UIElement:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Diagnostics;
namespace CustomGUI
{
abstract class UIElement : IUIElement
{
private Point _location;
private Size _size;
private Color _background;
private Color _foreground;
private Color _borderColor;
private int _borderWidth;
public UIElement()
{
_foreground = Color.Black;
_background = Color.White;
_borderColor = Color.Transparent;
}
public Point Location
{
get
{
return _location;
}
set
{
_location = value;
}
}
public Size Size
{
get
{
return _size;
}
set
{
_size = value;
}
}
public virtual void Draw(Graphics drawingApi, Point origin)
{
Rectangle inside = GetRealRect(origin);
Pen borderPen = new Pen(new SolidBrush(_borderColor), _borderWidth);
drawingApi.FillRectangle(new SolidBrush(_background), inside);
drawingApi.DrawRectangle(borderPen, inside);
}
public Rectangle ClientRect
{
get
{
return new Rectangle(_location, _size);
}
}
public Color Background
{
get
{
return _background;
}
set
{
_background = value;
}
}
public Color Foreground
{
get
{
return _foreground;
}
set
{
_foreground = value;
}
}
public Rectangle GetRealRect(Point origin)
{
int left = ClientRect.Left + origin.X;
int top = ClientRect.Top + origin.Y;
int width = ClientRect.Width;
int height = ClientRect.Height;
Debug.WriteLine("GetRealRect " + left + ", " + top + ", " + width + ", " + height);
return new Rectangle(left, top, width, height);
}
public int BorderWidth
{
get
{
return _borderWidth;
}
set
{
_borderWidth = value;
}
}
public Color BorderColor
{
get
{
return _borderColor;
}
set
{
_borderColor = value;
}
}
}
}
GUIControl:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
class GUIControl : UIElement, IGUIContainer
{
private IGUIContainer _parent;
private List<IGUIContainer> _controls = new List<IGUIContainer>();
private bool _isSelected;
public List<IGUIContainer> Controls
{
get
{
return _controls;
}
}
public override void Draw(Graphics api, Point origin)
{
Point original = origin;
base.Draw(api, origin);
origin.Offset(this.Location);
foreach (var ctrl in Controls)
{
ctrl.Draw(api, origin);
}
if (IsSelected)
{
Pen selection = new Pen(Color.Yellow, 3);
selection.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
api.DrawRectangle(selection, GetRealRect(original));
}
}
public IGUIContainer HitTest(Point coord)
{
Point newOrigin = coord;
newOrigin.Offset(-this.Location.X, -this.Location.Y);
foreach (var ctrl in Controls)
{
IGUIContainer hit = ctrl.HitTest(newOrigin);
if (hit != null)
{
return hit;
}
}
return ClientRect.Contains(coord) ? this : null;
}
public bool IsSelected
{
get
{
return _isSelected;
}
set
{
_isSelected = value;
if (SelectionChanged != null)
{
SelectionChanged(this, _isSelected);
}
}
}
public event SelectionChangedHandler SelectionChanged;
public void AddChild(IGUIContainer child)
{
// if you need to implement event propagation this is the place to attach them to children
child.Parent = this;
Controls.Add(child);
}
public IGUIContainer Parent
{
get
{
return _parent;
}
set
{
_parent = value;
}
}
public static IGUIContainer Next(IGUIContainer self)
{
if (self.Parent != null &&
self.Parent.Controls.Count - 1 > self.Parent.Controls.IndexOf(self))
{
return self.Parent.Controls[self.Parent.Controls.IndexOf(self) + 1];
}
else if (self.Parent != null)
{
return Next(self.Parent);
}
else
{
return null;
}
}
}
}
GUILabel:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
namespace CustomGUI
{
class GuiLabel : GUIControl
{
public string Text { get; set; }
public Font Font { get; set; }
public GuiLabel()
{
Font = new Font(new FontFamily("Tahoma"), 12, FontStyle.Regular);
}
public override void Draw(System.Drawing.Graphics api, System.Drawing.Point origin)
{
base.Draw(api, origin);
Rectangle controlRect = GetRealRect(origin);
SizeF size = api.MeasureString(Text, Font);
Point textPosition = new Point(controlRect.Location.X + (int)(controlRect.Width - size.Width) / 2,
controlRect.Location.Y + (int)(controlRect.Height - size.Height) / 2);
api.DrawString(Text, Font, new SolidBrush(Foreground), textPosition);
}
}
}
Extension (for Traverse method to flatten recursion):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CustomGUI
{
static class Extensions
{
public static IEnumerable<T> Traverse<T>(this IEnumerable<T> source, Func<T, IEnumerable<T>> fnRecurse)
{
foreach (T item in source)
{
yield return item;
IEnumerable<T> seqRecurse = fnRecurse(item);
if (seqRecurse != null)
{
foreach (T itemRecurse in Traverse(seqRecurse, fnRecurse))
{
yield return itemRecurse;
}
}
}
}
}
}