Создание и сохранение модели

Каждая модель данных в комплексе Топоматик Робур располагается в рамках отдельного файла, включенного в состав проекта. С программной точки зрения модель представляет собой экземпляр класса, реализующий интерфейс IProjectModel. Основные методы этого интерфейса это:

  • LockWrite() - блокирует модель для начала редактирования данных
  • UnockWrite() - разблокирует модель после окончания редактирования данных
  • LockRead() - возвращает экземпляр класса, реализующий структуру данных конкретной модели

Для чтения данных из модели используется метод LockRead(). Если данные модели необходимо изменить, то перед вызовом LockRead() необходимо вызвать LockWrite(), а после UnockWrite().

После вызова метода LockWrite() файл модели становится недоступным для редактирования другими пользователями до того момента, пока не будет вызван метод UnockWrite().

Для подключения собственной модели к комплексу, необходимо выполнить следующие действия:

  • Создать класс модели, реализующий интерфейс IStateController, это необходимо для того чтобы была возможность определять состояние модели и была ли она изменена.
  • Создать класс редактора модели, наследник от ModelEditor, предназначенный для реализации загрузки, сохранения и открытия модели
  • Написать метод, который возвращает экземпляр наследника от ModelEditor и декорировать его атрибутом «cmd»
  • В классе, наследнике от PluginInitializator необходимо перекрыть метод Initialize и зарегестрировать модель, используя метод RegisterModelEditor класса PluginFactory
  • В файле *.plugin в секции cores описать вид модели в структуре проекта, а в секции coreitems включить модель в состав стандартного проекта.
Большая часть методов интерфейса IStateController реализована в классе StateControllerObject. Редактор модели на видовом экране окна план и других системных окон нужно наследовать от PlanModelEditor. Редактор модели в отдельном окне нужно наследовать от DocumentModelEditor

Для реализации сохранения и загрузки в программном комплексе Топоматик Робур используется сборка 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");
    }
    ...
}

Использование именованных элементов позволяет легко расширять и дополнять функции загрузки и сохранения по мере расширения модели данных.

Если в сохраненном файле отсутствует вложенный узел StgNode или массив, то функции GetNode и GetArray вернут пустые элементы. Проверить наличие именованного значения внутри узла позволяет метод IsExists

Создайте и настройте новый модуль для подключения к программному комплексу Топоматик Робур.

С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки:

Создайте новый класс 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 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()
            {
                //Здесь нам ничего не нужно делать
            }
        }
    }
}

В нашем случае редактор модели получился достаточно простым, поскольку наша модель не реагирует на команду открыть и фактически вся работа с ней будет происходить в отдельной команде для редактирования модели. Дополнительно необходимо реализовать диалог для редактирования данных модели. Его реализацию можно увидеть в исходных кодах примера.

В теле программного модуля необходимо объявить функции для создания и редактирования модели, функцию для создания редактора и регистрации модели.

    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».

В результате мы получим возможность добавлять в проект наши тестовые модели и редактировать их содержимое по команде «Редактировать» в контекстном меню модели.

Исходный код примера, вы можете скачать используя эту ссылку

Архив с кодом примера

developers/tutorial/createmodel.txt · Последние изменения: 2021/07/22 14:29 (внешнее изменение)