Работа с элементом PropertyGrid в C#

User Rating: 4 / 5

Доброго времени суток. В этой статье мы посмотрим на то, как работать с элементом PropertyGrid в C# и напишем простое приложение в среде Microsoft Visual Studio, демонстрирующее возможности этого элемента. В конце статьи вы сможете скачать архив с готовым примером проекта для среды Microsoft Visual Studio.

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

На панели элементов PropertyGrid располагается рядом с элементами ProgressBar и RadioButton:

 

Давайте посмотрим на приложение, которое мы собираемся разработать в рамках данной статьи.

Наша тестовая программа будет выглядеть следующим образом - в левой её части будет располагаться элемент PropertyGrid, который будет отображать свойства какого-то абстрактного робота (для отображения свойств в элементе PropertyGrid мы создадим с вами тестовый класс Robot и назначим его свойствам специальные атрибуты, а также создадим экземпляр класса робота, которым и будем управлять посредством элемента PropertyGrid). В правой части формы приложения будет отображаться внутреннее содержимое нашего экземпляра робота. Как только мы будем менять свойства робота через элемент PropertyGrid, мы будем сразу видеть как меняется внутреннее содержимое (т.е. свойства) нашего экземпляра робота:

Также наше приложение будет иметь кнопку "Сбросить все свойства в значения по умолчанию", которая сбросит все внутренние поля и свойства экземпляра робота в их значения по умолчанию, а также вернёт элемент PropertyGrid к его соответствующему состоянию.

Приступим к созданию нового проекта и разработке приложения.

Создание нового проекта в среде Microsoft Visual Studio

Откройте среду разработки Microsoft Visual Studio и создайте новый проект с типом Приложение Windows Forms (.NET Framework), как показано ниже:

В качестве имени нового проекта задайте PropertyGridSample и выберите местоположение, куда будут сохранены файлы нового проекта (здесь я обычно оставляю все настройки по умолчанию и рекомендую сделать то же самое, просто запомните каталог, указанный в поле "Расположение", чтобы затем открывать его в Проводнике).

Переименование главной формы приложения

После создания проекта будет создана главная форма по умолчанию с именем Form1 и соответствующий ей класс Form1.cs.

Выберем в окне "Обозреватель решений" эту форму и в окне "Свойства" изменим название класса для формы на FrmPropertyGridMain.cs.

В диалоговом окне, запрашивающем подтверждение переименования класса формы и связанных с ней файлов, соглашаемся.

В итоге должно получиться следующее - в окне "Обозреватель решений" форма и зависящие от неё файлы будут переименованы:

 

А так должно выглядеть окно "Свойства":

Настройка главной формы приложения и её элементов

Далее нам нужно изменить размеры и свойства главной формы и расположить все нужные нам элементы управления.

Выберите форму, чтобы она появилась в представлении конструктора и установите для формы следующие свойства и их значения:

  • FormBorderStyle - FixedSingle
  • Text - [Allineed.Ru] Пример работы с элементом PropertyGrid
  • Size - 994; 535
  • StartPosition - CenterScreen
  • Name - FrmPropertyGridMain
  • MaximizeBox - False
  • MinimizeBox - False

Теперь расположим на главной форме нужные элементы. Для этого в левой части Microsoft Visual Studio находим "Панель элементов" и последовательно перетаскиваем с неё на нашу главную форму следующие элементы, а также устанавливаем им описанные ниже свойства:

1) Один элемент PropertyGrid

Перетаскиваем на форму элемент PropertyGrid и устанавливаем ему следующие свойства:

  • Location - 12; 12
  • Size - 486; 426
  • Name - PropertyGridRobotProperties

2) Один элемент Button

Перетаскиваем на форму элемент Button и устанавливаем ему следующие свойства:

  • Text - &Сбросить все свойства в значения по умолчанию
  • Location - 207; 444
  • Size - 291; 23
  • Name - ButtonResetAllProperties

3) Один элемент GroupBox

Перетаскиваем на форму элемент GroupBox и устанавливаем ему следующие свойства:

  • Text - Детали робота:
  • Location - 514; 32
  • Size - 452; 406
  • Name - GroupBoxRobotDetails

Теперь внутрь этого элемента GroupBox перетаскиваем элемент TextBox и устанавливаем ему следующие свойства:

  • Scrollbars - Vertical
  • Location - 6; 19
  • Size - 440; 381
  • Multiline - True
  • ReadOnly - True
  • Name - TextBotRobotDetails

Значение свойства Multiline, установленное в True, позволит текстовому полю выводить множество строк, а не только одну.

Значение свойства ReadOnly, установленное в True, делает текстовое поле недоступным для ввода, т.к. мы лишь собираемся отображать в нём текст без возможности изменения.

Теперь запустите приложение с помощью кнопки "Пуск" или нажмите клавишу F5. Вы увидите главную форму, но пока наша форма лишь отображает пустые элементы и ничего не происходит.

В следующих разделах статьи мы напишем код, который будет демонстрировать возможности элемента PropertyGrid.

Создание класса Robot для описания свойств робота

Добавим к нашему проекту новый класс Robot, который будет описывать абстрактного робота.

Кликаем правой кнопкой мыши по проекту PropertyGridSample и в контекстном меню выбираем Добавить → Класс..., как показано ниже:

В открывшемся окне задаём вместо Class1.cs имя нового класса - Robot.cs:

 

Теперь в проект был добавлен новый класс Robot, и вы увидите код этого класса в редакторе. Удалим всё содержимое редактора и заменим на код, представленный ниже:

using System.ComponentModel;

namespace PropertyGridSample {
    /// <summary>
    /// Класс, описывающий некоторого робота
    /// </summary>
    [DefaultProperty("Name")]
    public class Robot {
        const string DEFAULT_NAME = "Робот модели ALLINEED-123";
        const string DEFAULT_DESCRIPTION = "Это тестовый робот, созданный для демонстрации возможностей элемента PropertyGrid";
        const int DEFAULT_NUMBER_OF_ARMS = 2;
        const int DEFAULT_NUMBER_OF_LEGS = 2;

        /// <summary>
        /// Имя робота
        /// </summary>
        private string name = DEFAULT_NAME;

        /// <summary>
        /// Описание робота
        /// </summary>
        private string description = DEFAULT_DESCRIPTION;

        /// <summary>
        /// Количество рук у робота
        /// </summary>
        private int numberOfArms = DEFAULT_NUMBER_OF_ARMS;

        /// <summary>
        /// Количество ног у робота
        /// </summary>
        private int numberOfLegs = DEFAULT_NUMBER_OF_LEGS;

        /// <summary>
        /// Может ли робот ходить
        /// </summary>
        private bool canWalk = true;

        /// <summary>
        /// Может ли робот летать
        /// </summary>
        private bool canFly;

        /// <summary>
        /// Может ли робот плавать
        /// </summary>
        private bool canSwim;

        /// <summary>
        /// Может ли робот бегать
        /// </summary>
        private bool canRun;        

        /// <summary>
        /// Сбрасывает состояние робота и его внутренние свойства в значения по умолчанию
        /// </summary>
        public void ResetState() {
            name = DEFAULT_NAME;
            description = DEFAULT_DESCRIPTION;
            numberOfArms = DEFAULT_NUMBER_OF_ARMS;
            numberOfLegs = DEFAULT_NUMBER_OF_LEGS;
            canFly = canSwim = canRun = false;
            canWalk = true;
        }
  
        [Category("Общие"), Description("Имя робота")]
        [DefaultValue(DEFAULT_NAME)]
        [DisplayName("Имя")]
        [RobotStringConstraint(30)]
        public string Name {
            get {
                return name;
            }
            set {
                name = value;
            }
        }

        [Category("Общие")]
        [Description("Описание робота и его назначения")]
        [DefaultValue(DEFAULT_DESCRIPTION)]
        [DisplayName("Описание")]
        [RobotStringConstraint(100)]
        public string Description {
            get {
                return description;
            }
            set {
                description = value;
            }
        }

        [Category("Свойства"), Description("Количество рук, которые есть у робота (от 2 до 8). Значение по умолчанию: 2")]
        [DisplayName("Количество рук")]
        [DefaultValue(DEFAULT_NUMBER_OF_ARMS)]
        [RobotIntRangeConstraint(DEFAULT_NUMBER_OF_ARMS, 8)]        
        public int NumberOfArms {
            get {
                return numberOfArms;
            }
            set {
                numberOfArms = value;
            }
        }

        [Category("Свойства"), Description("Количество ног, которые есть у робота (от 2 до 6). Значение по умолчанию: 2")]
        [DisplayName("Количество ног")]
        [DefaultValue(DEFAULT_NUMBER_OF_LEGS)]
        [RobotIntRangeConstraint(DEFAULT_NUMBER_OF_LEGS, 6)]
        public int NumberOfLegs {
            get {
                return numberOfLegs;
            }
            set {
                numberOfLegs = value;
            }
        }

        [Category("Поведение"), Description("Указывает, может ли робот ходить. True - если да, в противном случае False")]
        [DisplayName("Может ходить")]
        [DefaultValue(true)]
        public bool CanWalk {
            get {
                return canWalk;
            }
            set {
                canWalk = value;
            }
        }

        [Category("Поведение"), Description("Указывает, может ли робот летать. True - если да, в противном случае False")]
        [DisplayName("Может летать")]
        [DefaultValue(false)]
        public bool CanFly {
            get {
                return canFly;
            }
            set {
                canFly = value;
            }
        }

        [Category("Поведение"), Description("Указывает, может ли робот плавать. True - если да, в противном случае False")]
        [DisplayName("Может плавать")]
        [DefaultValue(false)]
        public bool CanSwim {
            get {
                return canSwim;
            }
            set {
                canSwim = value;
            }
        }

        [Category("Поведение"), Description("Указывает, может ли робот бегать. True - если да, в противном случае False")]
        [DisplayName("Может бегать")]
        [DefaultValue(false)]
        public bool CanRun {
            get {
                return canRun;
            }
            set {
                canRun = value;
            }
        }

        public override string ToString() {
            return "Robot@" + GetHashCode() + " {\r\n" +
                "\tName = " + Name + "\r\n" +
                "\tDescription = " + Description + "\r\n" +
                "\tNumberOfArms = " + NumberOfArms + "\r\n" +
                "\tNumberOfLegs = " + NumberOfLegs + "\r\n" +
                "\tCanWalk = " + CanWalk + "\r\n" +
                "\tCanRun = " + CanRun + "\r\n" +
                "\tCanSwim = " + CanSwim + "\r\n" +
                "\tCanFly = " + CanFly + "\r\n" + 
            "}";
        }
    }
}

Вы можете обратить внимание, что редактор кода подсвечивает красным области и строки кода, где заданы ещё не определённые атрибуты - RobotStringConstraint и RobotIntRangeConstraint. Мы создадим эти два класса чуть позже, а пока разберём класс робота.

В верхней части класса Robot есть 4 константы, которые задают имя робота по умолчанию и его описание, а также количество рук и ног у робота по умолчанию:

        const string DEFAULT_NAME = "Робот модели ALLINEED-123";
        const string DEFAULT_DESCRIPTION = "Это тестовый робот, созданный для демонстрации возможностей элемента PropertyGrid";
        const int DEFAULT_NUMBER_OF_ARMS = 2;
        const int DEFAULT_NUMBER_OF_LEGS = 2;

Эти константы используются для инициализации свойств класса Robot, и с помощью элемента PropertyGrid мы сможем затем менять свойства нашего робота прямо на форме приложения.

После констант идут внутренние приватные поля класса, которые снабжены комментариями прямо в коде, поэтому мы их пропустим.

У класса робота мы задали метод ResetState(), которые будет сбрасывать экземпляр робота в начальное состояние, присваивая всем полям первичные значения. Обратите внимание, что по умолчанию наш робот "умеет ходить", поэтому поле canWalk мы устанавливаем в true:

        /// <summary>
        /// Сбрасывает состояние робота и его внутренние свойства в значения по умолчанию
        /// </summary>
        public void ResetState() {
            name = DEFAULT_NAME;
            description = DEFAULT_DESCRIPTION;
            numberOfArms = DEFAULT_NUMBER_OF_ARMS;
            numberOfLegs = DEFAULT_NUMBER_OF_LEGS;
            canFly = canSwim = canRun = false;
            canWalk = true;
        }

Переопределение метода ToString() позволяет нам получить текстовое представление конкретного экземпляра робота, возвращая строку со значениями внутренних свойств класса:

        public override string ToString() {
            return "Robot@" + GetHashCode() + " {\r\n" +
                "\tName = " + Name + "\r\n" +
                "\tDescription = " + Description + "\r\n" +
                "\tNumberOfArms = " + NumberOfArms + "\r\n" +
                "\tNumberOfLegs = " + NumberOfLegs + "\r\n" +
                "\tCanWalk = " + CanWalk + "\r\n" +
                "\tCanRun = " + CanRun + "\r\n" +
                "\tCanSwim = " + CanSwim + "\r\n" +
                "\tCanFly = " + CanFly + "\r\n" + 
            "}";
        }

Также обратим внимание на то, что открытым свойствам класса робота назначены специальные атрибуты, например:

        [Category("Свойства"), Description("Количество ног, которые есть у робота (от 2 до 6). Значение по умолчанию: 2")]
        [DisplayName("Количество ног")]
        [DefaultValue(DEFAULT_NUMBER_OF_LEGS)]
        [RobotIntRangeConstraint(DEFAULT_NUMBER_OF_LEGS, 6)]

Атрибут Category будет позволять группировать свойства объекта-робота в элементе PropertyGrid. 

Атрибут Description задаёт описание свойства, которое будет выводиться в поле со справкой, когда какое-то свойства выбирается в элементе PropertyGrid.

Атрибут DisplayName задаёт текст, который будет выводиться для свойства в элементе PropertyGrid. Если его не задать, то будет выводиться название поле, как оно задано непосредственно в классе.

Атрибут DefaultValue задаёт значение свойства по умолчанию. Это значение будет влиять на отображение элемента PropertyGrid. Когда мы будем менять свойство в элементе при запуске программы, то в случае, если зададим какое-то значение, отличающееся от назначенного по умолчанию, оно будет подсвечиваться полужирным шрифтом, если же введённое значение совпадает со значением по умолчанию, то шрифт в элементе PropertyGrid будет стандартным для этого значения.

Атрибут RobotIntRangeConstraint - это наш новый тестовый атрибут, класс которого мы создадим чуть ниже по тексту статьи. Он будет отвечать за регулировку допустимых значений "от" и "до" для целочисленных полей класса нашего робота. При попытке ввести в программе значение, выходящее за пределы допустимых, мы будем выводить ошибку. Аналогично атрибут RobotStringConstraintAttribute - это другой наш тестовый атрибут, и он будет отвечать за максимальную длину строки при задании строковых свойств робота - Name (имя робота) и Description (описание робота).

Создание классов для собственных атрибутов

Теперь добавим к проекту ещё два новых класса RobotIntRangeConstraintAttribute и RobotStringConstraintAttribute. Это делается точно так же, как и при создании класса Robot (правой кнопкой мыши по названию проекта и через меню Добавить → Класс...).

Когда классы будут созданы, заменим всё их содержимое на представленный ниже код:

Для класса RobotIntRangeConstraintAttribute:

using System;

namespace PropertyGridSample {
    /// <summary>
    /// Класс для атрибута, который регулирует минимальное и максимальное значения для какого-то целочисленного свойства
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public class RobotIntRangeConstraintAttribute : Attribute {
        private int minValue;
        private int maxValue;

        public RobotIntRangeConstraintAttribute(int minValue, int maxValue) {
            this.minValue = minValue;
            this.maxValue = maxValue;
        }

        public int MinValue { get { return minValue; } set { minValue = value; } }
        public int MaxValue { get { return maxValue; } set { maxValue = value; } }        
    }
}

Для класса RobotStringConstraintAttribute:

using System;

namespace PropertyGridSample {
    /// <summary>
    /// Класс для атрибута, который регулирует максимальную длину какой-то строки
    /// </summary>
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]
    public class RobotStringConstraintAttribute : Attribute {
        private int maxLength;
        
        public RobotStringConstraintAttribute(int maxLength) {
            this.maxLength = maxLength;
        }

        public int MaxLength { get { return maxLength; } set { maxLength = value; } }        
    }
}

Здесь хотелось бы остановиться на строчке с использованием AttributeUsage над именем новых классов:

[AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = false)]

Переданные в конструктор AttributeUsage параметры регулируют, что наши новые атрибуты могут применяться только к свойствам (AttributeTargets.Property), они являются ненаследуемыми (Inherited = false), а также не разрешено задавать несколько атрибутов на одном и том же свойстве (AllowMultiple = false).

Пишем код для главной формы приложения

Теперь, когда у нас готовы все основные классы - для робота и специальных атрибутов, которые мы задали на полях класса робота, запрограммируем поведение главной формы нашего приложения.

Для этого нужно в окне "Обозреватель решений" дважды кликнуть по главной форме, чтобы отобразить представление конструктора формы.

Дважды кликнем на свободной области формы, чтобы открыть код редактора формы.

Создадим в классе нашей формы поле с именем robot для экземпляра робота и создадим новый экземпляр робота:

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace PropertyGridSample {
    public partial class FrmPropertyGridMain : Form {

        private Robot robot = new Robot();

        // ... остальной код формы ...
    }
}

В методе-обработчике загрузки формы напишем следующий код:

        private void FrmPropertyGridMain_Load(object sender, EventArgs e) {
            PropertyGridRobotProperties.SelectedObject = robot;
            UpdateRobotDetails();
        }

Здесь мы устанавливаем элементу PropertyGrid ссылку на объект нашего робота, задавая его свойство SelectedObject. Второй строкой идёт вызов метода UpdateRobotDetails, который будет отображать содержимое полей робота. Добавим этот новый метод внутри класса формы:

        private void UpdateRobotDetails() {
            TextBotRobotDetails.Text = robot.ToString();
        }

Как видим, всё, что делает этот метод - задаёт свойству Text текстового поля результат вызова robot.ToString(), а переопределение метода ToString() мы подготовили ранее в классе робота, и он просто выводит содержимое экземпляра робота.

Вернёмся снова в представление конструктора формы и дважды кликнем по кнопке "Сбросить все свойства в значения по умолчанию". Это создаст метод-обработчик для события клика по кнопке. Разместим в нём следующий код:

        private void ButtonResetAllProperties_Click(object sender, EventArgs e) {
            robot.ResetState();
            UpdateRobotDetails();
            PropertyGridRobotProperties.SelectedObject = robot;
        }

Когда мы будем нажимать кнопку, то вызовем метод ResetState(), сбрасывающий состояние робота в исходное. Далее мы обновляем детали робота в текстовом поле и повторно устанавливаем свойство SelectedObject с целью обновления содержимого элемента PropertyGrid.

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

Перейдем опять в представление конструктора формы и выберем на ней элемент PropertyGrid. Далее в окне "Свойства" нажмём на иконку "молнии", чтобы отобразить доступные события элемента управления. Нам нужно событие PropertyValueChanged - делаем двойной клик левой кнопкой в поле справа от имени события:

 

В результате двойного клика мы попадём опять в код формы и увидим сгенерированный обработчик для события. Напишем в нём следующий код:

private void PropertyGridRobotProperties_PropertyValueChanged(object s, PropertyValueChangedEventArgs e) {
            GridItem changedItem = e.ChangedItem;            

            object value = changedItem.Value;

            AttributeCollection attrCollection = changedItem.PropertyDescriptor.Attributes;
            foreach (Attribute attr in attrCollection) {
                if (attr is RobotIntRangeConstraintAttribute) {
                    RobotIntRangeConstraintAttribute rangeConstraint = attr as RobotIntRangeConstraintAttribute;
                    int intValue = (int)value;
                    if (intValue < rangeConstraint.MinValue || intValue > rangeConstraint.MaxValue) {
                        MessageBox.Show("Значение должно быть в пределах от " + rangeConstraint.MinValue + " до " + rangeConstraint.MaxValue + "!", 
                            "Ошибка", 
                            MessageBoxButtons.OK, 
                            MessageBoxIcon.Error);

                        if ("NumberOfArms".Equals(changedItem.PropertyDescriptor.Name)) {
                            robot.NumberOfArms = (int)e.OldValue;                            
                        } else if ("NumberOfLegs".Equals(changedItem.PropertyDescriptor.Name)) {
                            robot.NumberOfLegs = (int)e.OldValue;
                        }
                        PropertyGridRobotProperties.Focus();
                    }

                } else if (attr is RobotStringConstraintAttribute) {
                    RobotStringConstraintAttribute stringConstraint = attr as RobotStringConstraintAttribute;
                    string strValue = (string)value;
                    if (strValue.Length > stringConstraint.MaxLength) {
                        MessageBox.Show("Максимальная длина строки не должна превышать " + stringConstraint.MaxLength + " символов!",
                            "Ошибка",
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Error);

                        if ("Name".Equals(changedItem.PropertyDescriptor.Name)) {
                            robot.Name = (string)e.OldValue;
                        } else if ("Description".Equals(changedItem.PropertyDescriptor.Name)) {
                            robot.Description = (string)e.OldValue;
                        }
                        PropertyGridRobotProperties.Focus();
                    }
                }
            }

            UpdateRobotDetails();
        }

Давайте разберём код этого метода. 

Обратим внимание, что вторым параметром наш метод принимает объект e класса PropertyValueChangedEventArgs. Этот объект содержит полезные свойства и методы для определения, какое именно свойство в элементе PropertyGrid было изменено.

В первых двух строках мы получаем изменённый элемент и его значение. Поскольку значение может быть любым типом данных, то оно возвращает обобщённый тип object, и мы сначала сохраняем это значение в переменной value:

            GridItem changedItem = e.ChangedItem;            

            object value = changedItem.Value;

Дальше мы получаем с изменённого элемента коллекцию атрибутов и пробегаем по этой коллекции в цикле foreach:

AttributeCollection attrCollection = changedItem.PropertyDescriptor.Attributes;
foreach (Attribute attr in attrCollection) {
    // ... тело цикла foreach ...
}

Внутри цикла нас будут интересовать специальные атрибуты двух новых классов RobotIntRangeConstraintAttribute и RobotStringConstraintAttribute, которые мы создали ранее. Поэтому внутри цикла для очередного атрибута attr, который назначен полям класса Robot мы проверяем, что этот атрибут является экземпляром этих классов:

if (attr is RobotIntRangeConstraintAttribute) {
   // текущий атрибут - это экземпляр класса RobotIntRangeConstraintAttribute,
   // и нужно проверить с его помощью, что в элементе PropertyGrid мы не вышли за пределы допустимых
   // к вводу значений для целочисленного свойства
} else if (attr is RobotStringConstraintAttribute) {
   // текущий атрибут - это экземпляр класса RobotStringConstraintAttribute,
   // и нужно проверить с его помощью, что в элементе PropertyGrid мы не 
   // превысили максимальную длину строкового свойства робота
}

В каждой ветке оператора if мы приводим атрибут к конкретному классу, а затем сверяем допустимость значения, которое мы будем вводить при работе программы в элементе PropertyGrid.

Для целочисленных свойств (количество рук робота, количество ног робота) мы проверим, что ввели значения в допустимом диапазоне. Если это не так, то мы выдадим ошибку.

Например, так будет срабатывать код при попытке введения значения 77 в поле "Количество ног"

Аналогично для строковых свойств (имя робота и его описание) мы проверим, что не ввели строку, превышающую допустимую максимальную длину. Если же длина строки превышена, то также выдадим ошибку при работе программы.

Например, так мы будем выводить ошибку при превышении длины значения поля "Имя":

Обратите внимание, что в случае, когда сработало какое-то ограничение и требуется выводить ошибку, мы возвращаем поля робота в старое значение, которое доступно через свойство e.OldValue.

Мы также вызываем метод Focus() у элемента PropertyGridRobotProperties - это требуется для того, чтобы элемент PropertyGrid перерисовал свою область и обновился.

Теперь наше тестовое приложение полностью готово, и можно потестировать его работу.

Ссылка на архив с готовым примером проекта:

https://allineed.ru/our-products/download/4-allineed-ru-examples/10-csharp-working-with-propertygrid

На этом всё, мы разобрали с вами пример работы с элементом PropertyGrid и написали небольшое тестовое приложение, показывающее основные принципы работы с элементом.

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

Спасибо за внимание и успехов в разработке программ на C#.

Яндекс.Метрика