====== Создание и сохранение модели ====== Каждая модель данных в комплексе [[http://www.topomatic.ru|Топоматик Робур]] располагается в рамках отдельного файла, включенного в состав проекта. С программной точки зрения модель представляет собой экземпляр класса, реализующий интерфейс [[developers:references:topomatic.applicationplatform.core.iprojectmodel|IProjectModel]]. Основные методы этого интерфейса это: * [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|LockWrite()]] - блокирует модель для начала редактирования данных * [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|UnockWrite()]] - разблокирует модель после окончания редактирования данных * [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|LockRead()]] - возвращает экземпляр класса, реализующий структуру данных конкретной модели Для чтения данных из модели используется метод [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|LockRead()]]. Если данные модели необходимо изменить, то перед вызовом [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|LockRead()]] необходимо вызвать [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|LockWrite()]], а после [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|UnockWrite()]]. После вызова метода [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|LockWrite()]] файл модели становится недоступным для редактирования другими пользователями до того момента, пока не будет вызван метод [[developers:references:topomatic.applicationplatform.core.iprojectmodel.lockwrite|UnockWrite()]]. Для подключения собственной модели к комплексу, необходимо выполнить следующие действия: * Создать класс модели, реализующий интерфейс [[developers:references:topomatic.foundationclasses.istatecontroller|IStateController]], это необходимо для того чтобы была возможность определять состояние модели и была ли она изменена. * Создать класс редактора модели, наследник от [[developers:references:topomatic.applicationplatform.core.modeleditor|ModelEditor]], предназначенный для реализации загрузки, сохранения и открытия модели * Написать метод, который возвращает экземпляр наследника от [[developers:references:topomatic.applicationplatform.core.modeleditor|ModelEditor]] и декорировать его атрибутом "cmd" * В классе, наследнике от [[developers:references:topomatic.applicationplatform.plugins.plugininitializator|PluginInitializator]] необходимо перекрыть метод [[developers:references:topomatic.applicationplatform.plugins.plugininitializator.initialize|Initialize]] и зарегестрировать модель, используя метод [[developers:references:topomatic.applicationplatform.plugins.pluginfactory.registermodeleditor|RegisterModelEditor]] класса [[developers:references:topomatic.applicationplatform.plugins.pluginfactory|PluginFactory]] * В файле *.plugin в секции [[developers:references:core.plugin:cores|cores]] описать вид модели в структуре проекта, а в секции [[developers:references:core.plugin:coreitems|coreitems]] включить модель в состав стандартного проекта. Большая часть методов интерфейса [[developers:references:topomatic.foundationclasses.istatecontroller|IStateController]] реализована в классе [[developers:references:topomatic.foundationclasses.statecontrollerobject|StateControllerObject]]. Редактор модели на видовом экране окна план и других системных окон нужно наследовать от [[developers:references:topomatic.applicationplatform.core.planmodeleditor|PlanModelEditor]]. Редактор модели в отдельном окне нужно наследовать от [[developers:references:topomatic.applicationplatform.core.documentmodeleditor|DocumentModelEditor]] Для реализации сохранения и загрузки в программном комплексе [[http://www.topomatic.ru|Топоматик Робур]] используется сборка [[developers:references:topomatic.stg|Topomatic.Stg.dll]]. За сохранение отвечает интерфейс [[developers:references:topomatic.stg.istgserializable|IStgSerializable]], который состоит из двух методов - [[developers:references:topomatic.stg.istgserializable.loadfromstg_topomatic.stg.stgnode|LoadFromStg]] для загрузки и [[developers:references:topomatic.stg.istgserializable.savetostg_topomatic.stg.stgnode|SaveToStg]] для сохранения. Оба метода в качестве параметра принимают экземпляр класса [[developers:references:topomatic.stg.stgnode|StgNode]]. Методы этого класса позволяют сохранить в узел и загрузить из узла все базовые типы данных, а также дополнительные узлы [[developers:references:topomatic.stg.stgnode|StgNode]] и массивы элементов, используя именованные элементы. Кроме того у каждого метода есть перекрытое значение, со значением по умолчанию. Значение по умолчанию будет использовано в том случае, если в загружаемом файле нет такого именованного элемента. Например: { .... private double m_Value1 = 0.0; private string m_Value2 = 0.0; void LoadFromStg(StgNode node) { //Если в сохраненном файле нет значения с ключом Value1 будет брошено исключение m_Value1 = node.GetDouble("Value1"); //Если в сохраненном файле нет значения с ключом Value2, //то переменной m_Value2 будет присвоено значение "defaultValue" m_Value2 = node.GetString("Value2", "defaultValue"); } ... } Использование именованных элементов позволяет легко расширять и дополнять функции загрузки и сохранения по мере расширения модели данных. Если в сохраненном файле отсутствует вложенный узел [[developers:references:topomatic.stg.stgnode|StgNode]] или массив, то функции [[developers:references:topomatic.stg.stgnode.getnode_system.string|GetNode]] и [[developers:references:topomatic.stg.stgnode.getarray_system.string_topomatic.stg.stgtype|GetArray]] вернут пустые элементы. Проверить наличие именованного значения внутри узла позволяет метод [[developers:references:topomatic.stg.stgcollection.isexists_system.string|IsExists]] Создайте и настройте новый [[developers:tutorial:module|модуль]] для подключения к программному комплексу [[http://www.topomatic.ru|Топоматик Робур]]. С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки: * [[developers:references:topomatic.cad.foundation|Topomatic.Cad.Foundation.dll]] - базовые математические типы и операции * [[developers:references:topomatic.controls|Topomatic.Controls.dll]] - базовые диалоги и таблицы Создайте новый класс Model.cs для реализации нашей модели. { //Класс реализующий структуру данных нашей модели //для поддержки интерфейса IStateController наследуем от StateControllerObject class Model : StateControllerObject, IStgSerializable { private bool m_ReadOnly = false; //Строковое значение public string StringValue = "Строка"; //Булево значение public bool BooleanValue = false; //Значение с плавающей точкой public double DoubleValue = 10.5; //Целое значение public int IntValue = 10; //Список строковых значений public List ArrayValues = new List(); //флаг только для чтения public override bool ReadOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } } //Загрузка из узла public void LoadFromStg(StgNode node) { //Все значения загружаем с указанием значения по умолчанию BooleanValue = node.GetBoolean("BooleanValue", false); StringValue = node.GetString("StringValue", "Строка"); DoubleValue = node.GetDouble("DoubleValue", 10.5); IntValue = node.GetInt32("IntValue", 10); ArrayValues.Clear(); //При загрузке массива указывается тип составляющих массив значений var array = node.GetArray("ArrayValues", StgType.String); for (int i = 0; i < array.Count; i++) { ArrayValues.Add(array.GetString(i)); } } //Сохранение в узел public void SaveToStg(StgNode node) { //Сохраняем значения в узел node.AddBoolean("BooleanValue", BooleanValue); node.AddString("StringValue", StringValue); node.AddDouble("DoubleValue", DoubleValue); node.AddInt32("IntValue", IntValue); //Сохраняем массив с указанием типа значений var array = node.AddArray("ArrayValues", StgType.String); for (int i = 0; i < ArrayValues.Count; i++) { array.AddString(ArrayValues[i]); } } } } И класс Editor.cs реализующий редактор модели { //Класс реализующий редактор нашей модели class Editor : ModelEditor { //Ссылки на другие модели в структуре public override ModelReference[] GetReferences(object model) { //В нашем случае никаких ссылок нет return null; } //Реализация загрузки модели по указанному пути, должна вернуть реализацию класса нашей модели public override object LoadFromFile(string fullpath) { //создаем экземпляр класса модели var model = new Model(); //если fullpath null - то необходимо просто вернуть экземпляр класса модели, без загрузки данных if (fullpath != null) { //создаем файловый поток using (var stream = new FileStream(fullpath, FileMode.Open, FileAccess.Read, FileShare.Read)) { //создаем документ для работы с Topomatic.Stg var document = new StgDocument(); //загружаем документ из потока в бинарном виде document.LoadFromStreamAsBinary(stream); //загружаем данные нашей модели из документа model.LoadFromStg(document.Body); } } //всегда возвращаем экземпляр модели return model; } //Реализация сохранения модели по указанному пути public override void SaveToFile(object model, string fullpath) { //в качестве параметра model приходит наша модель данных var m = model as Model; if (m != null) { //создаем файловый поток using (var stream = new FileStream(fullpath, FileMode.Create, FileAccess.Write, FileShare.None)) { //создаем документ для работы с Topomatic.Stg var document = new StgDocument(); //сохраняем данные нашей модели в документ m.SaveToStg(document.Body); //сохраняем документ в потока в бинарном виде document.SaveToStreamAsBinary(stream); } } } //Реализация открытия модели по команде "open" public override IEditorResult Open(IProjectModel model) { var cursor = Cursor.Current; Cursor.Current = Cursors.WaitCursor; try { //В нашем поросто возвращаем реализацию интерфеса IEditorResult return new EditorResult(); } finally { Cursor.Current = cursor; } } //Реализация интерфейса IEditorResult private class EditorResult : IEditorResult { private bool m_Opened; public EditorResult() { m_Opened = true; } //Необходимо реализовать флаг, показывающий открыта модель или нет public bool Opened { get { return m_Opened; } } //Необходимо реализовать метод закрытия модели public void Close() { //В нашем случае мы просто управляем флагом и все m_Opened = false; } //И метод перезагрузки модели public void Reload() { //Здесь нам ничего не нужно делать } } } } В нашем случае редактор модели получился достаточно простым, поскольку наша модель не реагирует на команду открыть и фактически вся работа с ней будет происходить в отдельной команде для редактирования модели. Дополнительно необходимо реализовать [[developers:tutorial:dlgandpropertygrid|диалог]] для редактирования данных модели. Его реализацию можно увидеть в исходных кодах примера. В теле программного модуля необходимо объявить функции для создания и редактирования модели, функцию для создания редактора и регистрации модели. partial class Module : Topomatic.ApplicationPlatform.Plugins.PluginInitializator { //функция создает экземпляр редактора модели и возвращает его [cmd("create_testmodel_editor")] public Editor CreateEditor() { return new Editor(); } //функция создает новую модель и возвращает её [cmd("create_testmodel")] private object CreateModel(object[] args) { //получаем текущий активный проект var project = ApplicationHost.Current.ActiveProject as ModelProject; Debug.Assert(project != null); if (project != null) { //запускаем групповое изменение свойств project.TransactionManager.BeginUpdate(); try { string folder; if ((args != null) && (args.Length > 0) && (args[0] != null)) { //В качестве аргумента приходит либо идентификатор каталога внутри проекта folder = args[0].ToString(); } else { //либо мы создаем этот идентификатор самостоятельно folder = PluginCoreOps.FindModelPathId(PluginCoreOps.CreateFolder(new string[] { "Модели", "Тестовые модели" })); } //создаем модель с помощью команды "mkitem" //в неё мы передаем идентификатор каталога и тип нашей модели return ApplicationHost.Current.Plugins.Execute("mkitem", new object[] { folder, "testmodel" }); } finally { project.TransactionManager.EndUpdate(); } } else { throw new OperationCanceledException(); } } //функция позволяет редактировать содержимое нашей модели [cmd("edit_testmodel")] private void OpenModel(string pathid) { //получаем IProjectModel используя идентификатор модели var project_model = PluginCoreOps.FindModel(pathid); if (project_model != null) { //вызываем блокировку модели на редактирование project_model.LockWrite(); try { //получаем класс нашей модели var model = project_model.LockRead() as Model; if (model != null) { //вызываем диалог редактирования нашей модели if (EditModelDlg.Execute(model)) project_model.Modified = true; } } finally { //снимаем блокировку модели на редактирование project_model.UnlockWrite(); } } } public override void Initialize(PluginFactory factory) { base.Initialize(factory); //Регестрируем нашу модель в проекте factory.RegisterModelEditor("testmodel", new ModelEditorInfo("Тестовая модель|*.testmodelx", ".testmodelx", "testmodel", "Тестовая модель", "create_testmodel_editor")); } } В файле .plugin нужно разместить нашу модель в секции [[developers:references:core.plugin:cores|cores]] и добавить [[developers:tutorial:cmdattribute|команды]] для её редактирования и создания. { "assemblies": { "tutorial6": { "assembly": "tutorial6.dll, tutorial6.ModulePluginHost" } }, "actions": { "id_create_testmodel": { "cmd": "create_testmodel \"%0\"", "title": "Тестовая модель" }, "id_edit_testmodel": { "cmd": "edit_testmodel \"%0\"", "title": "Редактировать..." } }, "contexts": { "ctx_mkitem": { "items": [ "id_create_testmodel \"%0\"" ] }, "testmodel.context": { "priority": 1001, "items": [ { "default": "$(if,$(opened,%0),core.id_close \"%0\" \"Скрыть модель\",core.id_open \"%0\" \"Показать модель\")" }, "id_edit_testmodel \"%0\"", "-", "core.id_rmitem \"%0\"" ] } }, "cores": { "testmodel": { "title": "$(referencename,%0)", "description": "Тестовая модель %0", "icon": "ic_file", "statusicon": "", "flags": "$(modelflags,%0)", "menu": "testmodel.context \"%0\"" } } } Обратите внимание на секцию [[developers:references:core.plugin:contexts|contexts]]. Для добавления возможности создать нашу тестовую модель из контекстного меню каталога проекта, мы добавляем функцию создания нашей модели в системное меню "ctx_mkitem". В результате мы получим возможность добавлять в проект наши тестовые модели и редактировать их содержимое по команде "Редактировать" в контекстном меню модели. {{ :developers:tutorial:createmodel:contexmenu.png?direct&600 |}} Исходный код примера, вы можете скачать используя эту ссылку {{ :developers:tutorial:createmodel:tutorial6.zip |Архив с кодом примера}}