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

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


developers:tutorial:sheets

Различия

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

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

Предыдущая версия справа и слева Предыдущая версия
Следующая версия
Предыдущая версия
developers:tutorial:sheets [2022/08/25 16:20]
proxor [Пользовательская динамическая ведомость]
developers:tutorial:sheets [2023/07/31 08:41] (текущий)
yulia
Строка 1: Строка 1:
 ===== Создание пользовательской динамической ведомости ===== ===== Создание пользовательской динамической ведомости =====
  
-В программном комплексе Топоматик Робур ведомости являются динамическими. Данные в динамической ведомости изменяются в соответствии с текущим состоянием базовой модели.+В программном комплексе ​[[http://​topomatic.ru/​|Топоматик Робур]] ведомости являются динамическими. Данные в [[road:​commons_tasks:​dynamic_vedomosti:​start|динамической ведомости]] изменяются в соответствии с текущим состоянием базовой модели.
  
 +Ведомости создаются с помощью мастера создания ведомостей. Пользователь может добавить в мастер фреймы содержащие дополнительные настройки. Значения этих настроек можно сохранять в ведомости.
 +-----
 ==== Создание пользовательской динамической ведомости ==== ==== Создание пользовательской динамической ведомости ====
 Для создания пользовательской ведомости необходимы следующие действия:​ Для создания пользовательской ведомости необходимы следующие действия:​
-  - Создание класса ​ведомости и элемента управления настроек (далее [[https://​docs.microsoft.com/​ru-ru/​dotnet/​desktop/​winforms/​controls/​custom?​view=netdesktop-6.0|контрола]]) для мастера создания ​ведомости +  - Создание класса ведомости 
-  - Создание шаблона ведомости +  - Создание шаблонов ведомости 
-  - Объявление команд запуска ​мастера создания ведомости+  - Создание команд ​для ​запуска ​и обновления ведомости
  
-Программно,​ ведомость является наследником от TemplateSheet или его подклассов. Класс ведомости содержит логику заполнения данными. В нём необходимо перекрыть ​следующие элементы: +Ведомость является наследником от [[developers:​references:​topomatic.tables.sheets.templatesheet|TemplateSheet]] или его подклассов. Класс ведомости содержит логику заполнения данными. В нём необходимо перекрыть метод ​[[developers:​references:​topomatic.tables.sheets.templatesheet.getsymbols|GetSheetSymbols()]] определяющий заполнение ведомости данными.
-  * Метод GetSheetSymbols() ​определение строк ведомости +
-  * Метод GetMonikers() - перечисление объектов-ключей +
-  * Метод GetFrame() - получение соответствующего контрола настроек ​по объекту-ключу +
-  * Методы LoadFromStg() и SaveFromStg() - сохранение ​и загрузка ​ведомости. Подробнее о процессах сохранение и загрузки можно узнать в разделе руководства Создание и сохранение модели +
-   +
-Контрол является наследником UserSheetWizardFrame и позволяет пользователю определить набор настроек используемых при заполнении ведомости.+
  
-Шаблон ведомости представляет из себя XML-файл определяющий внешний вид ведомости и расположение ячеек заголовков и данных. Корневой каталог шаблонов ведомостей расположен по пути "​c:​\ProgramData\Topomatic\Robur <​Тип_продукта>​\16.0\Sht\"​ (в зависимости от вашего типа продукта программного комплекса Топоматик Робур, имя каталога будет разным). Далее шаблон располагается в подпапках соответствующих типу ведомости. Например ведомости подобъектов будут располагаться в каталоге Alg и т.д. Структура шаблона ведомости описана в разделе руководства Структура шаблона ведомости+Если требуется использовать фрейм с дополнительными настройками в мастере создания ведомости,​ то нужно перекрыть методы [[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|Создание и редактирование ​шаблонов выходных ​ведомостей]]
 ----- -----
 ==== Подготовка модуля ==== ==== Подготовка модуля ====
Строка 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]]. 
 + 
 +<​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>​ 
 + 
 +В нашем классе ведомости необходимо перекрыть свойство ​[[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 свойства
  
-Опишем строки ​ведомостиМетод ​GetSheetSymbols() должен возвращать словарь ключей и значений. В нашем примере словарь будет содержать одну ​записьВ качестве ключа будет использоваться константа DEFAULT_ID класса TemplateSheetSymbols. Значением будет экземпляр класса TemplateSheetSymbols содержащий в себе ​перечисление строк ведомости ​(RowData).+В качестве ​аргументов, конструктор [[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(). ​В мастере создания ведомости пользователь определяет в каких границах необходимо рассчитать ведомость. Сведения ​о границах ведомости можно получить с помощью ​свойств FromStation ​(Начало участка), ToStation ​(Конец участка) ​и All (Признак использования всей протяжённости базового пути) унаследованных от TemplateStationingSheet. Пользуясь этими значениями ​мы будем проверять,​ попадает ли узел чёрного профиля в границы расчёта. Для этого воспользуемся методом ​ StationInLimits() статического класса AlignmentValueConverter. Подробнее о методе StationInLimits() можно узнать в разделе руководства Теги динамических чертежей профилей в подразделе Пользовательский тег динамического чертежа продольного профиля.+В текущем примере коллекцию строк ведомости будет возвращать метод ​**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|Теги динамических чертежей профилей]] в подразделе ​**Пользовательский тег динамического чертежа продольного профиля**.
  
-Получим осевой профиль базового подобъекта с помощью свойства Transitions класса Alignment. Подробнее о работе с продольными профилями можно узнать в разделе руководства Редактирование плана и профиля в подразделе Редактирование профиля.+Получим осевой профиль базового подобъекта с помощью свойства ​[[developers:​references:​topomatic.alg.alignment.transitions|Transitions]] класса ​[[developers:​references:​topomatic.alg.alignment|Alignment]]. Подробнее о работе с продольными профилями можно узнать в разделе руководства ​[[developers:​tutorial:​algedit|Редактирование плана и профиля]] в подразделе ​**Редактирование профиля**.
  
-Для описания строки ведомости воспользуемся классом ​SymbolContext,​ подклассом RowData. В качестве ​аргументов,​ конструктор SymbolContext принимает строку идентификатор и объект ​строки,​ свойства которого станут переменными шаблона и будут являться источниками данных. Объект ​строки может быть классом или структуройСвойства этого объекта должны быть декорированы атрибутами DesignAliasAttribute и DescriptionAttributeЗначение атрибута DesignAliasAttribute в дальнейшем будут использоваться в шаблоне ведомости. +В текущем примере для описания строк ведомости воспользуемся классом ​[[developers:​references:​topomatic.tables.sheets.symbolcontext|SymobolContext]]. В качестве объекта контекста ​([[developers:​references:​topomatic.tables.sheets.symbolcontext|SymobolContext]]) ​заголовка ведомости опишем структуру ​**HeadData** содержащую свойство ​**SheetName** (Имя ведомости). Для ​контекста данных создадим класс ​**TutorialRow** и объявим в нём свойства для нужных нам данных. Для каждого узла чёрного профиля рассчитаем данные и создадим экземпляр класса ​**TutorialRow**, в который поместим эти данные. Если в результате расчётов не было создано ни одной строки с данными,​ добавим пустую строку.
-Для строки ​заголовка ведомости опишем структуру HeadData содержащую свойство SheetName (Имя ведомости). Для ​строк с данным создадим класс TutorialRow и объявим в нём свойства для нужных нам данных. Для каждого узла чёрного профиля рассчитаем данные и создадим экземпляр класса TutorialRow,​ в который поместим эти данные. Если в результате расчётов не было создано ни одной строки с данными,​ добавим пустую строку.+
  
-Класс ведомости содержит уникальный объект-ключ. Объект-ключ это пустой объект класса Object. Этот объект должен создаваться при инициализации экземпляра класса ведомости и возвращаться вместе с объектами-ключами базового класса при вызове метода GetMonikers(). Объект-ключ служит в качестве идентификатора нашего экземпляра класса ведомости и служит для определения типа пользовательского ​контрола соответствующего типу нашей ведомости. Пользовательский ​контрол получается с помощью метода GetFrame() принимающий объект-ключ в качестве аргумента.+Для возможности использования пользовательского фрейма настроек в мастере создания ведомостей,​ класс ведомости ​должен ​содержать уникальный объект-ключ ​(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()]] принимающий объект-ключ в качестве аргумента.
  
-Методы LoadFromStg() и SaveToStg() содержат инструкции записи и чтения данных ведомости,​ а так же состояния пользовательских настроек. В текущем примере наша ведомость содержит только одно пользовательское свойство UserSetting. Оно не будет использоваться в расчётах и описано в справочных целях. Свойство UserSetting является типом bool, поэтому для чтения и записи значения воспользуемся методом GetBoolean() класса StgNode.+Методы ​[[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.1661444449.txt.gz · Последние изменения: 2022/08/25 16:20 — proxor