C#: Собственный контрол. Часть 1 из 2.

Сегодня мы немного поговорим о том как создать собственный элемент формы. Базовый набор VStudio достаточно полон, однако красивый интерфейс (GUI) с данным набором компонентов создать врятли получится. Говорить мы будем о создании элемента формы с нуля, а в качестве непосредственного контрола возьмем элемент ListBox (чтобы было на что ориентироваться и описывать логику)…

В первой части мы больше поговорим не о программинге, а о общей технологии создания своего пользовательского элемента управления.
Сразу стоит отметить, что нужно иметь достаточно хорошее представление о языке C# в целом, чтобы писать свой GUI. Ну или по крайней мере понимать принципы ООП. Приступим…

Нужно понимать что каждый элемент управления это класс, а значит это набор методов и свойств. Если мы говорим о таком элементе управления как ListBox (список элементов), то его базовый набор необходимых элементов можно описать так:
1) Непосредственно список элементов;
2) Выбранный элемент;
Маловато, не правда ли? Однако это база. Имея список и возможность помнить выбранный элемент из списка мы получаем, то что нам нужно. Будем отталкиваться что возможностей стандартного элемента нам не достаточно (например там нельзя назначать картинку элементу списка), поэтому дополним следующий набор свойств (забегая вперед отметим, что если мы создаем новый элемент с нуля, то он будет является наследником класса UserControl, что значит заведомую реализацию всех стандартных свойств (Enabled, ForeColor и т.д.)):
3) Цвет текста обычного и выбранного элемента;
4) Цвет фона обычного и выбранного элемента;

По поводу изображения нужно сразу определиться с тем что мы хотим видеть в конечном итоге. Хотим ли мы видеть одну картинку рядом с каждым элементом, хотим ли мы что бы эта картинка менялась на другую при выборе элемента, или же мы хотим видеть разные картинки рядом с каждым элементом списка. В зависимости от этого стоит продумать где эти картинки хранить. В первом и втором случае, изображения логичней хранить как свойства нашего контрола, в третем же случая, логичней хранить для каждого объекта в списке. Остановимся на варианте №3.

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

Ну пожалуй для начала хватит. Общее представление о классе мы имеем и логику его работы примерно представляем. Отдельно хочется остановится лишь на параметре «Список элементов». Что есть список элементов в обычном ListBox — это набор объектов (List<object>). Что мы хотим видеть в нашем списке элементов? Тоже самое + возможность задавать картинку. Отлично, с этим определились, идем дальше.

Примечание: В очередной раз сделаю оговорку, что в данном примере мы рассматриваем создание элемента с нуля. Класс ListBox всего лишь ориентир на то, что мы хотим получить. Это означает что будет код, который в принципе можно было не писать, использовав наследование от нужного класса.

Опишем скелет нашего класса контрола:

public class MyListBox: UserControl
{
   protected override void Dispose(bool disposing)
   {
     base.Dispose(disposing);
   }

   //Зачем это нужно опишем во второй части
   private int _SelectedIndex; 
   public int SelectedIndex { get; set; }    
   //Далее все понятно
   public Color ItemBorderColor { get; set; }
   public Color SelectedItemColor { get; set; }
   public Color ItemBackgroundColor { get; set; }
   public Color SelectedItemBackgroundColor { get; set; }
   public int ItemBorderWeight { get; set; }

   public ObjectCollection Items { get; private set; }
   public object SelectedItem { 
     get { if (this.SelectedIndex < 0) return null;
     else return this.Items[this.SelectedIndex]; }
   }
}

Шаблон класса есть. Шаблон интуитивно понятен, кроме класса ObjectCollection, которого у нас нет, и который должен отвечать непосредсвено за элементы списка.
Данный класс в себе должен хранить список объектов с изображениями к ним, а так как это public элемент нашего класса GUI, то он должен быть наследником интерфейсов ICollection и IEnumerator. Далее приведу полный листинг данного класса.

public class ObjectCollection : ICollection, IEnumerator 
        {
            
            private struct ItemInCollection
            {
                public object Obj;
                public Image Img;
                
                public ItemInCollection(object Obj)
                {
                    this.Obj = Obj;
                    this.Img = null;
                }
                public ItemInCollection(object Obj, Image Img)
                {
                    this.Obj = Obj;
                    this.Img = Img;
                }
                
                public override string ToString()
                {
                    return this.Obj.ToString();
                }

            }

            private int Position = -1;
            
            public int Count { get { return this._Items.Count; } }
            
            private Object _SyncRoot = null;
            private bool _IsSynchronized = true;
            public Object SyncRoot { get { return this._SyncRoot; } }
            public bool IsSynchronized { get { return this._IsSynchronized; } }

            public object this[int Idx] { get { if (Idx >= this.Count) return null; return this._Items[Idx].Obj; } }
            public Image Img(int Idx) { if (Idx >= this.Count) return null; return this._Items[Idx].Img; }

            private List<ItemInCollection> _Items = new List<ItemInCollection>();


            public void Add(object Obj) { this._Items.Add(new ItemInCollection(Obj)); }
            public void Add(object Obj, Image Img) { this._Items.Add(new ItemInCollection(Obj, Img)); }
            
            public void Clear() { this._Items.Clear(); }

            public void CopyTo(Array A, int Idx)
            {
                A = new object[this.Count - Idx];
                int ii = 0;
                for (int i = Idx; i < this.Count; ++i)
                {
                    A.SetValue(this[i], ii++);
                }
            }
            public bool MoveNext()
            {
                this.Position++;
                return (this.Position < this.Count);
            }
            public void Reset()
            {
                this.Position = -1;
            }
            object IEnumerator.Current
            {
                get
                {
                    return this.Current;
                }
            }
            public object Current
            {
                get
                {
                    try
                    {
                        return this[this.Position];
                    }
                    catch (IndexOutOfRangeException)
                    {
                        throw new InvalidOperationException();
                    }
                }
            }
            IEnumerator IEnumerable.GetEnumerator()
            {
                return (IEnumerator)this;
            }

        }

По большей части код не сложный, так же интуитивно понятен. Но разберем мы его во второй части данной темы, которую полностью уделим программной части нашей задачи.

Запись опубликована в рубрике C# с метками , , , , , . Добавьте в закладки постоянную ссылку.