Здесь показаны различия между двумя версиями данной страницы.
Следующая версия | Предыдущая версия | ||
developers:tutorial:addlayer [2019/02/16 16:31] vasya создано |
developers:tutorial:addlayer [2022/03/15 19:14] (текущий) proxor |
||
---|---|---|---|
Строка 1: | Строка 1: | ||
- | Поведение модели может быть реализовано одним из следующих способов: | + | ====== Отображение на видовом экране ====== |
- | * При открытии модели открывается программа предназначенная для работы с файлами данного типа (например файлы ведомостей, текстовые файлы и т.п.) | + | |
- | * При открытии модели открывается отдельное окно для редактирования (например файлы чертежа) | + | Для графического отображения моделей на экране в программном комплексе используется видовой экран, реализованный с помощью элемента [[developers:references:topomatic.cad.view.cadview|CadView]]. Видовой экран служит для отображения элементов модели на экране, масштабирования и поворота изображения, а также поддерживает операции редактирования модели. Для реализации графического вывода каждой отдельной модели используются слои видового экрана - наследники от [[developers:references:topomatic.cad.view.cadviewlayer|CadViewLayer]]. |
- | * При открытии модель отображается в окне плана (например файлы поверхности, подобъектов и т.п.) | + | При создании наследника от [[developers:references:topomatic.cad.view.cadviewlayer|CadViewLayer]] необходимо реализовать следующие свойства и методы: |
+ | * [[developers:references:topomatic.cad.view.cadviewlayer.layerguid|LayerGuid]] - должен возвращать уникальный идентификатор тип слоя. Этот идентификатор должен однозначно идентифицировать все экземпляры слоя данного типа. | ||
+ | * [[developers:references:topomatic.cad.view.cadviewlayer.name|Name]] - имя слоя | ||
+ | * [[developers:references:topomatic.cad.view.cadviewlayer.selectionset|SelectionSet]] - возвращает экземпляр класса, наследника от [[developers:references:topomatic.cad.view.selectionset|SelectionSet]], который отвечает за выделение и редактирование объектов | ||
+ | * **OnGetLimits** - эта функция должна определить можно ли рассчитать общие границы слоя и вернуть их. | ||
+ | * **OnGetSnapObjects** - используется в том случае, если к элементам модели необходима объектная привязка. | ||
+ | * **OnPaint** - непосредственно реализует отображение модели | ||
+ | |||
+ | Для графических операций необходимо использовать экземпляр класса [[developers:references:topomatic.cad.foundation.cadpen|CadPen]], который приходит в метод **OnPaint** в качестве параметра. Он реализует основные графические операции, такие как: | ||
+ | * [[developers:references:topomatic.cad.foundation.cadpen.drawline_topomatic.cad.foundation.vector2d_topomatic.cad.foundation.vector2d|DrawLine]] - нарисовать линию | ||
+ | * [[developers:references:topomatic.cad.foundation.cadpen.drawpoint_topomatic.cad.foundation.vector2d|DrawPoint]] - нарисовать точку | ||
+ | * [[developers:references:topomatic.cad.foundation.cadpen.vertexcircle_topomatic.cad.foundation.vector2d_system.double|VertexCircle]] - нарисовать круг | ||
+ | * [[developers:references:topomatic.cad.foundation.cadpen.vertexarc_topomatic.cad.foundation.vector2d_system.double_system.double_system.double|VertexArc]] - нарисовать арку | ||
+ | * [[developers:references:ba26a4f6fed24b8edc05d90315f5a5e8|VertexEllipse]] - нарисовать эллипс | ||
+ | |||
+ | Все операции выполняются в системе координат модели. За текущий масштаб и другие трансформации отвечает видовой экран. | ||
+ | |||
+ | За вывод текста на экран отвечает отдельный класс [[developers:references:topomatic.cad.foundation.fontmanager|FontManager]]. Получить его экземпляр можно используя синглтон [[developers:references:topomatic.cad.foundation.fontmanager.current|FontManager.Current]]. Для отображения текста на экране необходимо получить требуемый шрифт у экземпляра класса [[developers:references:topomatic.cad.foundation.fontmanager|FontManager]] и вызвать у него метод [[developers:references:0d4287aafc4aea88565fe81cee5a2730|DrawString]]. | ||
+ | |||
+ | Создайте новую [[developers:tutorial:createmodel|модель]] и подключите её к программному комплексу [[http://www.topomatic.ru|Топоматик Робур]]. | ||
+ | |||
+ | |||
+ | С помощью диалогового окна Менеджер ссылок добавьте ссылки на следующие библиотеки: | ||
+ | |||
+ | * [[developers:references:topomatic.cad.foundation|Topomatic.Cad.Foundation.dll]] - базовые математические типы и операции | ||
+ | * [[developers:references:topomatic.controls|Topomatic.Controls.dll]] - базовые диалоги и таблицы | ||
+ | * [[developers:references:topomatic.cad.view|Topomatic.Cad.View.dll]] - видовой экран | ||
+ | |||
+ | Реализуйте класс Model.cs, который описывает модель. В данном случае задача модели хранить список точек, введённых пользователем. | ||
+ | <code csharp> | ||
+ | //Класс реализующий структуру данных нашей модели | ||
+ | //для поддержки интерфейса IStateController наследуем от StateControllerObject | ||
+ | class Model : StateControllerObject, IStgSerializable | ||
+ | { | ||
+ | private bool m_ReadOnly = false; | ||
+ | |||
+ | public List<Vector2D> Points = new List<Vector2D>(); | ||
+ | |||
+ | //флаг только для чтения | ||
+ | public override bool ReadOnly | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | return m_ReadOnly; | ||
+ | } | ||
+ | |||
+ | set | ||
+ | { | ||
+ | m_ReadOnly = value; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //Загрузка из узла | ||
+ | public void LoadFromStg(StgNode node) | ||
+ | { | ||
+ | Points.Clear(); | ||
+ | //При загрузке массива указывается тип составляющих массив значений | ||
+ | var array = node.GetArray("Points", StgType.Node); | ||
+ | for (int i = 0; i < array.Count; i++) | ||
+ | { | ||
+ | Points.Add(Vector2D.LoadFromStg(array.GetNode(i))); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //Сохранение в узел | ||
+ | public void SaveToStg(StgNode node) | ||
+ | { | ||
+ | //Сохраняем значения в узел | ||
+ | //Сохраняем массив с указанием типа значений | ||
+ | var array = node.AddArray("Points", StgType.Node); | ||
+ | for (int i = 0; i < Points.Count; i++) | ||
+ | { | ||
+ | Points[i].SaveToStg(array.AddNode()); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Для отображения модели на видовом экране необходимо создать слой видового экрана. Поместите его в файл ModelLayer.cs. | ||
+ | <code csharp> | ||
+ | class ModelLayer : CadViewLayer | ||
+ | { | ||
+ | |||
+ | //для удобства определяем статический метод, позволяющий получить наш слой с видового экрана | ||
+ | public static ModelLayer GetModelLayer(CadView cadView) | ||
+ | { | ||
+ | //сначала проверяем, есть ли наш слой сразу в самом видовом экране | ||
+ | //это возможно, если видовой экран создан отдельно и слой расположен прямо на видовом экране | ||
+ | var layer = cadView[ModelLayer.ID] as ModelLayer; | ||
+ | if (layer == null) | ||
+ | { | ||
+ | //теперь проверяем не находится ли слой в составе нескольких слоёв модели | ||
+ | //это наиболее распространённая ситуация | ||
+ | //для этого мы получаем слой, который содержит внутри все слои всех моделей | ||
+ | var multi = cadView[Consts.ModelsLayer] as MultiLayer; | ||
+ | if (multi != null) | ||
+ | { | ||
+ | //после этого получаем текущий слой активной модели | ||
+ | var active = multi.ResolveActive(); | ||
+ | //проверяем его на соответствие нашему слою | ||
+ | layer = active as ModelLayer; | ||
+ | if (layer == null) | ||
+ | { | ||
+ | //кроме того возможен вариант, что у нашей модели несколько слоев | ||
+ | //в этом случае они объединяются внутри общего слоя модели, который и будет являться активным | ||
+ | var compound = active as CompoundLayer; | ||
+ | if (compound != null) | ||
+ | { | ||
+ | layer = compound[ModelLayer.ID] as ModelLayer; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | //если наш слой найден, но он заблокирован на редактирование, то мы не можем его вернуть | ||
+ | if ((layer != null) && (!layer.ResolveEnable())) | ||
+ | { | ||
+ | return null; | ||
+ | } | ||
+ | return layer; | ||
+ | } | ||
+ | |||
+ | //наша модель | ||
+ | private Model m_Model; | ||
+ | |||
+ | //класс, отвечающий за выделение объектов | ||
+ | private SelectionSet m_SelectionSet; | ||
+ | |||
+ | public ModelLayer() | ||
+ | { | ||
+ | //в качестве класса, отвечающего за выделение объектов мы используем заглушку которая реализует его по умолчанию, в этом случае он не выделяет ничего | ||
+ | m_SelectionSet = new DefaultSelectionSet(this); | ||
+ | } | ||
+ | |||
+ | //Guid нашего слоя | ||
+ | public static readonly Guid ID = new Guid("{36C745EB-2111-4D44-B4A1-9BE0B7DBD730}"); | ||
+ | |||
+ | //Возвращаем в качестве LayerId ID объявленный выше | ||
+ | public override Guid LayerGuid | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | return ID; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //Возвращаем нашу заглушку | ||
+ | public override SelectionSet SelectionSet | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | return m_SelectionSet; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public override string Name | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | return "Слой тестовой модели"; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | public Model Model | ||
+ | { | ||
+ | get | ||
+ | { | ||
+ | return m_Model; | ||
+ | } | ||
+ | set | ||
+ | { | ||
+ | m_Model = value; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | //Рассчитываем границы слоя | ||
+ | protected override bool OnGetLimits(out BoundingBox2D limits) | ||
+ | { | ||
+ | //Если есть точки в модели | ||
+ | if (m_Model.Points.Count > 0) | ||
+ | { | ||
+ | //Создаем рамку вокруг первой точки | ||
+ | limits = new BoundingBox2D(m_Model.Points[0], m_Model.Points[0]); | ||
+ | for (int i = 1; i < m_Model.Points.Count; i++) | ||
+ | { | ||
+ | //и добавляем в нее все остальные точки | ||
+ | limits.AddPoint(m_Model.Points[i]); | ||
+ | } | ||
+ | return true; | ||
+ | } | ||
+ | //если точек в модели нет, возвращаем пустую рамку | ||
+ | limits = BoundingBox2D.Empty; | ||
+ | return false; | ||
+ | } | ||
+ | |||
+ | protected override void OnGetSnapObjects(ObjectSnapEventArgs e) | ||
+ | { | ||
+ | //Поскольку мы не реализуем привязки, то здесь мы не делаем ничего | ||
+ | } | ||
+ | |||
+ | protected override void OnPaint(CadPen pen) | ||
+ | { | ||
+ | //рисуем нашу линию жёлтым цветом | ||
+ | pen.Color = Color.Yellow; | ||
+ | //Начинаем рисовать | ||
+ | pen.BeginDraw(); | ||
+ | try | ||
+ | { | ||
+ | //для отрисовки используем возможность нарисовать массив нескольких точек | ||
+ | //Для этого вызываем начало отрисовки массива | ||
+ | pen.BeginArray(); | ||
+ | for (int i = 0; i < m_Model.Points.Count; i++) | ||
+ | { | ||
+ | //Добавляем точки | ||
+ | pen.Vertex(m_Model.Points[i]); | ||
+ | } | ||
+ | //Заканчиваем отрисовку массива, в виде линии | ||
+ | pen.EndArray(ArrayMode.Polyline); | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | //заканчиваем рисовать | ||
+ | pen.EndDraw(); | ||
+ | } | ||
+ | //в каждой точке пишем номер оранжевым цветом | ||
+ | pen.Color = Color.Orange; | ||
+ | //начинаем рисовать | ||
+ | pen.BeginDraw(); | ||
+ | try | ||
+ | { | ||
+ | var font = FontManager.Current.DefaultFont; | ||
+ | for (int i = 0; i < m_Model.Points.Count; i++) | ||
+ | { | ||
+ | //пишем номер точки, высотой в 2 еденицы чертежа | ||
+ | font.DrawString(i.ToString(), pen, m_Model.Points[i], 0.0, 2.0); | ||
+ | } | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | //заканчиваем рисовать | ||
+ | pen.EndDraw(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | <note> | ||
+ | Мы реализовали у слоя статический метод **ModelLayer.GetModelLayer** который позволяет получить наш слой с видового экрана. Такой метод объявлен для большинства стандартных слоёв программного комплекса [[http://www.topomatic.ru|Топоматик Робур]]. | ||
+ | </note> | ||
+ | |||
+ | Поскольку наша модель предназначена для отображения на видовом экране плана, класс редактора необходимо наследовать от [[developers:references:topomatic.applicationplatform.core.planmodeleditor|PlanModelEditor]]. Реализуйте редактор модели в файле Editor.cs. | ||
+ | |||
+ | <code csharp> | ||
+ | //Класс реализующий редактор нашей модели | ||
+ | class Editor : PlanModelEditor | ||
+ | { | ||
+ | //Реализация загрузки модели по указанному пути, должна вернуть реализацию класса нашей модели | ||
+ | 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); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | protected override CadViewLayer CreatePlanLayer(IProjectModel model) | ||
+ | { | ||
+ | var m = model.LockRead() as Model; | ||
+ | if (m != null) | ||
+ | { | ||
+ | return new ModelLayer() { Model = m }; | ||
+ | } | ||
+ | return null; | ||
+ | } | ||
+ | |||
+ | protected override void ReloadModel(IProjectModel model, EditorResult editorResult) | ||
+ | { | ||
+ | var m = model.LockRead() as Model; | ||
+ | if (m != null) | ||
+ | { | ||
+ | var layer = (ModelLayer)editorResult.PlanLayer; | ||
+ | layer.Model = m; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | protected override void RemovePlanLayer(IProjectModel model, CadViewLayer layer) | ||
+ | { | ||
+ | var model_layer = layer as ModelLayer; | ||
+ | if (model_layer != null) | ||
+ | { | ||
+ | model_layer.Model = null; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Обратите внимание, что в случае реализации редактора модели в виде наследника от [[developers:references:topomatic.applicationplatform.core.planmodeleditor|PlanModelEditor]] нет необходимости перекрывать функцию **Open**, но нужно реализовать функции работающие с вашим видовым слоем - **CreatePlanLayer** и **RemovePlanLayer**. | ||
+ | |||
+ | В теле программного модуля, кроме [[developers:tutorial:createmodel|функций для создания и регистрации модели и редактора]], необходимо реализовать функцию для редактирования модели. В нашем случае она будет запрашивать у пользователя [[developers:tutorial:dynamicrender|ввод многоугольника из нескольких точек]] и сохранять их в нашу модель. | ||
+ | <code csharp> | ||
+ | ... | ||
+ | [cmd("edit_pointsmodel")] | ||
+ | public void EditModel() | ||
+ | { | ||
+ | var cadView = CadView; | ||
+ | if (cadView != null) | ||
+ | { | ||
+ | var model_layer = ModelLayer.GetModelLayer(cadView); | ||
+ | if (model_layer != null) | ||
+ | { | ||
+ | //список выбранных точек | ||
+ | var positions = new List<Vector2D>(); | ||
+ | //делегат для динамической отрисовки | ||
+ | DrawCursorEvent dynamic_draw = delegate (CadPen pen, Vector3D vertex) | ||
+ | { | ||
+ | //если есть точки в списке, нужно их нарисовать и нарисовать линию | ||
+ | //от последней выбранной точки, до текущего положения курсора | ||
+ | if (positions.Count > 0) | ||
+ | { | ||
+ | pen.Color = Color.Lime; | ||
+ | pen.BeginDraw(); | ||
+ | try | ||
+ | { | ||
+ | for (int i = 1; i < positions.Count; i++) | ||
+ | { | ||
+ | pen.DrawLine(positions[i - 1], positions[i]); | ||
+ | } | ||
+ | pen.DrawLine(positions[positions.Count - 1], vertex.Pos); | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | pen.EndDraw(); | ||
+ | } | ||
+ | } | ||
+ | }; | ||
+ | //подписываемся на событие отрисовки | ||
+ | cadView.DynamicDraw += dynamic_draw; | ||
+ | try | ||
+ | { | ||
+ | Vector3D pos; | ||
+ | //просим пользователя указать несколько точек | ||
+ | while (CadCursors.GetPoint(cadView, out pos, "Укажите точку")) | ||
+ | { | ||
+ | positions.Add(pos.Pos); | ||
+ | } | ||
+ | //если точки заданы, то изменяем нашу модель | ||
+ | if (positions.Count > 0) | ||
+ | { | ||
+ | //получаем её со слоя | ||
+ | var model = model_layer.Model; | ||
+ | //очищаем точки | ||
+ | model.Points.Clear(); | ||
+ | //добавляем новые точки | ||
+ | model.Points.AddRange(positions); | ||
+ | //выставляем флаг модификации вручную | ||
+ | var p = PluginCoreOps.FindModel(model); | ||
+ | if (p != null) | ||
+ | p.Modified = true; | ||
+ | //обновляем видовой экран | ||
+ | cadView.Unlock(); | ||
+ | cadView.Invalidate(); | ||
+ | } | ||
+ | } | ||
+ | finally | ||
+ | { | ||
+ | //отписываемся от события отрисовки | ||
+ | cadView.DynamicDraw -= dynamic_draw; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | ... | ||
+ | </code> | ||
+ | |||
+ | В файле .plugin мы [[developers:tutorial:createmodel|описываем и подключаем нашу модель]]. Кроме того добавляем дополнительную команду и пункт меню, предназначенный для редактирования модели. А в контекстном меню модели поддерживаем функционал для её активации. | ||
+ | |||
+ | <code javascript> | ||
+ | { | ||
+ | ... | ||
+ | "actions": { | ||
+ | ... | ||
+ | "id_edit_pointsmodel": { | ||
+ | "cmd": "edit_pointsmodel", | ||
+ | "title": "Редактировать активную модель" | ||
+ | } | ||
+ | ... | ||
+ | }, | ||
+ | "contexts": { | ||
+ | ... | ||
+ | "pointsmodel.context": { | ||
+ | "priority": 1001, | ||
+ | "items": [ | ||
+ | { "default": "core.id_activate \"%0\"" }, | ||
+ | "$(if,$(opened,%0),core.id_close \"%0\" \"Скрыть модель\",core.id_open \"%0\" \"Показать модель\")", | ||
+ | "core.id_rmitem \"%0\"", | ||
+ | "core.id_mvitem \"%0\"", | ||
+ | "core.id_dublicate \"%0\"", | ||
+ | "-", | ||
+ | "core.id_rmitem \"%0\"" | ||
+ | ] | ||
+ | } | ||
+ | ... | ||
+ | }, | ||
+ | "menubars": { | ||
+ | ... | ||
+ | "rbproj.test_menu": { | ||
+ | "items": [ | ||
+ | "id_edit_pointsmodel" | ||
+ | ] | ||
+ | } | ||
+ | ... | ||
+ | } | ||
+ | ... | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | В результате мы получим возможность добавлять в проект наши тестовые модели и редактировать их содержимое по команде "Редактировать активную модель" в контекстном меню модели. После изменения наша модель будет отображаться на видовом экране плана. | ||
+ | {{ :developers:tutorial:addlayer:result.png?direct&600 |}} | ||
+ | |||
+ | <note>[[developers:tutorial:tutorialcode|Исходный код]] примера расположен в проекте **"tutorial7"**.</note> | ||