====== Таблицы и диалоги ====== Для быстрого и удобного оформления диалогов программный комплекс [[http://www.topomatic.ru|Топоматик Робур]] предлагает несколько разных классов, в том числе [[developers:references:topomatic.controls.dialogs.simpledlg|SimpleDlg]] - это стандартный класс, предназначенный для создания модальных диалогов. Он поддерживает стандартный стиль оформления, характерный для всех диалогов программного комплекса, и обладает простым и удобным функционалом. Для вывода простых информационных сообщений и сообщений о ошибках используется [[developers:references:topomatic.controls.dialogs.messagedlg|MessageDlg]]. Достаточно вызвать статический метод [[developers:references:topomatic.controls.dialogs.messagedlg.show_system.string|MessageDlg.Show]] с требуемым сообщением. Достаточно создать наследника от [[developers:references:topomatic.controls.dialogs.simpledlg|SimpleDlg]] и перекрыть два метода: * **DoInit()** - метод вызывается при инициализации диалога, на нём необходимо инициализировать содержимое диалога * **DoCommit()** - метод вызывается, если пользователь выбрал кнопку ОК на диалоге. Необходимо проверить введённые данные, и в качестве результата вернуть - являются ли данные корректными. В случае если данные некорректны диалог не будет закрыт. Метод **DoGetModifiedWarning** класса [[developers:references:topomatic.controls.dialogs.simpledlg|SimpleDlg]] может быть использован, если необходимо предупредить пользователя о том, что он нажимает кнопку Отмена, а на диалог были внесены изменения. Для этого метод должен вернуть есть ли на диалоге какие-либо изменения. Достаточно часто бывает необходимо представить данные в виде редактируемой таблицы. Программный комплекс [[http://www.topomatic.ru|Топоматик Робур]] предоставляет для этого элемент управления [[developers:references:topomatic.controls.dialogs.edittableframe|EditTableFrame]]. Этот элемент предназначен для отображения таблицы со всеми необходимыми кнопками управления. Для отображения диалогов, состоящих из одной таблицы можно использовать класс [[developers:references:topomatic.controls.dialogs.edittabledlg|EditTableDlg]]. Для этого необходимо вызвать статический метод класса [[developers:references:topomatic.controls.dialogs.edittabledlg.execute_system.string_system.collections.ienumerable_system.boolean_system.boolean|EditTableDlg.Execute]] Для того чтобы заполнить таблицу данными необходимо присвоить свойство [[developers:references:topomatic.controls.dialogs.edittableframe.wrapper|Wrapper]] у экземпляра [[developers:references:topomatic.controls.dialogs.edittableframe|EditTableFrame]]. Поведение и вид элемента управления элемент управления [[developers:references:topomatic.controls.dialogs.edittableframe|EditTableFrame]] зависят от реализации класса, назначенного свойству [[developers:references:topomatic.controls.dialogs.edittableframe.wrapper|Wrapper]] и поддержки следующих интерфейсов: * Интерфейс [[developers:references:system.collections.ienumarable|IEnumerable]], таблица доступна только для редактирования элементов, удаление и добавление строк недоступно * Интерфейсы [[developers:references:system.collections.ilist|IList]] и [[developers:references:topomatic.componentmodel.iactivator|IActivator]], доступно добавление и удаление строк * Интерфейс [[developers:references:topomatic.controls.isupportclipboard|ISupportClipboard]], поддерживается возможность копирования и вставки строк через буфер обмена * Интерфейс [[developers:references:topomatic.componentmodel.isupportinterpolation|ISupportInterpolation]], поддерживается возможность интерполирования между строками таблицы Для более быстрого создания таблиц можно воспользоваться сервисным классом [[developers:references:topomatic.alg.runtime.wrappers.simplechangetrackingwrapper|SimpleChangeTrackingWrapper]]. Этот класс реализует интерфейсы [[developers:references:system.collections.ilist|IList]] и [[developers:references:system.componentmodel.ichangetracking|IChangeTracking]]. При его использовании необходимо на конструкторе создать объекты и заполнить ими свойство **Items**, а на перекрытой функции AcceptChanges заполнить данные модели по объектам этого свойства. Также необходимо поддержать интерфейс [[developers:references:topomatic.componentmodel.iactivator|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; } } ... Наша таблица будет выглядеть примерно так: {{ :developers:tutorial:dlgandpropertygrid:simple_table.png?direct&600 |}} Заголовок таблицы можно сделать многоуровневым, на нижнем уровне всегда отображается значение атрибута **[DisplayName]** а верхние настраиваются с помощью атрибута **[Category]** с разделением уровней символом "**|**". Например **[Category("Уровень3|Уровень1")]** Отображение свойств в таблице настраивается с помощью разнообразных атрибутов, наследованных от [[developers:references:topomatic.componentmodel.propertytypeconverter|PropertyTypeConverter]], [[developers:references:topomatic.componentmodel.propertyeditor|PropertyEditor]] и [[developers:references:topomatic.componentmodel.propertyprovider|PropertyProvider]]. Более подробно они описаны в других разделах [[developers:tutorial:start|Руководства]]. В частности, для отображения перечисления в виде выпадающего списка, необходимо написать наследника от [[developers:references:topomatic.componentmodel.baseenumconverter|BaseEnumConverter]]. В котором установить соответствие между значениями перечисления и строками используя свойство **Dictionary**. А затем назначить его на нужное свойство используя атрибут [[developers:references:topomatic.componentmodel.propertytypeconverterattribute|[PropertyTypeConverter]]]. Создайте и настройте новый [[developers:tutorial:module|модуль]] для подключения к программному комплексу [[http://www.topomatic.ru|Топоматик Робур]]. С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки: * [[developers:references:topomatic.cad.foundation|Topomatic.Cad.Foundation.dll]] - базовые математические типы и операции * [[developers:references:topomatic.cad.view|Topomatic.Cad.View.dll]] - элемент управления для отображения слоёв моделей * [[developers:references:topomatic.componentmodel|Topomatic.ComponentModel.dll]] - компоненты используемые для инспектирования свойств объектов * [[developers:references:topomatic.controls|Topomatic.Controls.dll]] - базовые диалоги и таблицы * [[developers:references:topomatic.alg.runtime|Topomatic.Alg.Runtime.dll]] - возможность использования класса [[developers:references:topomatic.alg.runtime.wrappers.simplechangetrackingwrapper|SimpleChangeTrackingWrapper]] Не забудьте поставить флаг «Копировать локально» в «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 m_Data; //При создании таблицы, заполняем её содержимое данными из модели public TableWrapper(List data) { m_Data = data; this.Items = new List(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** в строке таблицы декорировано атрибутом [[developers:references:topomatic.cad.foundation.design.lengthattribute|Length]]. Большая часть атрибутов реализованных в сборках программного комплекса, реализованы в пространствах имен **Design** Теперь напишем реализацию диалога. Для этого добавьте в проект новую форму и назовите её TestSimpleDialog. {{ :developers:tutorial:dlgandpropertygrid:create_form.png?direct&600 |}} Откройте окно редактора кода, и сделайте её наследником от [[developers:references:topomatic.controls.dialogs.simpledlg|Topomatic.Controls.Dialogs.SimpleDlg]]. Затем добавьте на форму обычную панель и откройте код дизайнера в файле TestSimpleDialog.Designer.cs {{ :developers:tutorial:dlgandpropertygrid:add_panel.png?direct&600 |}} Замените класс панели **panel1** на [[developers:references:topomatic.controls.dialogs.edittableframe|EditTableFrame]] вручную и сохраните файл. Теперь вернитесь в окно дизайнера и разместите [[developers:references:topomatic.controls.dialogs.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 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 m_Values = new List(); [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" ] } } } Здесь мы описываем [[developers:references:core.plugin:actions|actions]] для нашей команды, и добавляем пункт в меню. Результатом запуска проекта будет появления в главном меню пункта «Примеры», с подпунктом "Диалог и таблица", который будет работать в соответствии с описанным выше алгоритмом. После запуска и выполнения мы увидим следующий результат: {{ :developers:tutorial:dlgandpropertygrid:result.png?direct&600 |}} Исходный код примера, вы можете скачать используя эту ссылку {{ :developers:tutorial:dlgandpropertygrid:tutorial5.zip |Архив с кодом примера}}