Иногда может потребоваться создать свой собственный элемент компоновки, поскольку стандартные контейнеры не могут охватить весь спектр задач и потребностей разработчиков. При создании собственного контейнера комповки надо учитывать несколько моментов.
Во-первых, как и все элементы компоновки, новый элемент должен быть унаследован от класса System.Windows.Controls.Panel.
Во-вторых, новый контейре компоновки, должен реализовать два метода - MeasureOverride и ArrangeOverride. который унаследованы в классовой ирерахии от класса FrameworkElement. Вобщем всю логику работы можно представить в два этапа - измерение размеров элементов, которое происходит в методе MeasureOverride, и сам процесс компановки элементов, который происходит в методе ArrangeOverride. Посмотрим, что представляют эти методы.
В методе MeasureOverride мы должны пройти по всем дочерним элементам контейнера и вызвать для каждого элемента метод Measure(). В этот метод в качестве параметра передаются размеры максимально допустимого места для данного элемента. После этого метода мы можем получить текущее значение свойства DesiredSize данного элемента с учетом ограничения по размеру. В любом случае этот метод должен вызываться обязательно, тем более что без этого некоторые дочерние элементы не будут отрисованы. В конце метода мы возвращаем размер панели:
protected override Size MeasureOverride(Size panelSpace) { // Обход всех дочерних элементов foreach (UIElement element in this.Children) { //Указываем аксимально допустимый размер элемента Size availableElementSize = new Size(...); element.Measure(availableElementSize); } return new Size(...); }
Метод ArrangeOverride похож на метод MeasureOverride. В нем мы также возвращаем размер панели и также осуществляем обход элементов. Только теперь вместо метода Measure() вызывается метод Arrange(). В этот метод в качестве параметра передаются координаты прямоугольника, в котором должен располагаться элемент на панели для данного элемента:
protected override Size ArrangeOverride(Size panelSize) { // Обход всех дочерних элементов foreach (UIElement element in this.Children) { // Указываем координаты прямоугольнкиа, // в который будет вписан элемент Rect elementBounds = new Rect(...); element.Arrange(elementBounds); } // возвращаем размер панели return arrangeSize; }
Теперь посмотрим на примере, как реализовать собственную панель. Допустим, мы хотим создать некую панель Table - таблицу, которая будет содержать некоторое количество строк и столбцов, автоматически настраивающих свои размеры под размер элементов управления.
Для начала в методе CalculateColumns мы будем устанавливать количество строк и столбцов. Кроме того, в методе MeasureOverride мы будем устанавливать значения высоты для каждой строки и значения ширины для каждого столбца. И на основании этих данных в методе ArrangeOverride позиционировать элемент на панели:
public class Table : System.Windows.Controls.Panel { public int ColumnsNumber { get; set; } public int RowsNumber { get; set; } public double[] Rows; public double[] Columns; private int realColumns; private int realRows; public Table() : base() {} private void CalculateColumns() { // Подсчет элементов double elementCount = this.Children.Count; // Если панель пуста, выходим из метода if (elementCount == 0) return; realRows = RowsNumber; realColumns = ColumnsNumber; // Если свойства Rows и Columns установлены, используем их if ((realRows != 0) && (realColumns != 0)) return; // Если ни одно из свойств не установлено, вычисляем кол-во столбцов if ((realColumns == 0) && realRows == 0) realColumns = (int)Math.Ceiling(Math.Sqrt(elementCount)); // Если установлено только свойство Rows, вычисляем свойство Columns if (realColumns == 0) realColumns = (int)Math.Ceiling(elementCount / realRows); // Если установлено только свойство Columns, вычисляем свойство Rows if (realRows == 0) realRows = (int)Math.Ceiling(elementCount / realColumns); //Массив для значений высоты строк Rows = new double[realRows]; //Массив для значений ширины столбцов Columns = new double[realColumns]; } protected override Size MeasureOverride(Size availableSize) { CalculateColumns(); // некий ограничитель int constraint = 300; // распределяем пространство панели равномерно Size childConstraint = new Size(constraint / realColumns, constraint / realRows); int rowcounter = 0; int colcounter = 0; // Обход всех элементов foreach (UIElement child in this.Children) { // Получаем желаемый размер элемента child.Measure(childConstraint); // Обновляем максимальное значение стркои Rows[rowcounter] = child.DesiredSize.Height < Rows[rowcounter] ? Rows[rowcounter] : child.DesiredSize.Height; // Обновляем максимальное значение столбца Columns[colcounter] = child.DesiredSize.Width < Columns[colcounter] ? Columns[colcounter] : child.DesiredSize.Width; //Добавляем 10 для задания отступа Columns[colcounter] = Columns[colcounter] + 10; colcounter++; if (colcounter == realColumns) { rowcounter++; colcounter = 0; } } // Получаем совокупную высоты всех строки и ширину всех столбцов double panelHeight = Rows.Sum(); double panelWidth = Columns.Sum(); // На основании полученных значений устанавливаем размер панели return new Size(panelHeight, panelWidth); } protected override Size ArrangeOverride(Size arrangeSize) { double cellWidth ; double cellHeight; // Счетчики int rowcounter = 0; int colcounter = 0; // Текущие позиции double currentX = 0; double currentY = 0; // Обход всех элементов панели foreach (UIElement child in this.Children) { cellHeight = Rows[rowcounter]; cellWidth = Columns[colcounter]; // Определяем пространство для каждого дочернего элемента child.Arrange(new Rect(currentX, currentY, cellWidth, cellHeight)); colcounter++; currentX += cellWidth; if (colcounter == realColumns) { rowcounter++; colcounter = 0; currentY += cellHeight; currentX = 0; } } // Возвращаем размер панели return arrangeSize; } }
Чтобы использовать в XAML нашу таблицу, сначала надо добавить в декларации пространство имен приложения:
<UserControl x:Class="TestApplication.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:local="clr-namespace:TestApplication" mc:Ignorable="d" d:DesignHeight="250" d:DesignWidth="300"> <Grid x:Name="LayoutRoot" Background="White"> <TextBlock Text="TIOBE Index" HorizontalAlignment="Center"/> <local:Table Background="AntiqueWhite" ColumnsNumber="3" Margin="0 20 0 0"> <TextBlock Text="Первая" Height="40" /> <TextBlock Text="пятерка" Height="30" /> <TextBlock Text="языков" Height="20" /> <TextBlock Text="Place" /> <TextBlock Text="Language" /> <TextBlock Text="Rate" /> <TextBlock Text="1" /> <TextBlock Text="C" /> <TextBlock Text="17.346%" /> <TextBlock Text="2" /> <TextBlock Text="Java" /> <TextBlock Text="16.599%" /> <TextBlock Text="3" /> <TextBlock Text="C++" /> <TextBlock Text="17.346%" /> <TextBlock Text="4" /> <TextBlock Text="Objective-C" /> <TextBlock Text="8.309%" /> <TextBlock Text="5" /> <TextBlock Text="C#" /> <TextBlock Text="6.823%" /> </local:Table> </Grid> </UserControl>
Если мы запустим приложение, то увидим браузере нашу таблицу:
Обратите внимание. что мы задали число столбцов, и панель автоматически расчитала все строки. Также обратите внимание, что для первой строки высота была установлена по самому большому элементу - в данном случае по кнопке, которая имеет высоту 40 пикселей.