Обзор пользовательских панелей XAML
Панель — это объект, который предоставляет поведение макета для дочерних элементов, содержащихся в нем, когда выполняется система макета расширяемого языка разметки приложений (XAML) и отрисовывается пользовательский интерфейс приложения.
Важные API: Панель, УпорядочитьOverride, MeasureOverride
Пользовательские панели для макета XAML можно определить, наследив пользовательский класс из класса Panel. Вы предоставляете поведение для панели, переопределяя MeasureOverride и ArrangeOverride, предоставляя логику, которая измеряет и упорядочивает дочерние элементы.
Базовый класс Panel
Чтобы определить пользовательский класс панели, можно либо наследовать из класса Panel напрямую, либо получить от одного из практических классов панели, которые не запечатаны, например Grid или StackPanel. Проще наследоваться от панели, так как ее можно обойти с существующей логикой макета панели, которая уже имеет поведение макета. Кроме того, панель с поведением может иметь существующие свойства, которые не относятся к функциям макета панели.
На панели настраиваемая панель наследует следующие API:
- Свойство Children .
- Свойства Background, ChildrenTransitions и IsItemsHost и идентификаторы свойств зависимостей. Ни один из этих свойств не является виртуальным, поэтому обычно не переопределяете или не заменяете их. Обычно эти свойства не требуются для пользовательских сценариев панели, даже не для чтения значений.
- Макет переопределяет методы MeasureOverride и ArrangeOverride. Изначально они были определены FrameworkElement. Класс base Panel не переопределяет эти, но практические панели, такие как Сетка , имеют переопределение реализаций, которые реализуются как машинный код и выполняются системой. Предоставление новых (или аддитивных) реализаций для ArrangeOverride и MeasureOverride является основной частью усилий, необходимых для определения пользовательской панели.
- Все другие API FrameworkElement, UIElement и DependencyObject, такие как Height, Видимость и т. д. Иногда вы ссылаетесь на значения этих свойств в переопределении макета, но они не являются виртуальными, поэтому обычно они не переопределяются или не заменяют их.
Здесь основное внимание уделяется описанию концепций макета XAML, поэтому вы можете рассмотреть все возможности того, как пользовательская панель может и должна вести себя в макете. Если вы хотите перейти вправо и увидеть пример реализации настраиваемой панели, см . пример настраиваемой панели BoxPanel.
Свойство Children
Свойство Children относится к пользовательской панели, так как все классы, производные от Панели, используют свойство Children в качестве места для хранения содержащихся дочерних элементов в коллекции. Дочерние элементы называются свойством содержимого XAML для класса Panel , и все классы, производные от панели , могут наследовать поведение свойства содержимого XAML. Если свойство присваивается свойству содержимого XAML, это означает, что разметка XAML может опустить элемент свойства при указании этого свойства в разметке, а значения задаются как непосредственные дочерние элементы разметки (содержимое). Например, если вы наследуете класс CustomPanel из Панели , который не определяет новое поведение, вы по-прежнему можете использовать эту разметку:
<local:CustomPanel>
<Button Name="button1"/>
<Button Name="button2"/>
</local:CustomPanel>
Когда средство синтаксического анализа XAML считывает эту разметку, дочерние элементы, как известно, являются свойством содержимого XAML для всех производных типов Панели, поэтому средство синтаксического анализа добавит два элемента Button в значение UIElementCollection свойства Children. Свойство содержимого XAML упрощает упрощенную связь родительского-дочернего элемента в разметке XAML для определения пользовательского интерфейса. Дополнительные сведения о свойствах содержимого XAML и заполнении свойств коллекции при анализе XAML см. в руководстве по синтаксису XAML.
Тип коллекции, поддерживающий значение свойства Children, является класс UIElementCollection. UIElementCollection — это строго типизированная коллекция, которая использует UIElement в качестве его принудительного типа элемента. UIElement — это базовый тип, наследуемый сотнями практических типов элементов пользовательского интерфейса, поэтому применение типов здесь намеренно свободно. Но он применяется, что у вас не может быть Кисть в качестве прямого дочернего элемента панели, и обычно это означает, что только элементы, которые, как ожидается, будут отображаться в пользовательском интерфейсе и участвовать в макете будут найдены как дочерние элементы на панели.
Как правило, пользовательская панель принимает любой дочерний элемент UIElement по определению XAML, просто используя характеристики свойства Children as-is. В качестве расширенного сценария можно поддерживать дополнительную проверку типов дочерних элементов при переопределении коллекции в макете.
Помимо циклического прохождения коллекции "Дочерние" в переопределениях, логика панели также может влиять на Children.Count
. У вас может быть логика, которая выделяет пространство по крайней мере частично на основе количества элементов, а не требуемых размеров и других характеристик отдельных элементов.
Переопределение методов макета
Базовая модель для методов переопределения макета (MeasureOverride и ArrangeOverride) заключается в том, что они должны выполнять итерацию всех дочерних элементов и вызывать конкретный метод макета каждого дочернего элемента. Первый цикл макета начинается, когда система макета XAML задает визуальный элемент для корневого окна. Так как каждый родительский элемент вызывает макет в дочерних элементах, он распространяет вызов методов макета к каждому возможному элементу пользовательского интерфейса, который должен быть частью макета. В макете XAML существует два этапа: мера, а затем упорядочение.
Вы не получаете встроенного метода макета для MeasureOverride и ArrangeOverride из базового класса Panel. Элементы в дочерних элементах не будут автоматически отображаться в виде части визуального дерева XAML. Это позволяет сделать элементы известными для процесса макета, вызывая методы макета для каждого из элементов, которые вы найдете в дочерних элементах через проход макета в реализации MeasureOverride и ArrangeOverride.
Нет никаких причин вызывать базовые реализации в переопределениях макета, если у вас нет собственного наследования. Собственные методы для поведения макета (если они существуют) выполняются независимо, и не вызывая базовую реализацию из переопределения, не препятствуют возникновению собственного поведения.
Во время прохождения мер логика макета запрашивает каждый дочерний элемент для требуемого размера, вызывая метод Measure для этого дочернего элемента. Вызов метода Measure устанавливает значение свойства DesiredSize. Возвращаемое значение MeasureOverride — это требуемый размер самой панели.
Во время прохождения упорядочивания позиции и размеры дочерних элементов определяются в пространстве x-y, а композиция макета подготовлена для отрисовки. Код должен вызывать "Упорядочить" для каждого дочернего элемента в Дочерних элементах, чтобы система макета обнаружила, что элемент принадлежит в макете. Вызов Упорядочивания является предшественником композиции и отрисовки; он сообщает системе макета, где этот элемент идет, когда композиция отправляется для отрисовки.
Многие свойства и значения способствуют работе логики макета во время выполнения. Способ думать о процессе макета заключается в том, что элементы без дочерних элементов (как правило, наиболее глубоко вложенный элемент в пользовательском интерфейсе) — это те, которые могут сначала завершить измерения. У них нет зависимостей от дочерних элементов, влияющих на их требуемый размер. Они могут иметь свои собственные нужные размеры, и это предложения по размеру до тех пор, пока макет на самом деле не будет установлен. Затем передача меры продолжает идти вверх по визуальному дереву, пока корневой элемент не будет иметь свои измерения и все измерения можно завершить.
Макет кандидата должен соответствовать текущему окну приложения или другим частям пользовательского интерфейса. Панели часто являются местом, где определяется логика вырезки. Логика панели может определить размер, доступный в реализации MeasureOverride , и может потребоваться отправить ограничения размера на дочерние элементы и разделить пространство между детьми, чтобы все вместилось как можно лучше. В идеале результат макета — это то, что использует различные свойства всех частей макета, но по-прежнему помещается в окно приложения. Для этого требуется хорошая реализация логики макета панелей, а также разумный дизайн пользовательского интерфейса в части любого кода приложения, создающего пользовательский интерфейс с помощью этой панели. Дизайн панели не будет выглядеть хорошо, если общая конструкция пользовательского интерфейса включает в себя больше дочерних элементов, чем может быть включено в приложение.
Большая часть того, что делает работу системы макета, заключается в том, что любой элемент, основанный на FrameworkElement , уже имеет некоторое собственное поведение при действии в качестве дочернего элемента в контейнере. Например, существует несколько API FrameworkElement, которые информируют о поведении макета или необходимы для работы макета. Например:
- DesiredSize (фактически — свойство UIElement);
- ActualHeight и ActualWidth;
- Height и Width;
- Margin;
- событие LayoutUpdated;
- HorizontalAlignment и VerticalAlignment;
- методы ArrangeOverride и MeasureOverride;
- методы Arrange и Measure: они имеют собственные реализации, определенные на уровне FrameworkElement, которые обрабатывают действия макета на уровне элементов.
MeasureOverride
Метод MeasureOverride имеет возвращаемое значение, используемое системой макета в качестве запуска DesiredSize для самой панели, когда метод Measure вызывается на панели его родительским элементом в макете. Выбор логики в методе так же важен, как и то, что он возвращает, и логика часто влияет на возвращаемое значение.
Все реализации MeasureOverride должны выполнять циклы по дочерним элементам и вызывать метод Measure для каждого дочернего элемента. Вызов метода Measure устанавливает значение свойства DesiredSize. Это может сообщить, сколько пространства требуется самой панели, а также как это пространство делится между элементами или размером для определенного дочернего элемента.
Ниже приведен очень базовый скелет метода MeasureOverride:
protected override Size MeasureOverride(Size availableSize)
{
Size returnSize; //TODO might return availableSize, might do something else
//loop through each Child, call Measure on each
foreach (UIElement child in Children)
{
child.Measure(new Size()); // TODO determine how much space the panel allots for this child, that's what you pass to Measure
Size childDesiredSize = child.DesiredSize; //TODO determine how the returned Size is influenced by each child's DesiredSize
//TODO, logic if passed-in Size and net DesiredSize are different, does that matter?
}
return returnSize;
}
Элементы часто имеют естественный размер к тому времени, когда они готовы к макету. После прохождения мер параметр DesiredSize может указать, что естественный размер, если доступный размер, который вы передали для measure, был меньше. Если естественный размер превышает доступный размер, который вы передали для Measure, параметр DesiredSize ограничен доступнойSize. Это то, как выполняется внутренняя реализация Measure, и переопределения макета должны учитывать это поведение.
Некоторые элементы не имеют естественного размера, так как они имеют автоматические значения для высоты и ширины. Эти элементы используют полную доступностьSize, так как это то, что представляет автоматическое значение: размер элемента до максимального доступного размера, который родитель немедленного макета взаимодействует с помощью вызова Measure с availableSize. На практике всегда есть некоторые измерения, которые пользовательский интерфейс имеет размер (даже если это окно верхнего уровня).) В конечном итоге передача меры разрешает все значения auto в родительские ограничения, а все элементы автозначных значений получают реальные измерения (которые можно получить, проверив ActualWidth и ActualHeight после завершения макета).
Это законно передать размер в Меру, которая имеет по крайней мере одно бесконечное измерение, чтобы указать, что панель может попытаться размерить себя, чтобы соответствовать измерениям его содержимого. Каждый дочерний элемент, измеряемый, задает значение DesiredSize с помощью естественного размера. Затем во время прохождения упорядочивания панель обычно упорядочивает этот размер.
Текстовые элементы, такие как TextBlock, имеют вычисляемый код ActualWidth и ActualHeight на основе их текстовых строк и текстовых свойств, даже если значение высоты или ширины не задано, и эти измерения должны соблюдаться логикой панели. Вырезка текста является особенно плохим интерфейсом.
Даже если ваша реализация не использует требуемые измерения размера, рекомендуется вызвать метод Measure для каждого дочернего элемента, так как вызываются внутренние и собственные поведения, которые активируются методом Measure. Для участия элемента в макете каждый дочерний элемент должен иметь метод Measure, вызываемый им во время прохождения меры, и метод Упорядочения, вызываемый им во время прохождения упорядочения. Вызов этих методов задает внутренние флаги объекта и заполняет значения (например , свойство DesiredSize ), необходимые для логики макета системы при создании визуального дерева и отрисовке пользовательского интерфейса.
Возвращаемое значение MeasureOverride основано на логике панели, интерпретирующей Метод DesiredSize или другие рекомендации по размеру для каждого дочернего элемента в Дочерних элементах при вызове меры. Что делать с значениями DesiredSize от дочерних элементов и как возвращаемое значение MeasureOverride должно использовать их до интерпретации собственной логики. Обычно значения не добавляются без изменений, так как входные данные MeasureOverride часто являются фиксированным доступным размером, предлагаемым родительским элементом панели. При превышении этого размера сама панель может быть обрезана. Обычно вы сравниваете общий размер дочерних элементов с доступным размером панели и при необходимости вносите корректировки.
Советы и рекомендации
- В идеале пользовательская панель должна быть подходящей для того, чтобы быть первым истинным визуальным элементом в композиции пользовательского интерфейса, возможно, на уровне непосредственно в разделе Page, UserControl или другой элемент, который является корнем страницы XAML. В реализациях MeasureOverride не возвращайте входной размер без проверки значений. Если возвращаемый размер имеет в нем значение Бесконечности , это может вызвать исключения в логике макета среды выполнения. Значение Бесконечности может поступать из главного окна приложения, которое можно прокручивать и поэтому не имеет максимальной высоты. Другое прокручиваемое содержимое может иметь то же поведение.
- Еще одна распространенная ошибка в реализации MeasureOverride — возвратить новый размер по умолчанию (значения высоты и ширины равны 0). Вы можете начать с этого значения, и это может быть даже правильное значение, если панель определяет, что ни один из дочерних элементов не должен отображаться. Но размер по умолчанию приводит к неправильному размеру панели. Он запрашивает пространство в пользовательском интерфейсе, поэтому не получает пробела и не отображается. Все код панели в противном случае может работать нормально, но вы по-прежнему не увидите ее содержимое или панель, если она состоит с нулевой высотой, нулевой шириной.
- В переопределениях избегайте соблазна приведения дочерних элементов к FrameworkElement и использования свойств, вычисляемых в результате макета, особенно ActualWidth и ActualHeight. В большинстве распространенных сценариев логику можно на основе логики в значении DesiredSize дочернего элемента, и вам не потребуется никаких связанных свойств Height или Width дочернего элемента. В специализированных случаях, когда вы знаете тип элемента и имеете дополнительную информацию, например естественный размер файла изображения, можно использовать специализированные сведения элемента, так как это не значение, активно изменяемое системами макетов. Включение вычисляемых свойств макета в рамках логики макета значительно увеличивает риск определения непреднамеренного цикла макета. Эти циклы вызывают условие, в котором невозможно создать допустимый макет, и система может вызвать разметку LayoutCycleException , если цикл не может восстановиться.
- Панели обычно делят доступное пространство между несколькими дочерними элементами, хотя именно то, как разделено пространство. Например, Grid реализует логику макета, которая использует значения RowDefinition и ColumnDefinition для разделения пространства на ячейки Сетки, поддерживая значения размера звезд и пикселей. Если они являются значениями пикселей, размер, доступный для каждого дочернего элемента, уже известен, поэтому это то, что передается в качестве входного размера для измерения стиля сетки.
- Сами панели могут вводить зарезервированное пространство для заполнения между элементами. Если это сделать, обязательно предоставьте измерения как свойство, отличное от Margin или любого свойства Padding .
- Элементы могут иметь значения для свойств ActualWidth и ActualHeight на основе предыдущего прохода макета. Если значения изменяются, код пользовательского интерфейса приложения может поместить обработчики для LayoutUpdated на элементы, если есть специальная логика выполнения, но логика панели обычно не требует проверки изменений с обработкой событий. Система макета уже определяет, когда повторно запустить макет, так как измененное значение свойства макета, а параметр MeasureOverride или ArrangeOverride панели вызывается автоматически в соответствующих обстоятельствах.
ArrangeOverride
Метод ArrangeOverride имеет возвращаемое значение Size, используемое системой макета при отрисовке самой панели, когда метод "Упорядочить" вызывается на панели его родительским элементом в макете. Как правило, входной финалSize и возвращаемый размер Объекта ArrangeOverride совпадают. Если они нет, это означает, что панель пытается сделать себя другим участником утверждения макета доступным. Окончательный размер был основан на том, что ранее был выполнен проход меры через код панели, поэтому возврат другого размера не является типичным: это означает, что вы намеренно игнорируете логику мер.
Не возвращайте размер с компонентом Бесконечности. При попытке использовать такой размер создается исключение из внутреннего макета.
Все реализации ArrangeOverride должны выполнять циклы по дочерним элементам и вызывать метод Arrange для каждого дочернего элемента. Как и Measure, у упорядочивания нет возвращаемого значения. В отличие от Measure, вычисляемое свойство не получается в результате (однако элемент, заданный в вопросе, обычно вызывает событие LayoutUpdated).
Вот очень базовый скелет метода ArrangeOverride:
protected override Size ArrangeOverride(Size finalSize)
{
//loop through each Child, call Arrange on each
foreach (UIElement child in Children)
{
Point anchorPoint = new Point(); //TODO more logic for topleft corner placement in your panel
// for this child, and based on finalSize or other internal state of your panel
child.Arrange(new Rect(anchorPoint, child.DesiredSize)); //OR, set a different Size
}
return finalSize; //OR, return a different Size, but that's rare
}
Передача макета может произойти без прохождения меры. Однако это происходит только в том случае, если система макета не изменила свойства, которые повлияли бы на предыдущие измерения. Например, если выравнивание изменяется, не нужно повторно измерять этот элемент, так как его DesiredSize не изменится при изменении выбора выравнивания. С другой стороны, если ActualHeight изменяет любой элемент в макете, требуется новый проход меры. Система макета автоматически обнаруживает изменения истинной меры и снова вызывает передачу меры, а затем запускает другой проход упорядочения.
Входные данные для Упорядочивания принимают значение Rect. Наиболее распространенным способом создания этого прямоугольника является использование конструктора, имеющего входные данные точки и входные данные размера. Точка — это точка, в которой должен размещаться верхний левый угол ограничивающего прямоугольника элемента. Размер — это измерения, используемые для отрисовки этого конкретного элемента. Для этого элемента часто используется DesiredSize, так как установка DesiredSize для всех элементов, участвующих в макете, была целью прохождения меры макета. (Передача мер определяет все размеры элементов в итеративном режиме, чтобы система макета могли оптимизировать размещение элементов после того, как он перейдет к проходу упорядочения.)
Как правило, реализация ArrangeOverride зависит от логики, с помощью которой панель определяет компонент Point, определяющий порядок размещения каждого дочернего элемента. Абсолютная панель размещения, например Canvas, использует явные сведения о размещении, полученные из каждого элемента через Canvas.Left и Canvas.Top значения. Панель разделения пространства, например Сетка , будет иметь математические операции, разделяющие доступное пространство на ячейки, и каждая ячейка будет иметь значение x-y, для которого его содержимое должно быть помещено и упорядочено. Адаптивная панель, например StackPanel , может расширяться, чтобы соответствовать содержимому в его измерении ориентации.
Существуют все еще дополнительные влияния на элементы в макете, помимо того, что вы напрямую управляете и передаете в Упорядочивание. Это происходит из внутренней реализации производного объекта Arrange , который является общим для всех производных типов FrameworkElement и дополнен некоторыми другими типами, такими как текстовые элементы. Например, элементы могут иметь поле и выравнивание, а некоторые могут иметь заполнение. Эти свойства часто взаимодействуют. Дополнительные сведения см. в разделе "Выравнивание", " Поля" и "Заполнение".
Панели и элементы управления
Избегайте размещения функциональных возможностей в настраиваемую панель, которая вместо этого должна быть создана как пользовательский элемент управления. Роль панели заключается в том, чтобы представить содержимое дочернего элемента, существующее в нем, как функцию макета, которая происходит автоматически. Панель может добавлять украшения в содержимое (аналогично тому, как Border добавляет границу вокруг элемента, который он представляет), или выполнять другие корректировки, связанные с макетом, например заполнение. Но это примерно так далеко, как вы должны идти при расширении выходных данных визуального дерева за пределами отчетов и использования информации от детей.
Если у пользователя есть какое-либо взаимодействие, необходимо написать пользовательский элемент управления, а не панель. Например, панель не должна добавлять окна просмотра прокрутки в содержимое, даже если цель заключается в предотвращении вырезки, так как полосы прокрутки, пальцы и т. д. являются интерактивными частями управления. (Содержимое может иметь полосы прокрутки в конце концов, но вы должны оставить это до логики дочернего элемента. Не заставляйте его добавлять прокрутку как операцию макета.) Вы можете создать элемент управления, а также написать пользовательскую панель, которая играет важную роль в визуальном дереве этого элемента управления, когда речь идет о представлении содержимого в этом элементе управления. Но элемент управления и панель должны быть отдельными объектами кода.
Одна из причин, по которой важно различие между элементом управления и панелью, обусловлена модель автоматизации пользовательского интерфейса и специальными возможностями Майкрософт. Панели обеспечивают поведение визуального макета, а не логическое поведение. Визуальное представление элемента пользовательского интерфейса не является аспектом пользовательского интерфейса, который обычно важен для сценариев специальных возможностей. Специальные возможности предназначены для предоставления частей приложения, которые логически важны для понимания пользовательского интерфейса. При необходимости элементы управления должны предоставлять возможности взаимодействия инфраструктуре модель автоматизации пользовательского интерфейса. Дополнительные сведения см. в разделе "Пользовательские одноранговые узлы автоматизации".
Другой API макета
Существуют некоторые другие API- интерфейсы, которые являются частью системы макета, но не объявляются панелью. Их можно использовать в реализации панели или в пользовательском элементе управления, использующего панели.
- Методы UpdateLayout, InvalidateMeasure и InvalidateArrange запускают этап разметки. InvalidateArrange может не активировать передачу мер, но две другие. Никогда не вызывать эти методы из переопределения метода макета, так как они почти наверняка вызывают цикл макета. Код управления обычно не должен вызывать их. Большинство аспектов макета активируются автоматически путем обнаружения изменений в свойствах макета, определенных платформой, таких как Ширина и т. д.
- Событие LayoutUpdated создается, когда изменяется какой-либо аспект макета элемента. Это не зависит от панелей; событие определяется FrameworkElement.
- Событие SizeChanged создается только после завершения этапов разметки и показывает, что в результате изменилось значение ActualHeight или ActualWidth. Это еще одно событие FrameworkElement. Существуют случаи, когда срабатывает МакетUpdated, но SizeChanged не выполняется. Например, внутреннее содержимое может быть изменено, но размер элемента не изменился.
См. также
Справочные материалы
Основные понятия
Windows developer