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

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


developers:tutorial:sheets

Различия

Здесь показаны различия между двумя версиями данной страницы.

Ссылка на это сравнение

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
developers:tutorial:sheets [2022/08/26 09:30]
proxor [Создание пользовательской динамической ведомости]
developers:tutorial:sheets [2023/07/31 08:41] (текущий)
yulia
Строка 1: Строка 1:
 ===== Создание пользовательской динамической ведомости ===== ===== Создание пользовательской динамической ведомости =====
  
-В программном комплексе Топоматик Робур ведомости являются динамическими. Данные в динамической ведомости изменяются в соответствии с текущим состоянием базовой модели.+В программном комплексе ​[[http://​topomatic.ru/​|Топоматик Робур]] ведомости являются динамическими. Данные в [[road:​commons_tasks:​dynamic_vedomosti:​start|динамической ведомости]] изменяются в соответствии с текущим состоянием базовой модели.
  
-Ведомости создаются с помощью мастера создания ведомостей. Пользователь может добавить в мастер фреймы содержащие дополнительные настройки.+Ведомости создаются с помощью мастера создания ведомостей. Пользователь может добавить в мастер фреймы содержащие дополнительные настройки. Значения этих настроек можно сохранять в ведомости.
 ----- -----
 ==== Создание пользовательской динамической ведомости ==== ==== Создание пользовательской динамической ведомости ====
Строка 11: Строка 11:
   - Создание команд для запуска и обновления ведомости   - Создание команд для запуска и обновления ведомости
  
-Программно,​ ведомость является наследником от TemplateSheet или его подклассов. Класс ведомости содержит логику заполнения данными. В нём необходимо перекрыть метод GetSheetSymbols() определяющий заполнение ведомости данными.+Ведомость является наследником от [[developers:​references:​topomatic.tables.sheets.templatesheet|TemplateSheet]] или его подклассов. Класс ведомости содержит логику заполнения данными. В нём необходимо перекрыть метод ​[[developers:​references:​topomatic.tables.sheets.templatesheet.getsymbols|GetSheetSymbols()]] определяющий заполнение ведомости данными.
  
-Если требуется использовать фрейм с дополнительными настройками в мастере создания ведомости,​ то нужно перекрыть методы GetMonikers() и GetFrame(). Фрейм является наследником UserSheetWizardFrame и позволяет пользователю определить набор настроек используемых при создании ведомости.+Если требуется использовать фрейм с дополнительными настройками в мастере создания ведомости,​ то нужно перекрыть методы ​[[developers:​references:​topomatic.tables.sheets.templatesheet.getmonikers|GetMonikers()]] и [[developers:​references:​topomatic.tables.sheets.templatesheet.getframe|GetFrame()]]. Фрейм является наследником ​[[developers:​references:​topomatic.tables.export.usersheetwizardframe|UserSheetWizardFrame]] и позволяет пользователю определить набор настроекиспользуемых при создании ведомости.
  
-Если пользовательские настройки требуется сохранить,​ то так же следует перекрыть методы LoadFromStg() и SaveFromStg(). Подробнее о процессах сохранение и загрузки можно узнать в разделе руководства Создание и сохранение модели.+Если пользовательские настройки требуется сохранить,​ то так же следует перекрыть методы ​[[developers:​references:​topomatic.tables.sheets.templatesheet.loadfromstg|LoadFromStg()]] и [[developers:​references:​topomatic.tables.sheets.templatesheet.savetostg|SaveToStg()]]. Подробнее о процессах сохранение и загрузки можно узнать в разделе руководства Создание и сохранение модели.
  
-Шаблон ведомости представляет из себя XML-файл определяющий внешний вид ведомости и расположение ячеек заголовков и данных. Корневой каталог шаблонов ведомостей расположен по пути "​c:​\ProgramData\Topomatic\Robur <​Тип_продукта>​\16.0\Sht\"​ (в зависимости от вашего типа продукта программного комплекса Топоматик Робур, имя каталога будет разным). Далее шаблон располагается в подпапках соответствующих типу ведомости. Например ведомости подобъектов будут располагаться в каталоге Alg и т.д. Структура шаблона ведомости описана в разделе руководства Структура шаблона ведомости+Шаблон ведомости представляет из себя ​[[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|Создание и редактирование ​шаблонов выходных ​ведомостей]]
 ----- -----
 ==== Подготовка модуля ==== ==== Подготовка модуля ====
Строка 25: Строка 25:
 С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:​ С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:​
  
-  * Topomatic.Alg.dll - базовые классы подобъектов +  * [[developers:​references:​topomatic.alg|Topomatic.Alg.dll]] - базовые классы подобъектов 
-  * Topomatic.Alg.Model.dll - базовые классы моделей подобъектов +  * [[developers:​references:​topomatic.alg.model|Topomatic.Alg.Model.dll]] - базовые классы моделей подобъектов 
-  * Topomatic.Alg.Runtime.dll - возможность использования ​статического ​класса ​AlgCoreTools, ​для ​доступа к необходимым ​константам +  * [[developers:​references:​topomatic.alg.runtime|Topomatic.Alg.Runtime]] - возможность использования класса ​[[developers:​references:​topomatic.alg.runtime.serviceclasses.activealignmentreciver|ActiveAlignmentReciver]] ​для ​получения модели ​активного подобъекта 
-  * Topomatic.Alg.Tables.dll - возможность использования TemplateStationingSheet,​ подкласса TemplateSheet,​ содержащего указатель на пикетаж базового пути +  * [[developers:​references:​topomatic.alg.tables|Topomatic.Alg.Tables.dll]] - возможность использования ​[[developers:​references:​topomatic.alg.runtime.sheet.templatestationingsheet|TemplateStationingSheet]], подкласса ​[[developers:​references:​topomatic.tables.sheets.templatesheet|TemplateSheet]], содержащего указатель на пикетаж базового пути 
-  * Topomatic.Cad.Foundation.dll - базовые математические типы и операции +  * [[developers:​references:​topomatic.cad.foundation|Topomatic.Cad.Foundation.dll]] - базовые математические типы и операции 
-  * Topomatic.ComponentModel.dll - возможность использования ​статического класса TypeExplorer +  * [[developers:​references:​topomatic.componentmodel|Topomatic.ComponentModel.dll]] классы атрибутов 
-  * Topomatic.Tables.dll - базовые классы ведомостей +  * [[developers:​references:​topomatic.tables|Topomatic.Tables.dll]] - базовые классы ведомостей 
-  * Topomatic.Tables.Export.dll - классы экспорта ведомостей+  * [[developers:​references:​topomatic.tables.export|Topomatic.Tables.Export.dll]] - классы экспорта ведомостей
  
 ----- -----
Строка 41: Строка 41:
 === Класс ведомости === === Класс ведомости ===
  
-Так как ведомость предназначена для подобъекта, то наследовать наш класс мы будем от TemplateStationingSheet. В нашем классе ​ведомости необходимо перекрыть свойство Station, ​которое ​будет возвращать пикетаж (Station) базового ​подобъекта (Alignment). Создадим класс ведомости. Конструктор будет принимать ​имя базового подобъекта и сам подобъект.+Чтобы у пользователя была возможность выбрать участок ​подобъекта, в границах ​которого ​будет ​генерироваться ведомость следует наследовать ​наш класс от [[developers:​references:​topomatic.alg.runtime.sheet.templatestationingsheet|TemplateStationingSheet]].
  
-Опишем строки ведомости. Метод ​GetSheetSymbols() должен возвращать словарь ключей и значений. В нашем ​примере словарь будет содержать одну запись. В качестве ключа будет использоваться константа DEFAULT_ID класса TemplateSheetSymbolsЗначением будет экземпляр класса TemplateSheetSymbols содержащий в себе перечисление строк ведомости (RowData).+<​note>​ 
 +[[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]] - Признак использования всей протяжённости базового пути 
 +</​note>​
  
-В текущем примере ​коллекцию строк ​ведомости ​будет возвращать метод GetSymbols(). В мастере создания ведомости ​пользователь определяет в каких границах необходимо рассчитать ведомость. Сведения ​о границах ведомости можно получить с помощью свойств FromStation (Начало участка), ToStation (Конец участка) и All (Признак использования всей протяжённости ​базового пути) унаследованных от TemplateStationingSheetПользуясь этими значениями мы будем проверять,​ попадает ли узел чёрного профиля в границы расчёта. Для этого ​воспользуемся методом ​ ​StationInLimits() ​статического класса AlignmentValueConverter. Подробнее о методе StationInLimits() ​можно узнать ​в разделе руководства Теги динамических чертежей профилей в подразделе Пользовательский тег динамического чертежа продольного профиля.+В нашем классе ведомости ​необходимо перекрыть свойство ​[[developers:​references:​topomatic.alg.runtime.sheet.templatestationingsheet.stationing|Stationing]],​ которое будет возвращать пикетаж ([[developers:​references:​topomatic.alg.stationing.ialgstationing|Stationing]]) базового подобъекта ([[developers:​references:​topomatic.alg.alignment|Alignment]]). Создадим класс ведомости. Конструктор будет принимать ​имя базового подобъекта и сам подобъект.
  
-Получим осевой профиль базового подобъекта с помощью свойства Transitions ​класса ​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** шаблона строки в шаблоне ведомости. 
  
-Для описания строки ведомости воспользуемся классом SymbolContext,​ подклассом RowData. В качестве аргументов, ​конструктор SymbolContext принимает ​строку идентификатор и объект строки, свойства которого ​станут переменными шаблона и будут являться источниками данных. Объект строки может быть классом или структурой. Свойства ​этого ​объекта ​должны быть декорированы ​атрибутами DesignAliasAttribute и DescriptionAttribute. Значение ​атрибута ​DesignAliasAttribute в дальнейшем будут ​использоваться в шаблоне ​ведомости. +Стандартно используются следующие контексты: 
-Для ​строки ​заголовка ведомости ​опишем ​структуру HeadData содержащую свойство SheetName (Имя ведомости). Для ​строк с данным создадим класс TutorialRow и объявим в нём ​свойства ​для нужных нам данныхДля каждого узла чёрного ​профиля рассчитаем данные и создадим экземпляр класса TutorialRow,​ в который поместим эти данные. Если в результате расчётов не было создано ни одной строки с данными, добавим пустую строку.+  * [[developers:​references:​topomatic.tables.sheets.symbolcontext|SymobolContext]] - используется в большинстве случаев - разворачивает свойства объекта, помеченные ​специальными атрибутами в контекст строки 
 +  * [[developers:​references:​topomatic.tables.sheets.smtsymbolcontext|SmtSymobolContext]] - подкласс SymobolContext с возможностью развернуть семантические свойства 
 +  * [[developers:​references:​topomatic.tables.sheets.smdxsymbolcontext|SmdxSymobolContext]] - подкласс ​SymobolContext ​с возможностью развернуть smdx свойства
  
-Класс ведомости содержит уникальный объект-ключ. Объект-ключ это пустой ​объект класса Object. Этот объект должен создаваться ​при ​инициализации экземпляра класса ведомости и возвращаться вместе с объектами-ключами базового класса при вызове метода GetMonikers(). Объект-ключ ​служит в качестве идентификатора нашего экземпляра класса ​ведомости и служит ​для определения ​типа пользовательского контрола соответствующего типу ​нашей ведомости. Пользовательский контрол получается ​с помощью метода GetFrame() принимающий ​объект-ключ в качестве аргумента.+В качестве аргументов, конструктор [[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]]).
  
-Методы LoadFromStg() и SaveToStg() содержат инструкции записи и чтения данных ведомости,​ а так же состояния пользовательских настроек. В текущем примере наша ведомость содержит только одно пользовательское свойство UserSetting. Оно не будет использоваться в расчётах и описано в справочных целях. Свойство UserSetting является типом bool, поэтому для чтения и записи значения воспользуемся методом GetBoolean() класса StgNode.+В текущем примере коллекцию строк ведомости будет возвращать метод **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]].
  
 Добавьте в программу новые классы со следующим содержанием:​ Добавьте в программу новые классы со следующим содержанием:​
Строка 175: Строка 191:
  
         /// <​summary>​         /// <​summary>​
-        /// Получение ​контрола с пользовательскими настройками+        /// Получение ​фрейма с пользовательскими настройками
         /// </​summary>​         /// </​summary>​
         /// <param name="​moniker"></​param>​         /// <param name="​moniker"></​param>​
Строка 197: Строка 213:
         {         {
             base.SaveToStg(node);​             base.SaveToStg(node);​
-            node.GetBoolean("​UserSetting",​ UserSetting);​+            node.AddBoolean("​UserSetting",​ UserSetting);​
         }         }
  
Строка 205: Строка 221:
 </​code>​ </​code>​
  
-=== Класс пользовательского ​контрола === +=== Класс пользовательского ​фрейма === 
-Создадим класс пользовательского ​контрола (TutorialSheetFrame) наследник от UserSheetWizardFrame. В нашем примере пользовательский ​контрол будет содержать только один CheckBox. Добавим его в режиме конструктора Windows Forms. У нашего ​контрола необходимо перекрыть методы OnInitialize() и OnFinallize(),​ где мы будем читать и изменять состояние нашей ведомости соответственно. Так же можно перекрыть свойство Title, используемое мастером создания ведомости для формирования заголовка окна мастера.+Создадим класс пользовательского ​фрейма (**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]], используемое мастером создания ведомости для формирования заголовка окна мастера.
  
 Добавьте в программу новый класс со следующим содержанием:​ Добавьте в программу новый класс со следующим содержанием:​
Строка 251: Строка 267:
  
 === Шаблон ведомости === === Шаблон ведомости ===
-Создайте новый текстовый файл TutorialSheet.xml и расположите его в каталоге "​c:​\ProgramData\Topomatic\Robur <​Тип_продукта>​\16.0\Sht\Alg\TutorialSheet\"​. Заполните его следующим образом:​+Создайте новый текстовый файл ​**TutorialSheet.xml** и расположите его в каталоге "**c:​\ProgramData\Topomatic\Robur <​Тип_продукта>​\16.0\Sht\Alg\TutorialSheet\**". Заполните его следующим образом:​
 <code xml> <code xml>
 <?xml version="​1.0"​ encoding="​utf-8"?>​ <?xml version="​1.0"​ encoding="​utf-8"?>​
Строка 342: Строка 358:
 Для создания пользовательской ведомости следует объявить две команды. Первая команда создаёт экземпляр класса ведомости и определяет его состояние. Вторая команда будет вызывать первую и для возвращённого экземпляра класса ведомости вызовет мастер создания ведомости. Для создания пользовательской ведомости следует объявить две команды. Первая команда создаёт экземпляр класса ведомости и определяет его состояние. Вторая команда будет вызывать первую и для возвращённого экземпляра класса ведомости вызовет мастер создания ведомости.
  
-В теле программного модуля объявите команды,​ и декорируйте её атрибутом ​«cmd». Команда ​«generate_tutorial_sheet» ​создаст экземпляр класса ведомости,​ а команда ​«tutorial_sheet» ​вызовет для него мастер создания ведомости.+В теле программного модуля объявите команды,​ и декорируйте её атрибутом ​«**cmd**». Команда ​«**generate_tutorial_sheet**» ​создаст экземпляр класса ведомости,​ а команда ​«**tutorial_sheet**» ​вызовет для него мастер создания ведомости.
  
 <code csharp> <code csharp>
Строка 417: Строка 433:
 </​code>​ </​code>​
  
-Результатом запуска проекта будет появление в главном меню пункта ​«Tutorial», с подпунктами,​ которые будут работать в соответствии с описанными выше алгоритмами.+Результатом запуска проекта будет появление в главном меню пункта ​«**Tutorial**», с подпунктами,​ которые будут работать в соответствии с описанными выше алгоритмами.
  
 {{ :​developers:​tutorial:​sheets:​tutsheetsmaster.png?​nolink |}} {{ :​developers:​tutorial:​sheets:​tutsheetsmaster.png?​nolink |}}
developers/tutorial/sheets.1661506237.txt.gz · Последние изменения: 2022/08/26 09:30 — proxor