Каждая модель данных в комплексе Топоматик Робур располагается в рамках отдельного файла, включенного в состав проекта. С программной точки зрения модель представляет собой экземпляр класса, реализующий интерфейс IProjectModel. Основные методы этого интерфейса это:
Для чтения данных из модели используется метод LockRead(). Если данные модели необходимо изменить, то перед вызовом LockRead() необходимо вызвать LockWrite(), а после UnockWrite().
Для подключения собственной модели к комплексу, необходимо выполнить следующие действия:
Для реализации сохранения и загрузки в программном комплексе Топоматик Робур используется сборка Topomatic.Stg.dll. За сохранение отвечает интерфейс IStgSerializable, который состоит из двух методов - LoadFromStg для загрузки и SaveToStg для сохранения. Оба метода в качестве параметра принимают экземпляр класса 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"); } ... }
Использование именованных элементов позволяет легко расширять и дополнять функции загрузки и сохранения по мере расширения модели данных.
Создайте и настройте новый модуль для подключения к программному комплексу Топоматик Робур.
С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:
Создайте новый класс 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<string> ArrayValues = new List<string>(); //флаг только для чтения 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 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() { //Здесь нам ничего не нужно делать } } } }
В нашем случае редактор модели получился достаточно простым, поскольку наша модель не реагирует на команду открыть и фактически вся работа с ней будет происходить в отдельной команде для редактирования модели. Дополнительно необходимо реализовать диалог для редактирования данных модели. Его реализацию можно увидеть в исходных кодах примера.
В теле программного модуля необходимо объявить функции для создания и редактирования модели, функцию для создания редактора и регистрации модели.
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 нужно разместить нашу модель в секции cores и добавить команды для её редактирования и создания.
{ "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\"" } } }
Обратите внимание на секцию contexts. Для добавления возможности создать нашу тестовую модель из контекстного меню каталога проекта, мы добавляем функцию создания нашей модели в системное меню «ctx_mkitem».
В результате мы получим возможность добавлять в проект наши тестовые модели и редактировать их содержимое по команде «Редактировать» в контекстном меню модели.