===== Создание пользовательской динамической ведомости ===== В программном комплексе [[http://topomatic.ru/|Топоматик Робур]] ведомости являются динамическими. Данные в [[road:commons_tasks:dynamic_vedomosti:start|динамической ведомости]] изменяются в соответствии с текущим состоянием базовой модели. Ведомости создаются с помощью мастера создания ведомостей. Пользователь может добавить в мастер фреймы содержащие дополнительные настройки. Значения этих настроек можно сохранять в ведомости. ----- ==== Создание пользовательской динамической ведомости ==== Для создания пользовательской ведомости необходимы следующие действия: - Создание класса ведомости - Создание шаблонов ведомости - Создание команд для запуска и обновления ведомости Ведомость является наследником от [[developers:references:topomatic.tables.sheets.templatesheet|TemplateSheet]] или его подклассов. Класс ведомости содержит логику заполнения данными. В нём необходимо перекрыть метод [[developers:references:topomatic.tables.sheets.templatesheet.getsymbols|GetSheetSymbols()]] определяющий заполнение ведомости данными. Если требуется использовать фрейм с дополнительными настройками в мастере создания ведомости, то нужно перекрыть методы [[developers:references:topomatic.tables.sheets.templatesheet.getmonikers|GetMonikers()]] и [[developers:references:topomatic.tables.sheets.templatesheet.getframe|GetFrame()]]. Фрейм является наследником [[developers:references:topomatic.tables.export.usersheetwizardframe|UserSheetWizardFrame]] и позволяет пользователю определить набор настроек, используемых при создании ведомости. Если пользовательские настройки требуется сохранить, то так же следует перекрыть методы [[developers:references:topomatic.tables.sheets.templatesheet.loadfromstg|LoadFromStg()]] и [[developers:references:topomatic.tables.sheets.templatesheet.savetostg|SaveToStg()]]. Подробнее о процессах сохранение и загрузки можно узнать в разделе руководства Создание и сохранение модели. Шаблон ведомости представляет из себя [[https://ru.wikipedia.org/wiki/XML|XML-файл]] определяющий внешний вид ведомости и расположение ячеек заголовков и данных. Корневой каталог шаблонов ведомостей расположен по пути "**c:\ProgramData\Topomatic\Robur <Тип_продукта>\16.0\Sht\**" (в зависимости от вашего типа [[https://new.topomatic.ru/products/|продукта]] программного комплекса [[https://new.topomatic.ru/|Топоматик Робур]], имя каталога будет разным). Далее шаблон располагается в подпапках, соответствующих типу ведомости. Например, ведомости подобъектов будут располагаться в каталоге Alg и т.д. Структура шаблона ведомости описана в разделе руководства [[road:commons_tasks:archive_sections:creating_and-editing_templates:start|Создание и редактирование шаблонов выходных ведомостей]]. ----- ==== Подготовка модуля ==== Создайте и настройте новый [[developers:tutorial:module|модуль]] для подключения к программному комплексу [[http://topomatic.ru/|Топоматик Робур]]. С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки: * [[developers:references:topomatic.alg|Topomatic.Alg.dll]] - базовые классы подобъектов * [[developers:references:topomatic.alg.model|Topomatic.Alg.Model.dll]] - базовые классы моделей подобъектов * [[developers:references:topomatic.alg.runtime|Topomatic.Alg.Runtime]] - возможность использования класса [[developers:references:topomatic.alg.runtime.serviceclasses.activealignmentreciver|ActiveAlignmentReciver]] для получения модели активного подобъекта * [[developers:references:topomatic.alg.tables|Topomatic.Alg.Tables.dll]] - возможность использования [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet|TemplateStationingSheet]], подкласса [[developers:references:topomatic.tables.sheets.templatesheet|TemplateSheet]], содержащего указатель на пикетаж базового пути * [[developers:references:topomatic.cad.foundation|Topomatic.Cad.Foundation.dll]] - базовые математические типы и операции * [[developers:references:topomatic.componentmodel|Topomatic.ComponentModel.dll]] - классы атрибутов * [[developers:references:topomatic.tables|Topomatic.Tables.dll]] - базовые классы ведомостей * [[developers:references:topomatic.tables.export|Topomatic.Tables.Export.dll]] - классы экспорта ведомостей ----- ==== Пользовательская динамическая ведомость ==== В этом примере мы создадим динамическую ведомость подобъекта. Ведомость будет содержать информацию об отметках чёрного профиля, красного профиля и их разницу в каждом узле чёрного профиля. === Класс ведомости === Чтобы у пользователя была возможность выбрать участок подобъекта, в границах которого будет генерироваться ведомость следует наследовать наш класс от [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet|TemplateStationingSheet]]. [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet|TemplateStationingSheet]] - подкласс [[developers:references:topomatic.tables.sheets.templatesheet|TemplateSheet]], служащий для генерации ведомостей на участке трассы. Класс содержит фрейм мастера создания ведомостей, позволяющий пользователю указать границы участка в пределах которого требуется сгенерировать ведомость. Класс содержит дополнительные свойства: * [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.fromstation|FromStation]] - Начало участка * [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.tostation|ToStation]] - Конец участка * [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.all|All]] - Признак использования всей протяжённости базового пути В нашем классе ведомости необходимо перекрыть свойство [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.stationing|Stationing]], которое будет возвращать пикетаж ([[developers:references:topomatic.alg.stationing.ialgstationing|Stationing]]) базового подобъекта ([[developers:references:topomatic.alg.alignment|Alignment]]). Создадим класс ведомости. Конструктор будет принимать имя базового подобъекта и сам подобъект. Опишем строки ведомости. Метод [[developers:references:topomatic.tables.sheets.templatesheet.getsymbols|GetSheetSymbols()]] должен возвращать контексты данных. Как минимум метод должен вернуть контекст с ключом [[developers:references:topomatic.tables.export.templatesheetsymbols.default_id|DEFAULT_ID]] (константа класса [[developers:references:topomatic.tables.export.templatesheetsymbols|TemplateSheetSymbols]]), как основной контекст ведомости. Контексты представляют из себя коллекции наследников [[developers:references:topomatic.tables.sheets.rowdata|RowData]]. [[developers:references:topomatic.tables.sheets.rowdata|RowData]] - класс, представляющий строку с данными ведомости, его [[developers:references:topomatic.tables.sheets.rowdata.id|Id]] совпадает с **Id** шаблона строки в шаблоне ведомости. Стандартно используются следующие контексты: * [[developers:references:topomatic.tables.sheets.symbolcontext|SymobolContext]] - используется в большинстве случаев - разворачивает свойства объекта, помеченные специальными атрибутами в контекст строки * [[developers:references:topomatic.tables.sheets.smtsymbolcontext|SmtSymobolContext]] - подкласс SymobolContext с возможностью развернуть семантические свойства * [[developers:references:topomatic.tables.sheets.smdxsymbolcontext|SmdxSymobolContext]] - подкласс SymobolContext с возможностью развернуть smdx свойства В качестве аргументов, конструктор [[developers:references:topomatic.tables.sheets.symbolcontext|SymobolContext]] принимает строку идентификатор и объект строки, свойства которого станут переменными шаблона и будут являться источниками данных. Объект строки может быть классом или структурой. Свойства этого объекта должны быть декорированы атрибутами [[developers:references:topomatic.componentmodel.designaliasattribute|DesignAliasAttribute]] и [[developers:references:system.componentmodel.descriptionattribute|DescriptionAttribute]]. [[developers:references:topomatic.componentmodel.designaliasattribute|DesignAliasAttribute]] - это ключ значения в строке, он совпадает с ключом значения строки в шаблоне ведомости. [[developers:references:system.componentmodel.descriptionattribute|DescriptionAttribute]] - описание поля для отображения в меню редактора шаблонов. В нашем примере [[developers:references:topomatic.tables.sheets.templatesheet.getsymbols|GetSheetSymbols()]] вернёт словарь с одной записью. Значением будет экземпляр класса [[developers:references:topomatic.tables.export.templatesheetsymbols|TemplateSheetSymbols]] содержащий в себе коллекцию контекстов ([[developers:references:topomatic.tables.sheets.symbolcontext|SymobolContext]]). В текущем примере коллекцию строк ведомости будет возвращать метод **GetSymbols()**. Пользуясь значениями свойств [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.fromstation|FromStation]], [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.tostation|ToStation]] и [[developers:references:topomatic.alg.runtime.sheet.templatestationingsheet.all|All]] мы будем проверять, попадает ли узел чёрного профиля в границы расчёта. Для этого воспользуемся методом [[developers:references:topomatic.alg.alignmentvalueconverter.stationinlimits_system.double_system.double_system.double_system.boolean_system.boolean|StationInLimits()]] статического класса [[developers:references:topomatic.alg.alignmentvalueconverter|AlignmentValueConverter]]. Подробнее о методе [[developers:references:topomatic.alg.alignmentvalueconverter.stationinlimits_system.double_system.double_system.double_system.boolean_system.boolean|StationInLimits()]] можно узнать в разделе руководства [[developers:tutorial:dwpfields|Теги динамических чертежей профилей]] в подразделе **Пользовательский тег динамического чертежа продольного профиля**. Получим осевой профиль базового подобъекта с помощью свойства [[developers:references:topomatic.alg.alignment.transitions|Transitions]] класса [[developers:references:topomatic.alg.alignment|Alignment]]. Подробнее о работе с продольными профилями можно узнать в разделе руководства [[developers:tutorial:algedit|Редактирование плана и профиля]] в подразделе **Редактирование профиля**. В текущем примере для описания строк ведомости воспользуемся классом [[developers:references:topomatic.tables.sheets.symbolcontext|SymobolContext]]. В качестве объекта контекста ([[developers:references:topomatic.tables.sheets.symbolcontext|SymobolContext]]) заголовка ведомости опишем структуру **HeadData** содержащую свойство **SheetName** (Имя ведомости). Для контекста данных создадим класс **TutorialRow** и объявим в нём свойства для нужных нам данных. Для каждого узла чёрного профиля рассчитаем данные и создадим экземпляр класса **TutorialRow**, в который поместим эти данные. Если в результате расчётов не было создано ни одной строки с данными, добавим пустую строку. Для возможности использования пользовательского фрейма настроек в мастере создания ведомостей, класс ведомости должен содержать уникальный объект-ключ (Moniker). Объект-ключ это пустой объект класса [[https://docs.microsoft.com/en-us/dotnet/api/system.object?view=net-6.0|Object]]. Этот объект должен создаваться при инициализации экземпляра класса ведомости и возвращаться вместе с объектами-ключами базового класса при вызове метода [[developers:references:topomatic.tables.sheets.templatesheet.getmonikers|GetMonikers()]]. Объект-ключ служит в качестве идентификатора нашего экземпляра класса ведомости и служит для определения типа пользовательского фрейма, соответствующего типу нашей ведомости. Пользовательский фрейм получается с помощью метода [[developers:references:topomatic.tables.sheets.templatesheet.getframe|GetFrame()]] принимающий объект-ключ в качестве аргумента. Методы [[developers:references:topomatic.tables.sheets.templatesheet.loadfromstg|LoadFromStg()]] и [[developers:references:topomatic.tables.sheets.templatesheet.savetostg|SaveToStg()]] содержат инструкции записи и чтения данных ведомости, а так же состояния пользовательских настроек. В текущем примере наша ведомость содержит только одно пользовательское свойство **UserSetting**. Оно не будет использоваться в расчётах и описано в справочных целях. Свойство **UserSetting** является типом [[https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/bool|bool]], поэтому для чтения и записи значения воспользуемся методами [[developers:references:topomatic.stg.stgcollection.getboolean_system.string_system.boolean|GetBoolean()]] и [[developers:references:topomatic.stg.stgcollection.addboolean_system.string_system.boolean|AddBoolean()]] класса [[developers:references:topomatic.stg.stgnode|StgNode]]. Добавьте в программу новые классы со следующим содержанием: ... class TutorialRow { [DesignAliasAttribute("Station"), DescriptionAttribute("Расстояние от начала пути")] public string Station { get; set; } [DesignAliasAttribute("Piket"), DescriptionAttribute("Пикет")] public string Piket { get; set; } [DesignAliasAttribute("EgElevation"), DescriptionAttribute("Отметка чёрного профиля")] public string EgElevation { get; set; } [DesignAliasAttribute("RedElevation"), DescriptionAttribute("Отметка красного профиля")] public string RedElevation { get; set; } [DesignAliasAttribute("ElevationDelta"), DescriptionAttribute("Разница отметок")] public string ElevationDelta { get; set; } } class TutorialSheet : TemplateStationingSheet { private struct HeadData { [DesignAlias("SheetName"), DescriptionAttribute("Имя ведомости")] public string SheetName { get; set; } } private object m_TutorialMoniker = new object(); public TutorialSheet(string alignmentName, Alignment alignment) : base("TutorialSheet", "Рабочие отметки", $"{alignmentName} - ведомость рабочих отметок") { m_Alignment = alignment; UserSetting = false; } public override IAlgStationing Stationing { get { return m_Alignment.Stationing; } } public bool UserSetting { get; set; } protected override IDictionary GetSheetSymbols() { var result = new Dictionary(); result.Add(TemplateSheetSymbols.DEFAULT_ID, new TemplateSheetSymbols(GetSymbols())); return result; } /// /// Определение строк таблицы /// /// private IEnumerable GetSymbols() { // Получаем границы в которых создаётся таблица var start_station = this.FromStation; var end_station = this.ToStation; if (this.All) { start_station = 0.0; end_station = m_Alignment.Plan.CompoundLine.Length; } // Получаем черный и красный профили модели var axisTransition = m_Alignment.Transitions[0]; var egProfile = axisTransition.EgProfile; var redProfile = axisTransition.RedProfile; // Создаём коллекцию строк и добавляем в неё строку заголовка ведомости var result = new List(); result.Add(new SymbolContext("Head", new HeadData { SheetName = DefaultName })); var cnt = result.Count; // У каждой вершины черного профиля проверяем попадает ли она в границы ведомости // Определяем отметку красного профиля на соответствующем расстоянии от оси пути // Вычисляем рабочую отметку и формируем строку ведомости for (int i = 0; i < egProfile.Count; i++) { var egNode = egProfile[i]; var sta = egNode.Station; if (AlignmentValueConverter.StationInLimits(sta, start_station, end_station, true, true)) { double redY; var res = redProfile.GetY(sta, out redY); if (res) { var row = new TutorialRow() { Station = TablesExtensions.FloatToStr(sta), Piket = m_Alignment.Stationing.StationToString(sta), EgElevation = TablesExtensions.FloatToStr(egNode.Elevation), RedElevation = TablesExtensions.FloatToStr(redY), ElevationDelta = TablesExtensions.FloatToStr(redY - egNode.Elevation) }; result.Add(new SymbolContext("Row", row)); } } } if (cnt == result.Count) result.Add(new SymbolContext("Row", new TutorialRow())); return result; } public override IEnumerable GetMonikers() { foreach (var m in base.GetMonikers()) yield return m; yield return m_TutorialMoniker; } /// /// Получение фрейма с пользовательскими настройками /// /// /// public override UserSheetWizardFrame GetFrame(object moniker) { if (moniker == m_TutorialMoniker) { return new TutorialSheetFrame(); } return base.GetFrame(moniker); } public override void LoadFromStg(StgNode node) { base.LoadFromStg(node); UserSetting = node.GetBoolean("UserSetting", false); } public override void SaveToStg(StgNode node) { base.SaveToStg(node); node.AddBoolean("UserSetting", UserSetting); } private Alignment m_Alignment; } ... === Класс пользовательского фрейма === Создадим класс пользовательского фрейма (**TutorialSheetFrame**) наследник от [[developers:references:topomatic.tables.export.usersheetwizardframe|UserSheetWizardFrame]]. В нашем примере пользовательский фрейм будет содержать только один [[https://docs.microsoft.com/en-us/dotnet/api/system.windows.forms.checkbox?view=windowsdesktop-6.0|CheckBox]]. Добавим его в режиме [[https://docs.microsoft.com/ru-ru/visualstudio/designers/windows-forms-designer-overview?view=vs-2022|конструктора Windows Forms]]. У нашего фрейма необходимо перекрыть методы [[developers:references:topomatic.tables.export.usersheetwizardframe.oninitialize|OnInitialize()]] и [[developers:references:topomatic.tables.export.usersheetwizardframe.onfinalize|OnFinallize()]], где мы будем читать и изменять состояние нашей ведомости соответственно. Так же можно перекрыть свойство [[developers:references:topomatic.tables.export.usersheetwizardframe.title|Title]], используемое мастером создания ведомости для формирования заголовка окна мастера. Добавьте в программу новый класс со следующим содержанием: ... partial class TutorialSheetFrame : UserSheetWizardFrame { public TutorialSheetFrame() { InitializeComponent(); } public override void OnInitialize(UserSheet sheet) { base.OnInitialize(sheet); var sht = sheet as TutorialSheet; if (sht != null) { cbUserSetting.Checked = sht.UserSetting; } } public override bool OnFinallize(UserSheet sheet) { var sht = sheet as TutorialSheet; if (sht != null) { sht.UserSetting = cbUserSetting.Checked; } return base.OnFinallize(sheet); } public override string Title { get { return Resources.sTutorialFrame; } } } ... === Шаблон ведомости === Создайте новый текстовый файл **TutorialSheet.xml** и расположите его в каталоге "**c:\ProgramData\Topomatic\Robur <Тип_продукта>\16.0\Sht\Alg\TutorialSheet\**". Заполните его следующим образом: $(getvar, SheetName) ПК Расстояние от начала трассы, м Существующая отметка, м Проектная отметка, м Рабочая отметка, м 1 2 3 4 5 $(getvar, Piket) $(getvar, Station) $(getvar, EgElevation) $(getvar, RedElevation) $(getvar, ElevationDelta)
=== Команды создания ведомости === Для создания пользовательской ведомости следует объявить две команды. Первая команда создаёт экземпляр класса ведомости и определяет его состояние. Вторая команда будет вызывать первую и для возвращённого экземпляра класса ведомости вызовет мастер создания ведомости. В теле программного модуля объявите команды, и декорируйте её атрибутом «**cmd**». Команда «**generate_tutorial_sheet**» создаст экземпляр класса ведомости, а команда «**tutorial_sheet**» вызовет для него мастер создания ведомости. ... // Команда создания ведомости [cmd("generate_tutorial_sheet")]... private UserSheet GenerateTutorialSheet(object[] args) { var m = args[0] as IProjectModel; if (m != null) { var am = m.LockRead() as AlignmentModel; if (am != null) { var name = ApplicationHost.Current.Plugins.Execute("getname", new object[] { m }) as string; TemplateSheet sht = new TutorialSheet(name, am.Alignment); sht.TemplateRelativePath = Path.Combine("Alg", "TutorialSheet"); sht.TemplateFileName = Path.Combine(sht.TemplatesPath, "TutorialSheet.xml"); return sht; } } return null; } // Команда вызова мастера создания ведомости [cmd("tutorial_sheet")] private void TutorialSheet() { using (var reciver = ActiveAlignmentReciver.CreateReciver(true)) { var alignment = reciver.Alignment; if (alignment != null) { ApplicationHost.Current.Plugins.Execute(TableConsts.TABLES_SHEET_FUNCTION, new object[] { "generate_tutorial_sheet", reciver.ProjectModel }); } } } ... Теперь необходимо сформировать наш файл .plugin. Заполните его следующим образом. { "assemblies": { "TutorialSheets": { "assembly": "TutorialSheets.dll, TutorialSheets.ModulePluginHost" } }, "actions": { "id_tutorial_sheet": { "cmd": "tutorial_sheet", "title": "Ведомость рабочих отметок", "description": "Ведомость рабочих отметок" } }, "menubars": { "rbproj": { "items": [ { "id": "tutorial_menu", "title": "Tutorial", "items": [ "id_tutorial_sheet" ] } ] } } } Результатом запуска проекта будет появление в главном меню пункта «**Tutorial**», с подпунктами, которые будут работать в соответствии с описанными выше алгоритмами. {{ :developers:tutorial:sheets:tutsheetsmaster.png?nolink |}} {{ :developers:tutorial:sheets:tutsheet.png?nolink |}} ----- [[developers:tutorial:tutorialcode|Исходный код]] примера расположен в проекте **"TutorialSheets"**.