====== Отображение на видовом экране ====== Для графического отображения моделей на экране в программном комплексе используется видовой экран, реализованный с помощью элемента [[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, который описывает модель. В данном случае задача модели хранить список точек, введённых пользователем. //Класс реализующий структуру данных нашей модели //для поддержки интерфейса IStateController наследуем от StateControllerObject class Model : StateControllerObject, IStgSerializable { private bool m_ReadOnly = false; public List Points = new List(); //флаг только для чтения 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()); } } } Для отображения модели на видовом экране необходимо создать слой видового экрана. Поместите его в файл ModelLayer.cs. 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(); } } } Мы реализовали у слоя статический метод **ModelLayer.GetModelLayer** который позволяет получить наш слой с видового экрана. Такой метод объявлен для большинства стандартных слоёв программного комплекса [[http://www.topomatic.ru|Топоматик Робур]]. Поскольку наша модель предназначена для отображения на видовом экране плана, класс редактора необходимо наследовать от [[developers:references:topomatic.applicationplatform.core.planmodeleditor|PlanModelEditor]]. Реализуйте редактор модели в файле Editor.cs. //Класс реализующий редактор нашей модели 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; } } } Обратите внимание, что в случае реализации редактора модели в виде наследника от [[developers:references:topomatic.applicationplatform.core.planmodeleditor|PlanModelEditor]] нет необходимости перекрывать функцию **Open**, но нужно реализовать функции работающие с вашим видовым слоем - **CreatePlanLayer** и **RemovePlanLayer**. В теле программного модуля, кроме [[developers:tutorial:createmodel|функций для создания и регистрации модели и редактора]], необходимо реализовать функцию для редактирования модели. В нашем случае она будет запрашивать у пользователя [[developers:tutorial:dynamicrender|ввод многоугольника из нескольких точек]] и сохранять их в нашу модель. ... [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(); //делегат для динамической отрисовки 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; } } } } ... В файле .plugin мы [[developers:tutorial:createmodel|описываем и подключаем нашу модель]]. Кроме того добавляем дополнительную команду и пункт меню, предназначенный для редактирования модели. А в контекстном меню модели поддерживаем функционал для её активации. { ... "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" ] } ... } ... } В результате мы получим возможность добавлять в проект наши тестовые модели и редактировать их содержимое по команде "Редактировать активную модель" в контекстном меню модели. После изменения наша модель будет отображаться на видовом экране плана. {{ :developers:tutorial:addlayer:result.png?direct&600 |}} [[developers:tutorial:tutorialcode|Исходный код]] примера расположен в проекте **"tutorial7"**.