Здесь показаны различия между двумя версиями данной страницы.
Предыдущая версия справа и слева Предыдущая версия Следующая версия | Предыдущая версия | ||
developers:tutorial:selectionset [2019/03/10 16:03] vasya |
developers:tutorial:selectionset [2022/03/15 19:15] (текущий) proxor |
||
---|---|---|---|
Строка 127: | Строка 127: | ||
Поскольку экземпляры этого класса в дальнейшем будут выделятся на слое, то все свойства мы декорируем необходимыми атрибутами и дополнительно перекрываем метод [[developers:references:system.object.tostring|ToString()]]. | Поскольку экземпляры этого класса в дальнейшем будут выделятся на слое, то все свойства мы декорируем необходимыми атрибутами и дополнительно перекрываем метод [[developers:references:system.object.tostring|ToString()]]. | ||
+ | Реализуем класс модели и поместим его в файл Model.cs: | ||
+ | <code csharp> | ||
+ | //Класс реализующий структуру данных нашей модели | ||
+ | //для поддержки интерфейса 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()); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Дополнительно создадим сервисный класс, реализующий отображение списка точек и поместим его в файл PointsDrawer.cs: | ||
+ | <code csharp> | ||
+ | //Сервисный класс для отрисовки точек | ||
+ | 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(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Реализуем нашего наследника от [[developers:references:topomatic.cad.view.selectionset|SelectionSet]] и разместим его в файле ModelLayerSelectionSet.cs: | ||
+ | |||
+ | <code csharp> | ||
+ | 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); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | В реализацию слоя модели, расположенного в файле ModelLayer.cs, необходимо внести следующие изменения. | ||
+ | На конструкторе создаем экземпляра класса, наследника от [[developers:references:topomatic.cad.view.selectionset|SelectionSet]], меняем реализации функций **OnPaint** и **OnGetLimits**. Также перекрываем функции **OnHilightObject** и **OnDynamicDraw** - для реализации подсветки и отображения выделенных элементов. | ||
+ | |||
+ | <code csharp> | ||
+ | ... | ||
+ | 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(); | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | ... | ||
+ | </code> | ||
+ | |||
+ | В результате мы получим возможность по команде «Редактировать активную модель» добавлять новые полилинии с точками, а также выделять и подсвечивать их на видовом экране. | ||
+ | |||
+ | {{ :developers:tutorial:selectionset:selectionset_result.png?direct&600 |}} | ||
+ | |||
+ | <note>[[developers:tutorial:tutorialcode|Исходный код]] примера расположен в проекте **"tutorial8"**.</note> |