===== Создание пользовательской динамической ведомости =====
В программном комплексе [[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
=== Класс пользовательского фрейма ===
Создадим класс пользовательского фрейма (**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)ПКРасстояние от начала трассы, мСуществующая отметка, мПроектная отметка, мРабочая отметка, м12345$(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"**.