BoxPanel, пример настраиваемой панели
Узнайте, как писать код для пользовательского класса Panel, реализовывать методы ArrangeOverride и MeasureOverride и использовать свойство Children.
Важные API: Панель, УпорядочитьOverride,MeasureOverride
В примере кода показана пользовательская реализация панели, но мы не уделяем много времени объясняя концепции макета, влияющие на настройку панели для различных сценариев макета. Дополнительные сведения об этих концепциях макета и их применении к конкретному сценарию макета см. в обзоре пользовательских панелей XAML.
Панель — это объект, который предоставляет поведение макета для дочерних элементов, содержащихся в нем, когда выполняется система макета XAML и отрисовывается пользовательский интерфейс приложения. Пользовательские панели для макета XAML можно определить, наследив пользовательский класс из класса Panel. Вы предоставляете поведение для панели, переопределяя методы ArrangeOverride и MeasureOverride, предоставляя логику, которая измеряет и упорядочивает дочерние элементы. Этот пример является производным от Панели. При запуске с панели методы ArrangeOverride и MeasureOverride не имеют начального поведения. Код предоставляет шлюз, с помощью которого дочерние элементы становятся известны системой макета XAML и отображаются в пользовательском интерфейсе. Поэтому очень важно, чтобы учетные записи кода для всех дочерних элементов и следуют шаблонам, которые ожидает система макета.
Сценарий макета
При определении настраиваемой панели вы определяете сценарий макета.
Сценарий макета выражается с помощью следующих элементов:
- Что будет делать панель, когда у нее есть дочерние элементы
- Если панель имеет ограничения на собственное пространство
- Как логика панели определяет все измерения, размещение, позиции и размеры, которые в конечном итоге приводят к отображению макета пользовательского интерфейса дочерних элементов
Учитывая это, показанное BoxPanel
здесь для определенного сценария. В интересах сохранения кода в этом примере мы не будем подробно объяснить сценарий и сосредоточиться на необходимых шагах и шаблонах программирования. Если вы хотите узнать больше о сценарии, сначала перейдите к разделу "Сценарий для BoxPanel
", а затем вернитесь к коду.
Начните с производных от панели
Начните с создания пользовательского класса из Панели. Вероятно, самый простой способ сделать это — определить отдельный файл кода для этого класса, используя параметры контекстного меню "Добавить | новый класс элементов | " для проекта из Обозреватель решений в Microsoft Visual Studio. Присвойте классу (и файлу). BoxPanel
Файл шаблона для класса не начинается с большого количества операторов using, так как он не предназначен конкретно для приложений для Windows. Сначала добавьте инструкции using . Файл шаблона также начинается с нескольких инструкций using , которые, вероятно, не нужны, и их можно удалить. Ниже приведен список инструкций using, которые могут разрешать типы, необходимые для типичного пользовательского кода панели:
using System;
using System.Collections.Generic; // if you need to cast IEnumerable for iteration, or define your own collection properties
using Windows.Foundation; // Point, Size, and Rect
using Windows.UI.Xaml; // DependencyObject, UIElement, and FrameworkElement
using Windows.UI.Xaml.Controls; // Panel
using Windows.UI.Xaml.Media; // if you need Brushes or other utilities
Теперь, когда можно разрешить панель, сделайте ее базовым классом BoxPanel
. Кроме того, сделайте BoxPanel
общедоступным:
public class BoxPanel : Panel
{
}
На уровне класса определите некоторые int и двойные значения, которые будут совместно использоваться несколькими функциями логики, но которые не должны предоставляться как общедоступный API. В примере они называются: maxrc
, rowcount
colcount
, , cellwidth
, cellheight
, . maxcellheight
aspectratio
После этого полный файл кода выглядит следующим образом (удаление примечаний к использованию теперь, когда вы знаете, почему у нас есть):
using System;
using System.Collections.Generic;
using Windows.Foundation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Media;
public class BoxPanel : Panel
{
int maxrc, rowcount, colcount;
double cellwidth, cellheight, maxcellheight, aspectratio;
}
Отсюда мы будем отображать одно определение члена за раз, будь то переопределение метода или что-то поддерживаемое, например свойство зависимостей. Их можно добавить в скелет выше в любом порядке.
MeasureOverride
protected override Size MeasureOverride(Size availableSize)
{
// Determine the square that can contain this number of items.
maxrc = (int)Math.Ceiling(Math.Sqrt(Children.Count));
// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;
// Now trim this square down to a rect, many times an entire row or column can be omitted.
if (aspectratio > 1)
{
rowcount = maxrc;
colcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
}
else
{
rowcount = (maxrc > 2 && Children.Count <= maxrc * (maxrc - 1)) ? maxrc - 1 : maxrc;
colcount = maxrc;
}
// Now that we have a column count, divide available horizontal, that's our cell width.
cellwidth = (int)Math.Floor(availableSize.Width / colcount);
// Next get a cell height, same logic of dividing available vertical by rowcount.
cellheight = Double.IsInfinity(availableSize.Height) ? Double.PositiveInfinity : availableSize.Height / rowcount;
foreach (UIElement child in Children)
{
child.Measure(new Size(cellwidth, cellheight));
maxcellheight = (child.DesiredSize.Height > maxcellheight) ? child.DesiredSize.Height : maxcellheight;
}
return LimitUnboundedSize(availableSize);
}
Необходимый шаблон реализации MeasureOverride — это цикл по каждому элементу в Panel.Children. Всегда вызывайте метод Measure для каждого из этих элементов. Мера имеет параметр типа Size. То, что вы передаете здесь, — это размер, который ваша панель фиксирует, чтобы она была доступна для конкретного дочернего элемента. Таким образом, прежде чем выполнять цикл и начать вызывать меру, необходимо знать, сколько пространства каждая ячейка может занимать. Из самого метода MeasureOverride у вас есть доступное значение. Это размер родительского элемента панели, используемого при вызове Measure, который был триггером для этого метода MeasureOverride, вызываемого в первую очередь. Таким образом, типичная логика заключается в разработке схемы, в которой каждый дочерний элемент делит пространство общего доступного размера панели. Затем передайте каждое деление размера в Measure каждого дочернего элемента.
Как BoxPanel
разделить размер довольно просто: он разделяет его пространство на ряд прямоугольников, которые в значительной степени контролируются числом элементов. Поля зависят от количества строк и столбцов и доступных размеров. Иногда одна строка или столбец из квадрата не требуется, поэтому она удаляется, и панель становится прямоугольником, а не квадратом с точки зрения его строки: отношение столбцов. Дополнительные сведения о том, как эта логика была доставлена, перейдите к разделу "Сценарий для BoxPanel".
Так что делает мера? Он задает значение для свойства DesiredSize только для чтения для каждого элемента, в котором был вызван Measure. Наличие значения DesiredSize, возможно, важно после того, как вы получите передачу упорядочивания, так как DesiredSize сообщает, какой размер может или должен быть при упорядочении и в окончательной отрисовке. Даже если вы не используете DesiredSize в собственной логике, система по-прежнему нуждается в ней.
Эту панель можно использовать, если компонент высоты availableSize не подключен. Если это верно, панель не имеет известной высоты для разделения. В этом случае логика передачи мер сообщает каждому дочернему элементу, что у него нет ограниченной высоты. Это делает это путем передачи размера в меру вызова для детей, где Size.Height бесконечно. Это законно. При вызове меры логика заключается в том, что DesiredSize устанавливается как минимум из следующих значений: то, что было передано в Measure, или естественный размер этого элемента из таких факторов, как явно заданная высота и ширина.
Примечание.
Внутренняя логика StackPanel также имеет такое поведение: StackPanel передает бесконечное значение измерения для измерения дочерних элементов, указывая, что в измерении ориентации нет ограничений на дочерние элементы. StackPanel обычно динамически масштабирует все дочерние элементы в стеке, который растет в этом измерении.
Однако сама панель не может вернуть размер с бесконечным значением из MeasureOverride, что создает исключение во время макета. Таким образом, часть логики заключается в том, чтобы узнать максимальную высоту, которую запрашивают все дочерние запросы, и использовать такую высоту в качестве высоты ячейки в случае, если ограничения размера панели еще не приходят из собственных ограничений размера панели. Ниже приведена вспомогающая функция LimitUnboundedSize
, на которую ссылается предыдущий код, которая затем принимает максимальную высоту ячейки и использует ее для предоставления панели конечной высоты для возврата, а также обеспечения того, что cellheight
это конечное число перед инициированием передачи упорядочивания:
// This method limits the panel height when no limit is imposed by the panel's parent.
// That can happen to height if the panel is close to the root of main app window.
// In this case, base the height of a cell on the max height from desired size
// and base the height of the panel on that number times the #rows.
Size LimitUnboundedSize(Size input)
{
if (Double.IsInfinity(input.Height))
{
input.Height = maxcellheight * colcount;
cellheight = maxcellheight;
}
return input;
}
ArrangeOverride
protected override Size ArrangeOverride(Size finalSize)
{
int count = 1;
double x, y;
foreach (UIElement child in Children)
{
x = (count - 1) % colcount * cellwidth;
y = ((int)(count - 1) / colcount) * cellheight;
Point anchorPoint = new Point(x, y);
child.Arrange(new Rect(anchorPoint, child.DesiredSize));
count++;
}
return finalSize;
}
Необходимый шаблон реализации ArrangeOverride — это цикл по каждому элементу в Panel.Children. Всегда вызывайте метод Arrange для каждого из этих элементов.
Обратите внимание, что в MeasureOverride не существует столько вычислений, сколько обычно. Размер дочерних элементов уже известен из собственной логики MeasureOverride панели или из значения DesiredSize каждого дочернего набора во время прохождения меры. Однако нам по-прежнему нужно решить расположение на панели, где будет отображаться каждый дочерний элемент. На обычной панели каждый дочерний элемент должен отображаться по-разному. Панель, которая создает перекрывающиеся элементы, не является желательной для типичных сценариев (хотя это не из вопроса, чтобы создавать панели с целевым перекрытием, если это действительно ваш предполагаемый сценарий).
Эта панель упорядочивается по концепции строк и столбцов. Число строк и столбцов уже вычислялось (было необходимо для измерения). Таким образом, теперь форма строк и столбцов плюс известные размеры каждой ячейки способствуют логике определения позиции отрисовки (the anchorPoint
) для каждого элемента, который содержит эта панель. Эта точка, а также размер, уже известный из меры, используются в качестве двух компонентов, которые создают прямоугольник. Rect — это входной тип для Упорядочивания.
Иногда панели нужно закрепить содержимое. Если они делают, обрезанный размер — это размер, который присутствует в DesiredSize, так как логика меры задает ее как минимум того, что было передано в Measure, или других естественных факторов размера. Поэтому обычно вам не нужно специально проверять вырезку во время упорядочивания; вырезка просто происходит на основе передачи DesiredSize через каждый вызов Упорядочить.
Вам не всегда требуется количество во время прохождения цикла, если все сведения, необходимые для определения позиции отрисовки, известны другими средствами. Например, в логике макета холста позиция в коллекции "Дочерние " не имеет значения. Все сведения, необходимые для размещения каждого элемента на холсте, известны с помощью чтения Canvas.Left и Canvas.Top значений дочерних элементов в рамках логики упорядочивания. Логика BoxPanel
требуется для сравнения с colcount , поэтому известно, когда начать новую строку и смещать значение y .
Обычно входной финалSize и размер, возвращаемый из реализации ArrangeOverride, совпадают. Дополнительные сведения о том, почему см. в разделе "УпорядочитьOverride" в обзоре пользовательских панелей XAML.
Уточнение: управление числом строк и столбцов
Вы можете скомпилировать и использовать эту панель так же, как сейчас. Однако мы добавим еще одно уточнение. В приведенном коде логика помещает дополнительную строку или столбец на стороне, которая является самой длинной в пропорции. Но для большего контроля над формами ячеек, возможно, желательно выбрать 4x3 набор ячеек вместо 3x4, даже если собственный пропорции панели является "книжной". Поэтому мы добавим необязательное свойство зависимостей, которое потребитель панели может задать для управления этим поведением. Ниже приведено определение свойства зависимостей, которое является очень простым:
// Property
public Orientation Orientation
{
get { return (Orientation)GetValue(OrientationProperty); }
set { SetValue(OrientationProperty, value); }
}
// Dependency Property Registration
public static readonly DependencyProperty OrientationProperty =
DependencyProperty.Register(nameof(Orientation), typeof(Orientation), typeof(BoxPanel), new PropertyMetadata(null, OnOrientationChanged));
// Changed callback so we invalidate our layout when the property changes.
private static void OnOrientationChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
if (dependencyObject is BoxPanel panel)
{
panel.InvalidateMeasure();
}
}
Ниже показано, как использовать Orientation
логику меры в MeasureOverride
. На самом деле все это делает изменение того, как rowcount
и colcount
производные от maxrc
и истинного пропорции, и есть соответствующие различия размера для каждой ячейки из-за этого. Если Orientation
задано значение "Вертикально " (по умолчанию), оно преобразует значение истинного пропорции перед его использованием для счетчиков строк и столбцов для макета прямоугольника "книжный".
// Get an aspect ratio from availableSize, decides whether to trim row or column.
aspectratio = availableSize.Width / availableSize.Height;
// Transpose aspect ratio based on Orientation property.
if (Orientation == Orientation.Vertical) { aspectratio = 1 / aspectratio; }
Сценарий для BoxPanel
Конкретный сценарий BoxPanel
заключается в том, что это панель, в которой одним из основных детерминантов разделения пространства является знание количества дочерних элементов и разделение известного доступного пространства для панели. Панели — это фигуры прямоугольника. Многие панели работают путем деления прямоугольника на дополнительные прямоугольники; Это то, что сетка делает для своих ячеек. В случае Сетки размер ячеек задается значениями ColumnDefinition и RowDefinition, а элементы объявляют точную ячейку, в которую они попадают с присоединенными свойствами Grid.Row и Grid.Column. Для получения хорошего макета из сетки обычно требуется знать количество дочерних элементов заранее, чтобы иметь достаточно ячеек, и каждый дочерний элемент задает свои присоединенные свойства, чтобы поместиться в свою собственную ячейку.
Но что делать, если число детей является динамическим? Это, безусловно, возможно; Код приложения может добавлять элементы в коллекции, в ответ на любое динамическое условие времени выполнения, которое вы считаете достаточно важным, чтобы стоит обновить пользовательский интерфейс. Если вы используете привязку данных к резервным коллекциям или бизнес-объектам, получение таких обновлений и обновление пользовательского интерфейса обрабатывается автоматически, поэтому часто это предпочтительный метод (см. подробные сведения о привязке данных).
Но не все сценарии приложений предоставляют привязку данных. Иногда необходимо создать элементы пользовательского интерфейса во время выполнения и сделать их видимыми. BoxPanel
предназначен для этого сценария. Изменение числа дочерних элементов не является проблемой BoxPanel
, так как оно использует количество дочерних элементов в вычислениях, и настраивает существующие и новые дочерние элементы в новый макет, чтобы они все соответствовали.
Расширенный сценарий расширения BoxPanel
дальнейшего (не показанного здесь) может как разместить динамических детей, так и использовать DesiredSize ребенка в качестве более сильного фактора для изменения размера отдельных ячеек. Этот сценарий может использовать различные размеры строк или столбцов или фигуры, отличные от сетки, чтобы было меньше свободного места. Для этого требуется стратегия того, как несколько прямоугольников различных размеров и пропорций могут поместиться в содержащий прямоугольник как для эстетики, так и для наименьшего размера. BoxPanel
не делает этого; он использует более простой метод для разделения пространства. BoxPanel
Метод заключается в определении наименьшего квадратного числа, которое больше, чем число дочерних элементов. Например, 9 элементов будут помещаться в квадрат 3x3. Для 10 элементов требуется квадрат 4x4. Однако часто можно поместить элементы, удаляя одну строку или столбец начальной площади, чтобы сэкономить место. В примере count=10, который соответствует прямоугольнику 4x3 или 3x4.
Вы можете задаться вопросом, почему панель не будет выбирать 5x2 для 10 элементов, так как это соответствует номеру элемента аккуратно. Однако на практике панели имеют размер прямоугольников, которые редко имеют строго ориентированное соотношение аспектов. Метод наименьших квадратов — это способ предвзятости логики изменения размера, чтобы хорошо работать с типичными фигурами макета и не поощрять изменение размера, где фигуры ячеек получают нечетные пропорции.
См. также
Справочные материалы
Основные понятия
Windows developer