Помещение визуальных объектов в оболочку элемента

77

Определение визуального объекта — наиболее важный шаг в программировании визуального уровня, но его не достаточно для того, чтобы в действительности отобразить его содержимое на экране. Для отображения визуального объекта понадобится помощь полноценного элемента WPF, который добавит его в визуальное дерево. На первый взгляд это может показаться сокращением преимуществ программирования визуального уровня — в конце концов, мы же хотели избежать элементов с их накладными расходами? Однако единственный элемент может отображать неограниченное количество вложенных в него элементов.

Поэтому несложно создать окно, которое содержит в себе только один или два элемента, но при этом включает тысячи визуальных объектов. Ниже описаны шаги, необходимые для размещения визуальных объектов в элементе:

Переопределение VisualChildrenCount и GetVisualChild(), по сути, означает "заимствование" этого элемента. Если используется элемент управления содержимым, декоратор или панель, которая может содержать вложенные элементы, то эти элементы больше не будут визуализироваться. Например, переопределив эти два метода в пользовательском окне, вы не увидите остального содержимого этого окна. Вместо этого будут видны только добавленные визуальные объекты.

По этой причине принято создавать выделенный пользовательский класс, служащий оболочкой для визуальных объектов, которые необходимо отобразить. Например, рассмотрим окно, показанное на рисунке. Оно позволяет пользователю добавлять квадраты (каждый из которых является визуальным объектом) в специальный контейнер Canvas:

Рисование визуальных объектов

В левой части окна расположена панель инструментов с тремя объектами RadioButton. Элемент ToolBar изменяет способ отображения некоторых базовых элементов управления, таких как кнопки. Используя группу объектов RadioButton, можно создать набор взаимосвязанных кнопок. При щелчке на одной из них она выбирается и остается "нажатой", в то время как ранее выбранная кнопка возвращается к своему нормальному виду.

В правой части окна находится специализированный Canvas по имени DrawingCanvas, который хранит внутри себя коллекцию визуальных элементов. DrawingCanvas возвращает общее количество квадратов в свойстве VisualChildrenCount и использует метод GetVisualChild() для доступа к каждому визуальному объекту в коллекции. Вот как это реализовано:

public class DrawingCanvas : Panel
    {
        private List visuals = new List();

        protected override Visual GetVisualChild(int index)
        {
            return visuals[index];
        }
        protected override int VisualChildrenCount
        {
            get
            {
                return visuals.Count;
            }
        }

        public void AddVisual(Visual visual)
        {
            visuals.Add(visual);

            base.AddVisualChild(visual);
            base.AddLogicalChild(visual);
        }

        public void DeleteVisual(Visual visual)
        {
            visuals.Remove(visual);

            base.RemoveVisualChild(visual);
            base.RemoveLogicalChild(visual);            
        }

        public DrawingVisual GetVisual(Point point)
        {
            HitTestResult hitResult = VisualTreeHelper.HitTest(this, point);
            return hitResult.VisualHit as DrawingVisual;            
        }

        private List hits = new List();
        public List GetVisuals(Geometry region)
        {
            hits.Clear();
            GeometryHitTestParameters parameters = new GeometryHitTestParameters(region);
            HitTestResultCallback callback = new HitTestResultCallback(this.HitTestCallback);
            VisualTreeHelper.HitTest(this, null, callback, parameters);
            return hits;
        }

        private HitTestResultBehavior HitTestCallback(HitTestResult result)
        {
            GeometryHitTestResult geometryResult = (GeometryHitTestResult)result;
            DrawingVisual visual = result.VisualHit as DrawingVisual;
            if (visual != null &&
                geometryResult.IntersectionDetail == IntersectionDetail.FullyInside)
            {
                hits.Add(visual);
            }
            return HitTestResultBehavior.Continue;
        }

    }

DrawingCanvas не содержит логики рисования квадратов, их выбора и перемещения. Причина в том, что данная функциональность поддерживается на уровне приложений. Такой подход оправдан, потому что может существовать несколько инструментов рисования, и все они будут работать с одним контейнером DrawingCanvas. В зависимости от того, на какой кнопке щелкнул пользователь, он может получить возможность рисования фигур разного типа либо использовать разные линии для контуров и цвета заполнения. Все эти детали специфичны для окна; DrawingCanvas просто обеспечивает функциональность размещения ваших визуальных объектов, их визуализации и отслеживания.

Пройди тесты
Лучший чат для C# программистов