====== Отображение на видовом экране ======
Для графического отображения моделей на экране в программном комплексе используется видовой экран, реализованный с помощью элемента [[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"**.