Упражнение 9. Разработка собственного менеджера размещения MyPanel

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

Все стандартные панели WPF наследуют абстрактному базовому классу Panel и наш собственный менеджер тоже должен наследовать этот библиотечный класс. Класс Panel, в свою очередь, наследует от класса UIElement, поэтому наш класс расширения приобретет от своих предков все необходимое. Класс Panel является такой вершиной цепочки наследования, которая включает в себя все паттерны (образец, кирпичик, строительный блок, базовый функциональный объект), необходимые для разработки любых менеджеров размещения. В нашем случае в классе расширения нам останется только перекрыть пару виртуальных методов.

Та часть объектной модели UIElement, которая относится к менеджеру размещения, довольно проста. Методы Measure, MeasureCore, Arrange и ArrangeCore реализуют два этапа размещения, а свойство Visibility говорит, должен ли потомок отображаться и учитываться при размещении.

Свойство Visibility позволяет задать три способа участия дочернего элемента в размещении. По умолчанию оно равно Visible: элемент отображается и занимает место на экране. При значении Hidden элемент скрыт, но место на экране занимает. При Collapsed дочерний элемент не отображается и место на экране не занимает.

Механизм взаимодействия размещаемого дочернего элемента и менеджера размещения состоит из двух частей:

  • контракта, описывающего, как элемент принимает участие в размещении
  • набора реализаций этого контракта

Никакого встроенного размещения нет, все конкретные способы размещения построены по принципу расширения базовых классов.

Все элементы WPF, включая и менеджеры размещения, адаптируются к своему содержимому. Эта концепция применяется на всех уровнях построения пользовательского интерфейса: окна могут подгонять свой размер под находящиеся в них элементы, кнопки - подстраиваться под надписи на них, текстовые поля - изменять свой размер так, чтобы поместились все символы.

Менеджеры реализуют двухэтапное размещение своих дочерних элементов: измерение и установка. Вначале менеджер опрашивает каждого из своих дочерних элементов на предмет того, какой фактический размер тот предпочитает, чтобы полностью показать свое содержимое на экране. Это этап измерения. Затем менеджер соотносит эту информацию со своими настройками и возможностями, сколько он может вместить, и приступает к этапу установки.

В ходе установки родитель безусловно требует от каждого из своих дочерних элементов, чтобы тот установил определенный размер и положение. Эта модель позволяет родителю и потомку договариваться о месте на экране (месте под Солнцем). При этом фигурируют три размера:

  • Располагаемый размер ( available size ) - максимальная область, которую родитель готов первоначально предоставить потомку
  • Предпочтительный размер ( desired size ) - размер, который желал бы установить потомок
  • Фактический размер ( actual size ) - окончательный размер, который родитель назначает потомку

На размеры элемента могут налагаться ограничения MinWidth, MaxWidth, MinHeight и MaxHeight, внутри которых может выполнять адаптация под содержимое. Свойства Width и Height обычно не задаются, что разрешает автоматический выбор размера, но при явном их задании адаптация под размер отключается. Свойства ActualWidth и ActualHeight становятся определенными только после окончания двухэтапной адаптации и означают фактичекий размер элемента, установленный менеджером размещения.

  • Добавьте к решению новый проект WpfApp9 типа WPF Application и назначьте его стартовым
  • Заполните файл логики Window1.xaml.cs следующим кодом
using System;using System.Collections.Generic;using System.Text;using System.Windows;using System.Windows.Controls;using System.Windows.Data;using System.Windows.Documents;using System.Windows.Input;using System.Windows.Media;using System.Windows.Media.Imaging;using System.Windows.Navigation;using System.Windows.Shapes; namespace WpfApp9{ public partial class Window1 : Window { public Window1() { InitializeComponent(); } }} namespace WpfApp9{ class MyPanel : Panel { // Измерение // // На входе - сколько есть у родителя для менеджера // На выходе - сколько просит менеджер для себя protected override Size MeasureOverride(Size availableSize) { double maxChildWidth = 0.0; double maxChildHeight = 0.0; // Измерить требования каждого потомка и определить самые большие foreach (UIElement child in this.InternalChildren) { child.Measure(availableSize); maxChildWidth = Math.Max(child.DesiredSize.Width, maxChildWidth); maxChildHeight = Math.Max(child.DesiredSize.Height, maxChildHeight); } // Приблизительный несовершенный алгоритм // // Требуемая для размещения всех элементов длина окружности double idealCircumference = maxChildWidth * this.InternalChildren.Count; // Требуемый радиус окружности double idealRadius = (idealCircumference / (Math.PI * 2) + maxChildHeight); // Необходимые размеры описывающего окружность квадрата Size ideal = new Size(idealRadius * 2, idealRadius * 2); Size desired = ideal;// Менеджер столько хочет для себя // Если выделяемый менеджеру размер небесконечен if (!double.IsInfinity(availableSize.Width)) { // Если требуемого размера менеджеру нехватает if (availableSize.Width < desired.Width) { // Корректируем размер менеджера desired.Width = availableSize.Width; } } // То же самое и для высоты if (!double.IsInfinity(availableSize.Height)) { if (availableSize.Height < desired.Height) { desired.Height = availableSize.Height; } } return desired; } // Установка (заключение контракта) // // Делим отведенную для менеджера площадь protected override Size ArrangeOverride(Size finalSize) { // Размещаем квадратный менеджер в // центре выделенной родителем области Rect layoutRect; if (finalSize.Width > finalSize.Height) { layoutRect = new Rect( (finalSize.Width - finalSize.Height) / 2, 0, finalSize.Height, finalSize.Height); } else { layoutRect = new Rect( 0, (finalSize.Height - finalSize.Width) / 2, finalSize.Width, finalSize.Width); } double angleInc = 360.0 / this.InternalChildren.Count; double angle = 0; // Расставляем по кругу все дочерние элементы foreach (UIElement child in this.InternalChildren) { // Располагаем очередной элемент в верхней точке круга Point childLocation = new Point( layoutRect.Left + ((layoutRect.Width - child.DesiredSize.Width) / 2), layoutRect.Top); // Переместили по часовой стрелке и повернули вокруг оси child.RenderTransform = new RotateTransform( angle, child.DesiredSize.Width / 2, finalSize.Height / 2 - layoutRect.Top); // Утвердили окончательный размер и положение потомка child.Arrange(new Rect(childLocation, child.DesiredSize)); angle += angleInc; } return base.ArrangeOverride(finalSize); } }}

Вызов метода Arrange для дочернего элемента подтверждает заключение с ним контракта, а без этого элемент не буден виден на экране (попробуйте закомментировать).

  • Заполните файл разметки Window1.xaml следующим кодом XAML
<Window x:Class="WpfApp9.Window1" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Height="300" Width="300" Title="Менеджер размещения MyPanel" xmlns:MyMy="clr-namespace:WpfApp9" > <Border BorderThickness="3" CornerRadius="0" BorderBrush="Red" Padding="10" Background="Violet" > <MyMy:MyPanel> <Button>Первый</Button> <Button>Второй</Button> <Button>Третий</Button> <Button>Четвертый</Button> <Button>Пятый</Button> <Button>Шестой</Button> <Button>Седьмой</Button> </MyMy:MyPanel> </Border></Window>

Обратите внимание, что для видимости нашего класса MyPanel в разметочной части мы подключили к ней пространство имен среды исполнения CLR, обозначив его произвольным именем.

  • Запустите упражнение - окончательный результат выполнения будет выглядеть примерно так

 








Дата добавления: 2015-04-15; просмотров: 980;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.004 сек.