Инструменты пользователя

Инструменты сайта


Боковая панель

developers:tutorial:sheets

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

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

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


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

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

  1. Создание класса ведомости
  2. Создание шаблонов ведомости
  3. Создание команд для запуска и обновления ведомости

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

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

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

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


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

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

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


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

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

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

Чтобы у пользователя была возможность выбрать участок подобъекта, в границах которого будет генерироваться ведомость следует наследовать наш класс от TemplateStationingSheet.

TemplateStationingSheet - подкласс TemplateSheet, служащий для генерации ведомостей на участке трассы. Класс содержит фрейм мастера создания ведомостей, позволяющий пользователю указать границы участка в пределах которого требуется сгенерировать ведомость. Класс содержит дополнительные свойства:
  • FromStation - Начало участка
  • ToStation - Конец участка
  • All - Признак использования всей протяжённости базового пути

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

Опишем строки ведомости. Метод GetSheetSymbols() должен возвращать контексты данных. Как минимум метод должен вернуть контекст с ключом DEFAULT_ID (константа класса TemplateSheetSymbols), как основной контекст ведомости. Контексты представляют из себя коллекции наследников RowData. RowData - класс, представляющий строку с данными ведомости, его Id совпадает с Id шаблона строки в шаблоне ведомости.

Стандартно используются следующие контексты:

  • SymobolContext - используется в большинстве случаев - разворачивает свойства объекта, помеченные специальными атрибутами в контекст строки
  • SmtSymobolContext - подкласс SymobolContext с возможностью развернуть семантические свойства
  • SmdxSymobolContext - подкласс SymobolContext с возможностью развернуть smdx свойства

В качестве аргументов, конструктор SymobolContext принимает строку идентификатор и объект строки, свойства которого станут переменными шаблона и будут являться источниками данных. Объект строки может быть классом или структурой. Свойства этого объекта должны быть декорированы атрибутами DesignAliasAttribute и DescriptionAttribute. DesignAliasAttribute - это ключ значения в строке, он совпадает с ключом значения строки в шаблоне ведомости. DescriptionAttribute - описание поля для отображения в меню редактора шаблонов. В нашем примере GetSheetSymbols() вернёт словарь с одной записью. Значением будет экземпляр класса TemplateSheetSymbols содержащий в себе коллекцию контекстов (SymobolContext).

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

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

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

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

Методы LoadFromStg() и SaveToStg() содержат инструкции записи и чтения данных ведомости, а так же состояния пользовательских настроек. В текущем примере наша ведомость содержит только одно пользовательское свойство UserSetting. Оно не будет использоваться в расчётах и описано в справочных целях. Свойство UserSetting является типом bool, поэтому для чтения и записи значения воспользуемся методами GetBoolean() и AddBoolean() класса 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.AddBoolean("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».
developers/tutorial/sheets.txt · Последние изменения: 2023/07/31 08:41 — yulia