====== Выделение элементов ====== За выделение объектов на видовом экране отвечает класс, наследник от абстрактного класса [[developers:references:topomatic.cad.view.selectionset|SelectionSet]]. Он за возвращение списка всех выделяемых элементов, выделение элементов и их хранение, а также за копирование элементов через буфер обмена и их перемещение, если это необходимо. Каждый [[[[developers:references:topomatic.cad.view.cadviewlayer|слой]] видового экрана при реализации возвращает свой экземпляр [[developers:references:topomatic.cad.view.selectionset|SelectionSet]]. Если [[[[developers:references:topomatic.cad.view.cadviewlayer|слой]] не поддерживает выделение элементов, то в качестве [[developers:references:topomatic.cad.view.selectionset|SelectionSet]] можно вернуть экземпляр [[developers:references:topomatic.cad.view.defaultselectionset|DefaultSelectionSet]]. Это реализация по умолчанию, не позволяющая выделить элементы. Для реализации наследника от [[developers:references:topomatic.cad.view.selectionset|SelectionSet]] необходимо реализовать следующие функции и методы: * [[developers:references:topomatic.cad.view.selectionset.select_system.collections.ienumerable_system.boolean|Select]] - предназначен для выделения или снятия выделения с объекта. * [[developers:references:topomatic.cad.view.selectionset.clear|Clear]] - снимает выделение со всех выделенных объектов * [[developers:references:topomatic.cad.view.selectionset.erase|Erase]] - удаляет все выделенные объекты. * [[developers:references:topomatic.cad.view.selectionset.getobjectsatpoint_topomatic.cad.foundation.vector3d_system.predicate_1_system.int32|GetObjectsAtPoint]] - выбор объектов в указанной точке и расстояние до них. * [[developers:references:194476d8735a41d451648829001e3a70|GetObjectsByFrame]] - выбор объектов рамкой * [[developers:references:f6f3e815a6f715eef33c195c169a8798|GetObjectsByPolygon]] - выбор объектов полигоном. В данный момент метод не поддерживается. * [[developers:references:topomatic.cad.view.selectionset.getenumerator|GetEnumerator]] - перечисление выделенных объектов. * [[developers:references:topomatic.cad.view.selectionset.getselectable|GetSelectable]] - перечисление всех объектов которые можно выделить. * [[developers:references:topomatic.cad.view.selectionset.count|Count]] - количество выделенных элементов * [[developers:references:topomatic.cad.view.selectionset.isenable_system.object|IsEnable]] - проверяет включен ли объект на этом [[developers:references:topomatic.cad.view.selectionset|SelectionSet]]. * [[developers:references:topomatic.cad.view.selectionset.isowned_system.object|IsOwned]] - проверяет принадлежит ли объект выделяемым объектам этого [[developers:references:topomatic.cad.view.selectionset|SelectionSet]]. * [[developers:references:topomatic.cad.view.selectionset.isselected_system.object|IsSelected]] - проверяет выделен ли объект на этом [[developers:references:topomatic.cad.view.selectionset|SelectionSet]]. В функцию [[developers:references:topomatic.cad.view.selectionset.getobjectsatpoint_topomatic.cad.foundation.vector3d_system.predicate_1_system.int32|GetObjectsAtPoint]] в качестве параметра приходит временной интервал, в течении которого вы должны завершить выполнение функции. Если интервал равен 0 то время на выполнение функции не ограничено. Свойства объектов, которые будут выделены с помощью [[developers:references:topomatic.cad.view.selectionset|SelectionSet]] будут отображены на видовом экране. А в качестве типа объекта будет выведено значение функции [[developers:references:system.object.tostring|ToString()]]. Для того чтобы управлять названием и положением свойства в инспекторе объектов можно использовать атрибуты [DisplayName] и [Category]. Для скрытия свойства доступен атрибут [Browsable] Более подробно работа со свойствами описана в разделе [[developers:tutorial:dlgandpropertygrid|Таблицы и диалоги]]. Для реализации методов для выбора объектов, таких как [[developers:references:topomatic.cad.view.selectionset.getobjectsatpoint_topomatic.cad.foundation.vector3d_system.predicate_1_system.int32|GetObjectsAtPoint]] и сложной геометрии объектов можно использовать специальный класс - [[developers:references:topomatic.cad.view.nulldevicecontext|NullDeviceContext]]. Он позволяет определить принадлежит ли заданная точка объекту или рамке, нарисовав сам объект в памяти. При реализации метода [[developers:references:194476d8735a41d451648829001e3a70|GetObjectsByFrame]] необходимо учитывать тип рамки, и в зависимости от этого пользоваться либо [[developers:references:topomatic.cad.view.nulldevicecontext|NullDeviceContext]] либо [[developers:references:topomatic.cad.view.fulldevicecontext|FullDeviceContext]]. Создайте новую [[developers:tutorial:addlayer|модель и видовой слой]] и подключите к программному комплексу [[http://www.topomatic.ru|Топоматик Робур]]. Изменим модель таким образом, чтобы она хранила внутри несколько списков точек. Для этого создадим класс Points.cs описывающий список точек: //Класс для хранения точек в нашей модели //поддерживаем интерфесы IStgSerializable для сохранения и IOwned для определения владельца списка точек class Points : IStgSerializable, IOwned { private Model m_Owner; private List m_Points = new List(); 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 "Точки модели"; } } Поскольку экземпляры этого класса в дальнейшем будут выделятся на слое, то все свойства мы декорируем необходимыми атрибутами и дополнительно перекрываем метод [[developers:references:system.object.tostring|ToString()]]. Реализуем класс модели и поместим его в файл Model.cs: //Класс реализующий структуру данных нашей модели //для поддержки интерфейса IStateController наследуем от StateControllerObject class Model : StateControllerObject, IStgSerializable { private bool m_ReadOnly = false; private List m_Points = new List(); //Метод для добавления списка точек в модель 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(); } } } Реализуем нашего наследника от [[developers:references:topomatic.cad.view.selectionset|SelectionSet]] и разместим его в файле ModelLayerSelectionSet.cs: class ModelLayerSelectionSet : SelectionSet { //Список выделенных объектов private List m_Selected = new List(); 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> GetObjectsAtPoint(Vector3D point, Predicate match, int waitTimeOut) { //Создаем список объектов var list = new List>(); 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(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 match, Action 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 pointsList, Predicate match, Action 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, необходимо внести следующие изменения. На конструкторе создаем экземпляра класса, наследника от [[developers:references:topomatic.cad.view.selectionset|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(); } } } } ... В результате мы получим возможность по команде «Редактировать активную модель» добавлять новые полилинии с точками, а также выделять и подсвечивать их на видовом экране. {{ :developers:tutorial:selectionset:selectionset_result.png?direct&600 |}} [[developers:tutorial:tutorialcode|Исходный код]] примера расположен в проекте **"tutorial8"**.