Создание пользовательской динамической ведомости

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

Ведомости создаются с помощью мастера создания ведомостей. Пользователь может добавить в мастер фреймы содержащие дополнительные настройки.

Создание пользовательской динамической ведомости

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

Создание класса ведомости

Создание шаблонов ведомости

Создание команд для запуска и обновления ведомости

Программно, ведомость является наследником от TemplateSheet или его подклассов. Класс ведомости содержит логику заполнения данными. В нём необходимо перекрыть метод GetSheetSymbols() определяющий заполнение ведомости данными.

Если требуется использовать фрейм с дополнительными настройками в мастере создания ведомости, то нужно перекрыть методы GetMonikers() и GetFrame(). Фрейм является наследником UserSheetWizardFrame и позволяет пользователю определить набор настроек используемых при создании ведомости.

Если пользовательские настройки требуется сохранить, то так же следует перекрыть методы LoadFromStg() и SaveFromStg(). Подробнее о процессах сохранение и загрузки можно узнать в разделе руководства Создание и сохранение модели.

Шаблон ведомости представляет из себя XML-файл определяющий внешний вид ведомости и расположение ячеек заголовков и данных. Корневой каталог шаблонов ведомостей расположен по пути «c:\ProgramData\Topomatic\Robur <Тип_продукта>\16.0\Sht\» (в зависимости от вашего типа продукта программного комплекса Топоматик Робур, имя каталога будет разным). Далее шаблон располагается в подпапках соответствующих типу ведомости. Например ведомости подобъектов будут располагаться в каталоге Alg и т.д. Структура шаблона ведомости описана в разделе руководства Структура шаблона ведомости.

Подготовка модуля

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

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

Topomatic.Alg.dll - базовые классы подобъектов

Topomatic.Alg.Model.dll - базовые классы моделей подобъектов

Topomatic.Alg.Runtime.dll - возможность использования статического класса AlgCoreTools, для доступа к необходимым константам

Topomatic.Alg.Tables.dll - возможность использования TemplateStationingSheet, подкласса TemplateSheet, содержащего указатель на пикетаж базового пути

Topomatic.Cad.Foundation.dll - базовые математические типы и операции

Topomatic.ComponentModel.dll - возможность использования статического класса TypeExplorer

Topomatic.Tables.dll - базовые классы ведомостей

Topomatic.Tables.Export.dll - классы экспорта ведомостей

Пользовательская динамическая ведомость

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

Класс ведомости

Так как ведомость предназначена для подобъекта, то наследовать наш класс мы будем от TemplateStationingSheet. В нашем классе ведомости необходимо перекрыть свойство Station, которое будет возвращать пикетаж (Station) базового подобъекта (Alignment). Создадим класс ведомости. Конструктор будет принимать имя базового подобъекта и сам подобъект.

Опишем строки ведомости. Метод GetSheetSymbols() должен возвращать словарь ключей и значений. В нашем примере словарь будет содержать одну запись. В качестве ключа будет использоваться константа DEFAULT_ID класса TemplateSheetSymbols. Значением будет экземпляр класса TemplateSheetSymbols содержащий в себе перечисление строк ведомости (RowData).

В текущем примере коллекцию строк ведомости будет возвращать метод GetSymbols(). В мастере создания ведомости пользователь определяет в каких границах необходимо рассчитать ведомость. Сведения о границах ведомости можно получить с помощью свойств FromStation (Начало участка), ToStation (Конец участка) и All (Признак использования всей протяжённости базового пути) унаследованных от TemplateStationingSheet. Пользуясь этими значениями мы будем проверять, попадает ли узел чёрного профиля в границы расчёта. Для этого воспользуемся методом StationInLimits() статического класса AlignmentValueConverter. Подробнее о методе StationInLimits() можно узнать в разделе руководства Теги динамических чертежей профилей в подразделе Пользовательский тег динамического чертежа продольного профиля.

Получим осевой профиль базового подобъекта с помощью свойства Transitions класса Alignment. Подробнее о работе с продольными профилями можно узнать в разделе руководства Редактирование плана и профиля в подразделе Редактирование профиля.

Для описания строки ведомости воспользуемся классом SymbolContext, подклассом RowData. В качестве аргументов, конструктор SymbolContext принимает строку идентификатор и объект строки, свойства которого станут переменными шаблона и будут являться источниками данных. Объект строки может быть классом или структурой. Свойства этого объекта должны быть декорированы атрибутами DesignAliasAttribute и DescriptionAttribute. Значение атрибута DesignAliasAttribute в дальнейшем будут использоваться в шаблоне ведомости. Для строки заголовка ведомости опишем структуру HeadData содержащую свойство SheetName (Имя ведомости). Для строк с данным создадим класс TutorialRow и объявим в нём свойства для нужных нам данных. Для каждого узла чёрного профиля рассчитаем данные и создадим экземпляр класса TutorialRow, в который поместим эти данные. Если в результате расчётов не было создано ни одной строки с данными, добавим пустую строку.

Класс ведомости содержит уникальный объект-ключ. Объект-ключ это пустой объект класса Object. Этот объект должен создаваться при инициализации экземпляра класса ведомости и возвращаться вместе с объектами-ключами базового класса при вызове метода GetMonikers(). Объект-ключ служит в качестве идентификатора нашего экземпляра класса ведомости и служит для определения типа пользовательского контрола соответствующего типу нашей ведомости. Пользовательский контрол получается с помощью метода GetFrame() принимающий объект-ключ в качестве аргумента.

Методы LoadFromStg() и SaveToStg() содержат инструкции записи и чтения данных ведомости, а так же состояния пользовательских настроек. В текущем примере наша ведомость содержит только одно пользовательское свойство UserSetting. Оно не будет использоваться в расчётах и описано в справочных целях. Свойство UserSetting является типом bool, поэтому для чтения и записи значения воспользуемся методом GetBoolean() класса 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<string, TemplateSheetSymbols> GetSheetSymbols() { var result = new Dictionary<string, TemplateSheetSymbols>(); result.Add(TemplateSheetSymbols.DEFAULT_ID, new TemplateSheetSymbols(GetSymbols())); return result; } /// <summary> /// Определение строк таблицы /// </summary> /// <returns></returns> private IEnumerable<RowData> 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<RowData>(); 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<object> GetMonikers() { foreach (var m in base.GetMonikers()) yield return m; yield return m_TutorialMoniker; } /// <summary> /// Получение контрола с пользовательскими настройками /// </summary> /// <param name="moniker"></param> /// <returns></returns> 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.GetBoolean("UserSetting", UserSetting); } private Alignment m_Alignment; } ...

Класс пользовательского контрола

Создадим класс пользовательского контрола (TutorialSheetFrame) наследник от UserSheetWizardFrame. В нашем примере пользовательский контрол будет содержать только один CheckBox. Добавим его в режиме конструктора Windows Forms. У нашего контрола необходимо перекрыть методы OnInitialize() и OnFinallize(), где мы будем читать и изменять состояние нашей ведомости соответственно. Так же можно перекрыть свойство 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\». Заполните его следующим образом:

<?xml version="1.0" encoding="utf-8"?> <Tables title="Рабочие отметки"> <Styles> <Style id="style_0"> <LeftBorderColor>7</LeftBorderColor> <RightBorderColor>7</RightBorderColor> <TopBorderColor>7</TopBorderColor> <BottomBorderColor>7</BottomBorderColor> <Alignment>4</Alignment> <PreferableDisplayStyle>0</PreferableDisplayStyle> <FloatDisplayStyleDigits>2</FloatDisplayStyleDigits> <RotationAngle>0</RotationAngle> <FillColor>33554432</FillColor> <TextColor>7</TextColor> <TextStyleName>Standard</TextStyleName> <HorizontalField>1.50</HorizontalField> <VerticalField>0.50</VerticalField> <TextHeight>3.00</TextHeight> <Bold>0</Bold> <Italic>0</Italic> <TextAngle>0.00</TextAngle> </Style> <Style id="style_1"> <LeftBorderColor>7</LeftBorderColor> <RightBorderColor>7</RightBorderColor> <TopBorderColor>7</TopBorderColor> <BottomBorderColor>7</BottomBorderColor> <Alignment>4</Alignment> <PreferableDisplayStyle>2</PreferableDisplayStyle> <FloatDisplayStyleDigits>2</FloatDisplayStyleDigits> <RotationAngle>0</RotationAngle> <FillColor>33554432</FillColor> <TextColor>7</TextColor> <TextStyleName>Standard</TextStyleName> <HorizontalField>1.50</HorizontalField> <VerticalField>0.50</VerticalField> <TextHeight>3.00</TextHeight> <Bold>0</Bold> <Italic>0</Italic> <TextAngle>0.00</TextAngle> </Style> </Styles> <Table id="Tutorial" name="Ведомость рабочих отметок"> <Subtable name="Заголовок" id="Head"> <Column index="0" width="50" options="2" /> <Column index="1" width="50" options="2" /> <Column index="2" width="50" options="2" /> <Column index="3" width="50" options="2" /> <Column index="4" width="50" options="2" /> <Row index="0" height="0" options="1"> <Cell index="0" colspan="5" style="style_0">$(getvar, SheetName)</Cell> </Row> <Row index="1" height="12" options="2"> <Cell index="0" style="style_0">ПК</Cell> <Cell index="1" style="style_0">Расстояние от начала трассы, м</Cell> <Cell index="2" style="style_0">Существующая отметка, м</Cell> <Cell index="3" style="style_0">Проектная отметка, м</Cell> <Cell index="4" style="style_0">Рабочая отметка, м</Cell> </Row> <Row index="2" height="6" options="2"> <Cell index="0" style="style_0">1</Cell> <Cell index="1" style="style_0">2</Cell> <Cell index="2" style="style_0">3</Cell> <Cell index="3" style="style_0">4</Cell> <Cell index="4" style="style_0">5</Cell> </Row> </Subtable> <Subtable name="Данные" id="Row"> <Column index="0" width="50" options="2" /> <Column index="1" width="50" options="2" /> <Column index="2" width="50" options="2" /> <Column index="3" width="50" options="2" /> <Column index="4" width="50" options="2" /> <Row index="0" height="6" options="2"> <Cell index="0" style="style_1">$(getvar, Piket)</Cell> <Cell index="1" style="style_1">$(getvar, Station)</Cell> <Cell index="2" style="style_1">$(getvar, EgElevation)</Cell> <Cell index="3" style="style_1">$(getvar, RedElevation)</Cell> <Cell index="4" style="style_1">$(getvar, ElevationDelta)</Cell> </Row> </Subtable> </Table> <TextStyle Annotative="0" Backward="0" Name="Standard" FileName="SPDS.shx" Flags="0" FontName="SPDS" Height="2" LastUsedHeight="2" Oblique="0.261799395084381" Ratio="1" UpsideDown="0" /> </Tables>

Команды создания ведомости

Для создания пользовательской ведомости следует объявить две команды. Первая команда создаёт экземпляр класса ведомости и определяет его состояние. Вторая команда будет вызывать первую и для возвращённого экземпляра класса ведомости вызовет мастер создания ведомости.

В теле программного модуля объявите команды, и декорируйте её атрибутом «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<Alignment>.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», с подпунктами, которые будут работать в соответствии с описанными выше алгоритмами.

Исходный код примера расположен в проекте «TutorialSheets».