Таблицы и диалоги

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

Для вывода простых информационных сообщений и сообщений о ошибках используется MessageDlg. Достаточно вызвать статический метод MessageDlg.Show с требуемым сообщением.

Достаточно создать наследника от SimpleDlg и перекрыть два метода:

  • DoInit() - метод вызывается при инициализации диалога, на нём необходимо инициализировать содержимое диалога
  • DoCommit() - метод вызывается, если пользователь выбрал кнопку ОК на диалоге. Необходимо проверить введённые данные, и в качестве результата вернуть - являются ли данные корректными. В случае если данные некорректны диалог не будет закрыт.
Метод DoGetModifiedWarning класса SimpleDlg может быть использован, если необходимо предупредить пользователя о том, что он нажимает кнопку Отмена, а на диалог были внесены изменения. Для этого метод должен вернуть есть ли на диалоге какие-либо изменения.

Достаточно часто бывает необходимо представить данные в виде редактируемой таблицы. Программный комплекс Топоматик Робур предоставляет для этого элемент управления EditTableFrame. Этот элемент предназначен для отображения таблицы со всеми необходимыми кнопками управления.

Для отображения диалогов, состоящих из одной таблицы можно использовать класс EditTableDlg. Для этого необходимо вызвать статический метод класса EditTableDlg.Execute

Для того чтобы заполнить таблицу данными необходимо присвоить свойство Wrapper у экземпляра EditTableFrame. Поведение и вид элемента управления элемент управления EditTableFrame зависят от реализации класса, назначенного свойству Wrapper и поддержки следующих интерфейсов:

  • Интерфейс IEnumerable, таблица доступна только для редактирования элементов, удаление и добавление строк недоступно
  • Интерфейсы IList и IActivator, доступно добавление и удаление строк
  • Интерфейс ISupportClipboard, поддерживается возможность копирования и вставки строк через буфер обмена
  • Интерфейс ISupportInterpolation, поддерживается возможность интерполирования между строками таблицы
Для более быстрого создания таблиц можно воспользоваться сервисным классом SimpleChangeTrackingWrapper. Этот класс реализует интерфейсы IList и IChangeTracking. При его использовании необходимо на конструкторе создать объекты и заполнить ими свойство Items, а на перекрытой функции AcceptChanges заполнить данные модели по объектам этого свойства. Также необходимо поддержать интерфейс IActivator

Каждый элемент перечисления описывает строку в таблице. Заголовок таблицы строится на основании списка свойств этого элемента, используя атрибуты [DisplayName] и [Category]. Например, если элемент перечисления реализован следующим образом:

...
   class Item
   {
      [DisplayName("Пикет"), Category("Узлы")]
      public string Pk
      {
         get;set;
      }
 
      [DisplayName("Плюс"), Category("Узлы")]      
      public string Plus
      {
         get;set;
      }
 
      [DisplayName("Отметка, м"), Category("Узлы"), DefaultDouble]      
      public double Elevation
      {
         get;set;
      }
   }
...

Наша таблица будет выглядеть примерно так:

Заголовок таблицы можно сделать многоуровневым, на нижнем уровне всегда отображается значение атрибута [DisplayName] а верхние настраиваются с помощью атрибута [Category] с разделением уровней символом «|». Например [Category(«Уровень3|Уровень1»)]

Отображение свойств в таблице настраивается с помощью разнообразных атрибутов, наследованных от PropertyTypeConverter, PropertyEditor и PropertyProvider. Более подробно они описаны в других разделах Руководства. В частности, для отображения перечисления в виде выпадающего списка, необходимо написать наследника от BaseEnumConverter. В котором установить соответствие между значениями перечисления и строками используя свойство Dictionary. А затем назначить его на нужное свойство используя атрибут [PropertyTypeConverter].

Создайте и настройте новый модуль для подключения к программному комплексу Топоматик Робур.

С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:

Не забудьте поставить флаг «Копировать локально» в «False». Кроме того, необходимо добавить нужные пространства имен в секцию using. Visual Studio позволяет легко сделать это, нажав правой клавишей на имени класса и выбрав добавление директивы using.

Создайте новый класс, назовите его TableWrapper.cs и разместите в нём реализацию таблицы.

    //Перечисление из нескольких значений
    enum DataEnum
    {
        Value1,
        Value2,
        Value3
    }
 
    //Структура данных, которая будет хранится в нашей модели
    struct DataValue
    {
        public double Length;
 
        public string TextValue;
 
        public DataEnum Value;
    }
 
    //Конвертер перечисления DataEnum в строковое представление и обратно
    class DataEnumConverter : BaseEnumConverter
    {
        public DataEnumConverter()
        {
            Dictionary[DataEnum.Value1] = "Значение1";
            Dictionary[DataEnum.Value2] = "Значение2";
            Dictionary[DataEnum.Value3] = "Значение3";
        }
    }
 
    //Класс реализующий обработку содержимого таблицы
    //Для простоты реализации, наследуем его от SimpleChangeTrackingWrapper
    class TableWrapper : SimpleChangeTrackingWrapper, IActivator
    {
        //класс реализующий представление строки таблицы
        public class TableRow
        {
            //Указатель на нашу таблицу, необходим, чтобы при изменении свойств выставить св-во IsChanged 
            private SimpleChangeTrackingWrapper m_Wrapper;
 
            private double m_Length;
 
            private string m_TextValue;
 
            private DataEnum m_Value;
 
            public TableRow(SimpleChangeTrackingWrapper wrapper, DataValue value)
            {
                m_Wrapper = wrapper;
                m_Length = value.Length;
                m_TextValue = value.TextValue;
                m_Value = value.Value;
            }
 
            //Свойство длина
            //Обратите внимание, что оно декорировано атрибутом Length - который позволяет выводить содержимое с точностью длин
            [DisplayName("Длина, м"), Category("Верхний заголовок|Нижний заголовок"), Length]
            public double Length
            {
                get
                {
                    return m_Length;
                }
                set
                {
                    m_Length = value;
                    //Предупреждаем таблицу, о наличии изменений
                    m_Wrapper.IsChanged = true;
                }
            }
 
            //Стоковое свойство
            [DisplayName("Строка"), Category("Верхний заголовок|Нижний заголовок")]
            public string TextValue
            {
                get
                {
                    return m_TextValue;
                }
                set
                {
                    m_TextValue = value;
                    //Предупреждаем таблицу, о наличии изменений
                    m_Wrapper.IsChanged = true;
                }
            }
 
            //Свойство перечисление
            [DisplayName("Перечисление"), Category("Верхний заголовок|Другой заголовок")]
            //Наш конвертер назначен с помощью атрибута PropertyTypeonverterAttribute
            [PropertyTypeConverter(typeof(DataEnumConverter))]
            public DataEnum Value
            {
                get
                {
                    return m_Value;
                }
                set
                {
                    m_Value = value;
                    //Предупреждаем таблицу, о наличии изменений
                    m_Wrapper.IsChanged = true;
                }
            }
        }
 
        //Сохраняем ссылку на данные модели
        private List<DataValue> m_Data;
 
        //При создании таблицы, заполняем её содержимое данными из модели
        public TableWrapper(List<DataValue> data)
        {
            m_Data = data;
            this.Items = new List<object>(m_Data.Count);
            for (int i = 0; i < m_Data.Count; i++)
            {
                this.Items.Add(new TableRow(this, m_Data[i]));
            }
        }
 
        //Считаем что наша таблица всегда доступна для редактирования
        public override bool IsReadOnly
        {
            get
            {
                return false;
            }
        }
 
        //При вызове метода заполняем модель по даннм нашей таблицы
        public override void AcceptChanges()
        {
            m_Data.Clear();
            for (int i = 0; i < this.Items.Count; i++)
            {
                var row = (TableRow)this.Items[i];
                m_Data.Add(new DataValue() { Length = row.Length, TextValue = row.TextValue, Value = row.Value });
            }
        }
 
        //Реализация интерфеса IActivator
 
        //Всегда можем создавать экземпляр строки таблицы
        public bool CanCreateInstance
        {
            get
            {
                return true;
            }
        }
 
        //Создаем новый экземпляр строки таблицы
        public object CreateInstance()
        {
            return new TableRow(this, new DataValue());
        }
    }
Обратите внимание, что свойство Length в строке таблицы декорировано атрибутом Length. Большая часть атрибутов реализованных в сборках программного комплекса, реализованы в пространствах имен Design

Теперь напишем реализацию диалога. Для этого добавьте в проект новую форму и назовите её TestSimpleDialog.

Откройте окно редактора кода, и сделайте её наследником от Topomatic.Controls.Dialogs.SimpleDlg. Затем добавьте на форму обычную панель и откройте код дизайнера в файле TestSimpleDialog.Designer.cs

Замените класс панели panel1 на EditTableFrame вручную и сохраните файл. Теперь вернитесь в окно дизайнера и разместите EditTableFrame на диалоге. После чего оформите код диалога следующим образом:

    //Наследуем наш диалог от Topomatic.Controls.Dialogs.SimpleDlg
    partial class TestSimpleDialog : Topomatic.Controls.Dialogs.SimpleDlg
    {
        public TestSimpleDialog()
        {
            InitializeComponent();
        }
 
        protected override void DoInit()
        {
            base.DoInit();
            //на инициализации нам не нужно ничего делать
        }
 
        protected override bool DoCommit()
        {
            var wrapper = (TableWrapper)panel1.Wrapper;
            //при нажатии ОК проверяем, были ли изменения в таблице
            if (wrapper.IsChanged)
                //Если были, то заполняем данные модели
                wrapper.AcceptChanges();
            return base.DoCommit();
        }
 
        //Для удобства использования реализуем статический метод, который принимает данные и показывает диалог
        public static bool Execute(List<DataValue> values)
        {
            using (var dlg = new TestSimpleDialog())
            {
                //из-за особенностей реализации необходимо инициализировать фрэйм до вызова ShowDialog()
                dlg.panel1.Wrapper = new TableWrapper(values);
                return dlg.ShowDialog() == DialogResult.OK;
            }
        }
    }

В теле программного модуля объявите команду «test_dlg_and_table», и декорируйте её атрибутом «cmd».

    partial class Module : Topomatic.ApplicationPlatform.Plugins.PluginInitializator
    {
        //Используем статический список для хранения наших данных в качестве модели
        private static List<DataValue> m_Values = new List<DataValue>();
 
        [cmd("test_dlg_and_table")]
        public void TestDlgAndTable()
        {
            //Просто вызываем метод у нашего диалога
            TestSimpleDialog.Execute(m_Values);
        }
    }

Задача команды «test_dlg_and_table» показать диалог редактирования данных, которые хранятся в статическом списке m_Values и дать возможность их редактировать в виде таблицы.

Теперь необходимо сформировать наш файл .plugin. Заполните его следующим образом.

{
  "assemblies": {
    "tutorial5": {
      "assembly": "tutorial5.dll, tutorial5.ModulePluginHost"
    }
  },
  "actions": {
    "id_test_dlg_and_table": {
      "cmd": "test_dlg_and_table",
      "title": "Диалог и таблица"
    }
  },
  "menubars": {
    "rbproj": {
      "items": [
        {
          "id": "test_menu",
          "title": "Примеры"
        }
      ]
    },
    "rbproj.test_menu": {
      "items": [
        "id_test_dlg_and_table"
      ]
    }
  }
}

Здесь мы описываем actions для нашей команды, и добавляем пункт в меню. Результатом запуска проекта будет появления в главном меню пункта «Примеры», с подпунктом «Диалог и таблица», который будет работать в соответствии с описанным выше алгоритмом. После запуска и выполнения мы увидим следующий результат:

Исходный код примера, вы можете скачать используя эту ссылку

Архив с кодом примера

developers/tutorial/dlgandpropertygrid.txt · Последние изменения: 2021/07/22 14:29 (внешнее изменение)