====== Создание и сохранение модели ======
Каждая модель данных в комплексе [[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 |Архив с кодом примера}}