Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a simple GUI from scratch

On a platform that has no real libraries available, and bare-minimum graphics other than a "display object of dimension(x,y,xx,yy) at coordinates (x,y), I'm trying to create a simple gui.

Can someone point me to a reference where I can understand the logistical principles involved in displaying a set of objects on the screen, and highlighting the selected object, allowing users to navigate between objects and move highlighting to each object. It seems like it should be simple to do, but I would like to understand how people think about this.

How would one create an object with a method like obj.highlight() where obj.highlight would turn off highlighting in all other objects? Would one simply do a for next loop through an array of objects, skipping the current object, turning off highlighting and then set the current object to true? Highlighting would be accomplished by drawing another object on top of the selected object with a transparent center.

This is a single threaded system, (but allows a small amount of async processing).

I'm looking more for conceptual ideas but code in VB that doesn't make use of proprietary graphics calls might be useful.

like image 205
alphablender Avatar asked Jun 28 '11 22:06

alphablender


People also ask

Can I make my own GUI?

You're more than able to create a GUI without using anything more than what is provided to you in the way of HTML/CSS/JavaScript support. Along the same lines, you can build the house you live in without using any of the modern 'conveniences', like bricks, and hewn lumber, and metal.

Can Python create GUI?

Creating a simple graphical user interface (GUI) that works across multiple platforms can be complicated. But it doesn't have to be that way. You can use Python and the PySimpleGUI package to create nice-looking user interfaces that you and your users will enjoy!

Can you make a GUI with C++?

To develop C++ GUI or C++ graphical user interface application, you need an IDE that supports the C++ GUI application. To create the GUI app, you must use Visual Studio 2019 because it is better suited for the C++ GUI application.


2 Answers

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:

enter image description here

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;

                    }

                }

            }

        }
    }
}
like image 82
Marino Šimić Avatar answered Sep 28 '22 06:09

Marino Šimić


Well that is one question that can be answered in a million ways... :)

But as long as you can draw pixels (or anything remotely like it), you can draw a GUI. If you have an object oriented language at hand, I would not choose to highlight and unhighlight the current object. I would give if focus and remove focus from it, and let the object itself decide whether it should be redrawn and how that should be done.

You can automatically unfocus the previous object if all object are placed in some sort of container. When you press a navigational key (like Tab) or press a mouse button, that container can process that message and focus the next object and unfocus the last object.

It requires some programming, but the concept is quite easy. It becomes harder when you want it to perform well, look slick, have all kinds of anumations and transitions... But as I said, the concept is simple and you won't even need OO to do it, although it will probably give you a much cleaner result. I think I can program an ASCII based GUI in DOS Batch on a rainy afternoon if I needed to.

like image 30
GolezTrol Avatar answered Sep 28 '22 08:09

GolezTrol