Инструменты пользователя

Инструменты сайта


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

developers:tutorial:selectionset

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

За выделение объектов на видовом экране отвечает класс, наследник от абстрактного класса 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();
                    }
                }
            }
        }                
        ...

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

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