Выделение элементов

За выделение объектов на видовом экране отвечает класс, наследник от абстрактного класса SelectionSet. Он за возвращение списка всех выделяемых элементов, выделение элементов и их хранение, а также за копирование элементов через буфер обмена и их перемещение, если это необходимо. Каждый слой видового экрана при реализации возвращает свой экземпляр SelectionSet.

Если слой не поддерживает выделение элементов, то в качестве SelectionSet можно вернуть экземпляр DefaultSelectionSet. Это реализация по умолчанию, не позволяющая выделить элементы.

Для реализации наследника от SelectionSet необходимо реализовать следующие функции и методы:

Select - предназначен для выделения или снятия выделения с объекта.

Clear - снимает выделение со всех выделенных объектов

Erase - удаляет все выделенные объекты.

GetObjectsAtPoint - выбор объектов в указанной точке и расстояние до них.

GetObjectsByFrame - выбор объектов рамкой

GetObjectsByPolygon - выбор объектов полигоном. В данный момент метод не поддерживается.

GetEnumerator - перечисление выделенных объектов.

GetSelectable - перечисление всех объектов которые можно выделить.

Count - количество выделенных элементов

IsEnable - проверяет включен ли объект на этом SelectionSet.

IsOwned - проверяет принадлежит ли объект выделяемым объектам этого SelectionSet.

IsSelected - проверяет выделен ли объект на этом SelectionSet.

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

Свойства объектов, которые будут выделены с помощью SelectionSet будут отображены на видовом экране. А в качестве типа объекта будет выведено значение функции ToString().

Для того чтобы управлять названием и положением свойства в инспекторе объектов можно использовать атрибуты [DisplayName] и [Category]. Для скрытия свойства доступен атрибут [Browsable]

Более подробно работа со свойствами описана в разделе Таблицы и диалоги.

Для реализации методов для выбора объектов, таких как GetObjectsAtPoint и сложной геометрии объектов можно использовать специальный класс - NullDeviceContext. Он позволяет определить принадлежит ли заданная точка объекту или рамке, нарисовав сам объект в памяти.

При реализации метода GetObjectsByFrame необходимо учитывать тип рамки, и в зависимости от этого пользоваться либо NullDeviceContext либо FullDeviceContext.

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

Изменим модель таким образом, чтобы она хранила внутри несколько списков точек. Для этого создадим класс Points.cs описывающий список точек:

//Класс для хранения точек в нашей модели //поддерживаем интерфесы IStgSerializable для сохранения и IOwned для определения владельца списка точек class Points : IStgSerializable, IOwned { private Model m_Owner; private List<Vector2D> m_Points = new List<Vector2D>(); public Points(Model owner) { m_Owner = owner; } //Метод для добавления точки в список public void AddPoint(Vector2D point) { m_Points.Add(point); } //Метод для получения точки из списка public Vector2D this[int index] { get { return m_Points[index]; } } //Количество точек в списке //Свойство декорировано атрибутом DisplayName для отображения в инспекторе объектов [DisplayName("Количество точек")] public int Count { get { return m_Points.Count; } } //Владелец нашего списка точек, у нас это наша модель //Свойство декорировано атрибутом Browsable чтобы исключить отображение в инспекторе объектов [Browsable(false)] public object Owner { get { return m_Owner; } set { //Владелец назначается один раз на конструкторе объекта, назначение отдельно недопустимо throw new NotSupportedException(); } } //Реализация загрузки public void LoadFromStg(StgNode node) { m_Points.Clear(); //При загрузке массива указывается тип составляющих массив значений var array = node.GetArray("Values", StgType.Node); for (int i = 0; i < array.Count; i++) { m_Points.Add(Vector2D.LoadFromStg(array.GetNode(i))); } } //Реализация сохранения public void SaveToStg(StgNode node) { //Сохраняем значения в узел //Сохраняем массив с указанием типа значений var array = node.AddArray("Values", StgType.Node); for (int i = 0; i < m_Points.Count; i++) { m_Points[i].SaveToStg(array.AddNode()); } } //Дополнительно перекрываем метод ToString() для отображения типа объекта в инстпекторе объектов public override string ToString() { return "Точки модели"; } }

Поскольку экземпляры этого класса в дальнейшем будут выделятся на слое, то все свойства мы декорируем необходимыми атрибутами и дополнительно перекрываем метод ToString().

Реализуем класс модели и поместим его в файл Model.cs:

//Класс реализующий структуру данных нашей модели //для поддержки интерфейса IStateController наследуем от StateControllerObject class Model : StateControllerObject, IStgSerializable { private bool m_ReadOnly = false; private List<Points> m_Points = new List<Points>(); //Метод для добавления списка точек в модель public Points Add() { var p = new Points(this); m_Points.Add(p); return p; } //Метод для получения списка точек из модели public Points this[int index] { get { return m_Points[index]; } } //Метод для удаления списка точек из модели public bool Remove(Points points) { return m_Points.Remove(points); } //Количество списков в модели public int Count { get { return m_Points.Count; } } //флаг только для чтения public override bool ReadOnly { get { return m_ReadOnly; } set { m_ReadOnly = value; } } //Загрузка из узла public void LoadFromStg(StgNode node) { m_Points.Clear(); //При загрузке массива указывается тип составляющих массив значений var array = node.GetArray("Points", StgType.Node); for (int i = 0; i < array.Count; i++) { var p = new Points(this); p.LoadFromStg(array.GetNode(i)); m_Points.Add(p); } } //Сохранение в узел public void SaveToStg(StgNode node) { //Сохраняем значения в узел //Сохраняем массив с указанием типа значений var array = node.AddArray("Points", StgType.Node); for (int i = 0; i < m_Points.Count; i++) { m_Points[i].SaveToStg(array.AddNode()); } } }

Дополнительно создадим сервисный класс, реализующий отображение списка точек и поместим его в файл PointsDrawer.cs:

//Сервисный класс для отрисовки точек static class PointsDrawer { //Функция рисующая список точек при помощи pen public static void PaintPoints(CadPen pen, Points points, bool enabled) { //рисуем нашу линию жёлтым цветом pen.Color = Color.Yellow; //Начинаем рисовать pen.BeginDraw(); try { //для отрисовки используем возможность нарисовать массив нескольких точек //Для этого вызываем начало отрисовки массива pen.BeginArray(); for (int i = 0; i < points.Count; i++) { //Добавляем точки pen.Vertex(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 < points.Count; i++) { //пишем номер точки, высотой в 2 еденицы чертежа font.DrawString(i.ToString(), pen, points[i], 0.0, 2.0); } } finally { //заканчиваем рисовать pen.EndDraw(); } } }

Реализуем нашего наследника от SelectionSet и разместим его в файле ModelLayerSelectionSet.cs:

class ModelLayerSelectionSet : SelectionSet { //Список выделенных объектов private List<object> m_Selected = new List<object>(); public ModelLayerSelectionSet(ModelLayer layer) : base(layer) { } //Количество выделенных объектов public override int Count { get { return m_Selected.Count; } } //Метод очищает выделенные объекты public override void Clear() { m_Selected.Clear(); } //Метод удалет выделенные объекты из модели public override void Erase() { var model_layer = (ModelLayer)Layer; for (int i = 0; i < m_Selected.Count; i++) { model_layer.Model.Remove((Points)m_Selected[i]); } Clear(); } //Возвращаем выделенные объекты public override IEnumerator GetEnumerator() { return m_Selected.GetEnumerator(); } //Последний опрошенный элемент списка, необходим для работы GetObjectsAtPoint private int m_LastSearchIndex = 0; //Метод возвращает список объектов находящихся в выделенной точке и расстояние до них //В данном примере функция реализована с поддержкой таймаута public override IEnumerable<KeyValuePair<double, object>> GetObjectsAtPoint(Vector3D point, Predicate<object> match, int waitTimeOut) { //Создаем список объектов var list = new List<KeyValuePair<double, object>>(); var model_layer = (ModelLayer)Layer; //Проверяем видимый ли слой и есть ли у него модель if (model_layer.ResolveVisible() && (model_layer.Model != null)) { //Запоминаем время начала работы функции var t = Environment.TickCount; int result = -1; var model = model_layer.Model; //Формируем вокруг точки прямоугольник зависящий от текущего масштаба видового экрана var rect = Layer.CadView.MakeSearchRectangle(point); //Создаем контекст отрисовки, для проверки попадания объекта в рамку var dc = new NullDeviceContext(Layer.CadView, true); dc.BeginRender(); try { //Назначаем в качестве рамки полученный ранее прямоугольник dc.SetClipRect(rect); var count = model.Count; //Поиск начинаем не с начала списка, а с последнего опрошенного элемента var lastSearch = m_LastSearchIndex; for (int i = count - 1; i >= 0; i--) { var index = (i + lastSearch) % count; if (result == -1) { //Если пока элемены не найдены, то запоминаем текущий элемент списка в качестве последнего опрошенного m_LastSearchIndex = index; } var obj = model[index]; //Проверяем объект на соответствие нашему предикату if ((match == null) || (match(obj))) { //Рисуем точки PointsDrawer.PaintPoints(dc.Pen, obj, true); //Если нарисованные точки пересекают рамку или находятся в ней if (dc.Found) { //То запоминаем расстояние и текущий индекс элемента var distance = Math.Sqrt(dc.DistanceSquared); result = index; //В качестве последнего опрошенного ставим следующий элемент m_LastSearchIndex = index + 1; //Добавляем расстояние и объект в список list.Add(new KeyValuePair<double, object>(distance, obj)); } //Выставляем флаг обратно dc.Found = false; } //Если наш таймаут не 0 и время выполнения функции превышено - выходим из функции if ((waitTimeOut != 0) && (Environment.TickCount - t > waitTimeOut)) { break; } } } finally { dc.EndRender(); } } //Возвращаем список объектов return list; } //Метод возвращает список объектов внутри рамки выделения //Возможны два типа рамки: // Topomatic.Cad.View.Hints.FrameSelectType.Contains - объект должен целиком находится внтури рамки // Topomatic.Cad.View.Hints.FrameSelectType.Intersects - объект либо нахдится целиком внутри рамки, либо пересекает её public override void GetObjectsByFrame(FrameSelectType mode, RectangleD rect, Predicate<object> match, Action<object> action) { var model_layer = (ModelLayer)Layer; //Проверяем видимый ли слой и есть ли у него модель if (model_layer.ResolveVisible() && (model_layer.Model != null)) { var model = model_layer.Model; //Проверяем тип рамки if (mode == Topomatic.Cad.View.Hints.FrameSelectType.Contains) { //используем FullDeviceContext для проверки попадания объекта в рамку целиком var dc = new FullDeviceContext(Layer.CadView); dc.BeginRender(); try { //устанавливаем рамку dc.SetClipRect(rect); for (int i = 0; i < model.Count; i++) { var obj = model[i]; //Проверяем объект на соответствие нашему предикату if ((match == null) || (match(model))) { //Устанавливаем флаг полностью нарисован dc.FullDrawn = true; //Рисуем объект PointsDrawer.PaintPoints(dc.Pen, obj, true); //Если объект полностью нарисован и нарисован полностью if ((dc.FullDrawn) && (dc.Drawn)) { if (action != null) { //То выполняем метод для этого объекта action(obj); } } //Сбрасываем флаг отрисовки dc.Drawn = false; } } } finally { dc.EndRender(); } } if (mode == Topomatic.Cad.View.Hints.FrameSelectType.Intersects) { //используем NullDeviceContext для проверки попадания объекта в рамку или пересечения с ней var dc = new NullDeviceContext(Layer.CadView, false); dc.BeginRender(); try { //устанавливаем рамку dc.SetClipRect(rect); for (int i = 0; i < model.Count; i++) { var obj = model[i]; //Проверяем объект на соответствие нашему предикату if ((match == null) || (match(model))) { //Рисуем объект PointsDrawer.PaintPoints(dc.Pen, obj, true); //Если нарисованные точки пересекают рамку или находятся в ней if (dc.Found) { if (action != null) { //То выполняем метод для этого объекта action(obj); } } //Сбрасываем флаг отрисовки dc.Found = false; } } } finally { dc.EndRender(); } } } } //Метод зарезервирован для будущего использования и пока не поддерживается. Реализация не требуется. public override void GetObjectsByPolygon(FrameSelectType mode, List<Vector2D> pointsList, Predicate<object> match, Action<object> action) { throw new NotSupportedException(); } //Возвращаем список всех элементов, которые можно выделить public override IEnumerable GetSelectable() { var model_layer = (ModelLayer)Layer; for (int i = 0; i < model_layer.Model.Count; i++) { yield return model_layer.Model[i]; } } //Проверяем включен ли наш объект. В нашем случае всегда включен если включен слой public override bool IsEnable(object obj) { if (IsOwned(obj)) return true; return Layer.ResolveEnable(); } //Проверям принадлежит ли объект нашему SelectionSet public override bool IsOwned(object obj) { //Приводим объект к типу var points = obj as Points; if (points != null) { //И проверяем что модель равна модели нашего слоя return ((ModelLayer)Layer).Model == points.Owner; } return false; } //Проверяем, выделен ли объект public override bool IsSelected(object obj) { return m_Selected.Contains(obj); } //Выделяем или снимаем выделение с объекта public override void Select(object item, bool bFlag) { //Провеяем выделен ли наш объект var selected = IsSelected(item); if (bFlag) { //Если нужно выделить и он не выделен - выделяем if (!selected) m_Selected.Add(item); } else { //Если нужно убрать выделение и он выделен - убираем выделение if (selected) m_Selected.Remove(item); } } }

В реализацию слоя модели, расположенного в файле ModelLayer.cs, необходимо внести следующие изменения. На конструкторе создаем экземпляра класса, наследника от SelectionSet, меняем реализации функций OnPaint и OnGetLimits. Также перекрываем функции OnHilightObject и OnDynamicDraw - для реализации подсветки и отображения выделенных элементов.

... public ModelLayer() { //в качестве класса, отвечающего за выделение объектов мы используем ModelLayerSelectionSet m_SelectionSet = new ModelLayerSelectionSet(this); } ... protected override bool OnGetLimits(out BoundingBox2D limits) { bool init = false; limits = BoundingBox2D.Empty; for (int i = 0; i < m_Model.Count; i++) { var points = m_Model[i]; for (int j = 0; j < points.Count; j++) { var v = points[j]; if (init) { limits.AddPoint(v); } else { limits = new BoundingBox2D(v, v); init = true; } } } return init; } ... //Отрисовка модели protected override void OnPaint(CadPen pen) { for (int i = 0; i < m_Model.Count; i++) { PointsDrawer.PaintPoints(pen, m_Model[i], ResolveEnable()); } } ... //Отрисовка подсвеченных объектов protected override void OnHilightObject(CadPen pen, object obj) { base.OnHilightObject(pen, obj); //Если объект это наши точки var points = obj as Points; if (points != null) { //То включаем режим подсветки pen.DrawingMode = DrawingMode.Highlight; pen.HighlightMode = HighlightMode.DoubleBlack; //Рисуем точки PointsDrawer.PaintPoints(pen, points, ResolveEnable()); pen.Reset(); } } ... //Динамическая отрисовка слоя protected override void OnDynamicDraw(CadPen pen, Vector3D location) { base.OnDynamicDraw(pen, location); //Если есть выбранные элементы if (m_SelectionSet.Count > 0) { //Необходимо нарисовать все выбранные элементы foreach (var obj in m_SelectionSet) { var points = obj as Points; if (points != null) { //Рисуем элементы подсвеченными pen.HighlightMode = HighlightMode.Black; PointsDrawer.PaintPoints(pen, points, ResolveEnable()); pen.Reset(); } } } } ...

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

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