Боковая панель

Отображение на видовом экране

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

  • LayerGuid - должен возвращать уникальный идентификатор тип слоя. Этот идентификатор должен однозначно идентифицировать все экземпляры слоя данного типа.
  • Name - имя слоя
  • SelectionSet - возвращает экземпляр класса, наследника от SelectionSet, который отвечает за выделение и редактирование объектов
  • OnGetLimits - эта функция должна определить можно ли рассчитать общие границы слоя и вернуть их.
  • OnGetSnapObjects - используется в том случае, если к элементам модели необходима объектная привязка.
  • OnPaint - непосредственно реализует отображение модели

Для графических операций необходимо использовать экземпляр класса CadPen, который приходит в метод OnPaint в качестве параметра. Он реализует основные графические операции, такие как:

Все операции выполняются в системе координат модели. За текущий масштаб и другие трансформации отвечает видовой экран.

За вывод текста на экран отвечает отдельный класс FontManager. Получить его экземпляр можно используя синглтон FontManager.Current. Для отображения текста на экране необходимо получить требуемый шрифт у экземпляра класса FontManager и вызвать у него метод DrawString.

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

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

Реализуйте класс Model.cs, который описывает модель. В данном случае задача модели хранить список точек, введённых пользователем.

    //Класс реализующий структуру данных нашей модели
    //для поддержки интерфейса 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());
            }
        }
    }

Для отображения модели на видовом экране необходимо создать слой видового экрана. Поместите его в файл 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 который позволяет получить наш слой с видового экрана. Такой метод объявлен для большинства стандартных слоёв программного комплекса Топоматик Робур.

Поскольку наша модель предназначена для отображения на видовом экране плана, класс редактора необходимо наследовать от 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;
            }
        }
    }

Обратите внимание, что в случае реализации редактора модели в виде наследника от PlanModelEditor нет необходимости перекрывать функцию Open, но нужно реализовать функции работающие с вашим видовым слоем - CreatePlanLayer и RemovePlanLayer.

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

...
        [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;
                    }
                }
            }
        }
...

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

{
  ...
  "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"
      ]
    }
    ...
  }
  ...
}

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

Исходный код примера расположен в проекте «tutorial7».
developers/tutorial/addlayer.txt · Последние изменения: 2022/03/15 19:14 — proxor