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

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


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

developers:tutorial:sheets

Это старая версия документа.


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

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

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


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

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

  1. Создание класса ведомости и элемента управления настроек (далее контрола) для мастера создания ведомости
  2. Создание шаблона ведомости
  3. Объявление команд запуска мастера создания ведомости

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

  • Метод GetSheetSymbols() - определение строк ведомости
  • Метод GetMonikers() - перечисление объектов-ключей
  • Метод GetFrame() - получение соответствующего контрола настроек по объекту-ключу
  • Методы LoadFromStg() и SaveFromStg() - сохранение и загрузка ведомости. Подробнее о процессах сохранение и загрузки можно узнать в разделе руководства Создание и сохранение модели

Контрол является наследником UserSheetWizardFrame и позволяет пользователю определить набор настроек используемых при заполнении ведомости.

Шаблон ведомости представляет из себя 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».
developers/tutorial/sheets.1661505592.txt.gz · Последние изменения: 2022/08/26 09:19 — proxor