Создать простой графический интерфейс с нуля - PullRequest
6 голосов
/ 29 июня 2011

На платформе, у которой нет доступных реальных библиотек и графики с минимальным набором символов, кроме «экранного объекта измерения (x, y, xx, yy) в координатах (x, y), я пытаюсь создать простой графический интерфейс.

Может ли кто-нибудь указать мне ссылку, где я могу понять логистические принципы, связанные с отображением набора объектов на экране и выделением выделенного объекта, позволяя пользователям перемещаться между объектами и перемещать выделение к каждому объекту. Кажется, это должно быть просто, но я хотел бы понять, как люди думают об этом.

Как создать объект с помощью метода, такого как obj.highlight (), где obj.highlight отключит подсветку во всех других объектах? Можно ли просто выполнить цикл for для следующего массива объектов, пропустить текущий объект, отключить подсветку и затем установить для текущего объекта значение true? Выделение будет выполнено путем рисования другого объекта поверх выделенного объекта с прозрачным центром.

Это однопоточная система (но допускает небольшое количество асинхронной обработки).

Я больше ищу концептуальные идеи, но код на VB, который не использует проприетарные графические вызовы, может быть полезен.

Ответы [ 2 ]

3 голосов
/ 29 июня 2011

Я написал небольшой пример приложения, которое создает собственную платформу управления, рисуя форму, используя .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;

                    }

                }

            }

        }
    }
}
2 голосов
/ 29 июня 2011

Что ж, это один вопрос, на который можно ответить миллионами способов ...:)

Но пока вы можете рисовать пиксели (или что-нибудь подобное удаленно), вы можете рисовать графический интерфейс.Если у вас под рукой объектно-ориентированный язык, я бы не стал выделять и отменять выделение текущего объекта.Я бы дал фокус и убрал бы фокус с него, и позволил бы самому объекту решить, следует ли его перерисовывать и как это сделать.

Вы можете автоматически расфокусировать предыдущий объект, если все объекты помещены в какой-то контейнер.Когда вы нажимаете навигационную клавишу (например, Tab) или кнопку мыши, этот контейнер может обработать это сообщение и сфокусировать следующий объект и расфокусировать последний объект.

Это требует некоторого программирования, но концепция довольно проста,Становится все труднее, когда вы хотите, чтобы он работал хорошо, выглядел гладко, имел всевозможные виды анимации и переходов ... Но, как я уже сказал, концепция проста, и вам даже не понадобится ОО, чтобы сделать это, хотя, вероятно, она дастВы намного чище результат.Я думаю, что могу программировать графический интерфейс на основе ASCII в DOS Batch дождливым днем, если мне нужно.

...