从头开始创建一个简单的GUI

时间:2011-06-28 22:08:28

标签: algorithm conceptual-model

在没有真正可用库的平台上,以及除了“坐标(x,y)处的维度(x,y,xx,yy)的显示对象”之外的最小图形,我正在尝试创建一个简单的gui。

有人可以指点我参考,我可以理解在屏幕上显示一组对象所涉及的逻辑原理,并突出显示所选对象,允许用户在对象之间导航并将突出显示移动到每个对象。这似乎应该很简单,但我想了解人们如何看待这一点。

如何使用像obj.highlight()这样的方法创建一个对象,其中obj.highlight会关闭所有其他对象中的突出显示?是否只需通过一个对象数组进行下一个循环,跳过当前对象,关闭突出显示然后将当前对象设置为true?通过在透明中心的所选对象上绘制另一个对象来完成高亮显示。

这是一个单线程系统,(但允许少量的异步处理)。

我正在寻找更多概念性的想法,但VB中没有使用专有图形调用的代码可能会有用。

2 个答案:

答案 0 :(得分:3)

我编写了一个小型示例应用程序,通过使用.Net C#绘制表单来执行自己的控件框架。这个结果只是简单的事情:

enter image description here

我通过递归禁用所有控件并切换单击的控件来完成IsSelected。 请参阅window.MouseUp += (sender, arg) =>部分。

选择可以通过鼠标或Tab键。

代码方法应该可以移植到其他语言,并且可以在线翻译到VB.Net。

相关的代码片段:

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);
        }
    }
}

所有需要的类和接口:

<强> 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);
        }
    }
}

扩展(用于平移递归的Traverse方法):

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;

                    }

                }

            }

        }
    }
}

答案 1 :(得分:2)

这是一个可以用百万种方式回答的问题......:)

但只要您可以绘制像素(或任何远程像素),您就可以绘制GUI。如果您手头有面向对象的语言,我不会选择突出显示当前对象并取消突出显示当前对象。我会给焦点并从中移除焦点,然后让对象自己决定是否应该重绘它以及应该如何完成。

如果所有对象都放在某种容器中,您可以自动取消对前一个对象的聚焦。当您按导航键(如Tab键)或按下鼠标按钮时,该容器可以处理该消息并聚焦下一个对象并使最后一个对象不聚焦。

它需要一些编程,但这个概念很简单。当你希望它表现良好,看起来光滑,有各种各样的动画和过渡时变得更难......但正如我所说,这个概念很简单,你甚至不需要OO去做,尽管它可能会给你你的结果更清晰。我想我可以在下雨的下午在DOS Batch中编写一个基于ASCII的GUI,如果需要的话。