Руководство для начинающих (часть 1)

Некоторые из вас могли читать мои публикации здесь, на сайте The Code Project. В последнее время я написал несколько статей, посвященных технологиям WPF/LINQ/Silverlight/WCF. Обычно я пишу с удовольствием, так, чтобы статьи в первую очередь были интересными для меня. Иногда они получаются довольно сложными, что мне нравится.

Не так давно я опубликовал статью о WPF, которой присвоил средний уровень сложности. Но при обновлении сайта The Code Project она была отнесена к категории «для начинающих». Естественно, некоторые читатели выступили с резкой критикой по поводу ее излишней сложности.

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

Я немного подумал, просмотрел сайт и действительно обнаружил, что авторы, пишущие о WPF на сайте The Code Project (включая меня самого), слишком часто умничают. Не поймите меня неправильно: даже в зауми есть свои плюсы, лично я узнал массу полезного из этих статей. Но в то же время на сайте не так уж много материалов для новичков с ограниченным запасом свободного времени, поэтому я решил, что серия статей про WPF для начинающих не помешала бы. Я знаю, что Джош Смит (Josh Smith) подготовил отличную серию Пошаговое руководство по WPF, которую лично я рекомендую прочитать всем, кто этого еще не сделал.

Также есть книги. Лично у меня — целых три книги, но все они содержат разную информацию. И я подумал, что идея написать серию статей для начинающих не так уж плоха. И решил взяться за дело. Не знаю, сколько статей будет в этой серии, но, вероятнее всего, в нее войдут следующие статьи:

Как я уже говорил, Джош проделал отличную работу, но зачем тогда я купил три разные книги по WPF? Почему одной недостаточно? Иногда просто необходимо знать другую точку зрения. Например, Джош — эксперт в области WPF (эдакий величественный орел, реющий высоко в небе), а я — едва оперившийся птенец, который только учится летать в мире WPF. Вот я и решил сделать что-нибудь полезное и посмотреть на все чуть под другим углом — с точки зрения сумасшедшего птенца.

Естественно, в мои статьи попадут сведения из книг, но я надеюсь изложить и то новое, что узнал сам при знакомстве с WPF.

Написание — дело нелегкое, так что прошу оставлять комментарии внизу страницы и голосовать за статью. Так я узнаю, стоит ли мне продолжать работу. У меня в планах сотни других статей (целый список), над которыми мне придется поломать голову, но мне кажется, что эта серия будет полезной (дайте мне знать на форуме, посвященном этой статье, ждете ли вы продолжения), и, если так, я с радостью посвящу свое время тому, чтобы сделать ее максимально интересной.

Итак, думаю, можно без лишних слов переходить к делу. Поскольку это первая статья в этой серии (судьба последующих зависит от желания читателей), начать стоит с макета.

Макет

Макет — одна из самых важных составляющих любого проекта WPF. Следующие подразделы знакомят с новыми возможностями макета.

Важность макета

Платформу WPF можно использовать либо в браузере (частичное доверие) в виде приложения Xbap, либо в полнофункциональном приложении, как правило в исполняемом файле (*.exe). Формат приложения для нас роли не играет — в обоих случаях макет одинаково важен.

Итак, макет — фундаментальный элемент, используемый при написании любого приложения WPF, будь то XBap или полноценное приложение. С помощью элементов управления макета WPF разработчики и дизайнеры могут создавать очень сложные системы страниц и элементов управления. Без макета мы вряд ли сможем создать что-то, кроме хаоса. Так что, если вам нужен хаос, дальше можно не читать. А если вы все-таки хотите узнать, как воспользоваться новыми возможностями макета в WPF, стоит продолжить чтение.

Новые возможности WPF

Если вы веб-разработчик, многое (но не все) в этой статье будет, вероятно, новым для вас. Если вы разработчик приложений WinForms, вы, несомненно, сталкивались с классами Panel и раньше и даже, может быть, использовали некоторые более сложные подклассы Panel, такие как FlowLayoutPanel и TableLayoutPanel. Вы также должны быть знакомы со свойствами WinForms, такими как Anchor и Dock. Звучит знакомо? Многое из того, что вы уже знаете, пригодится, но придется изучить и кое-что еще. А теперь хорошая новость для веб-разработчиков — так как синтаксис XAML очень похож на XHTML, у вас не должно возникнуть проблем с освоением нового способа создания интерфейса пользователя.

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

В этой статье мы остановимся на следующих элементах:

  • Canvas
  • StackPanel
  • WrapPanel
  • DockPanel
  • Grid

Обратите внимание, что мы изложим только основные сведения об этих элементах управления. Ресурсов, где можно найти их более подробное описание, более чем достаточно. Кроме того, я считаю, что более сложные способы использования этих элементов выходят за рамки данной статьи. Не забывайте, что это серия статей для начинающих.

Краткое отступление о важности свойства Margin

Свойство Margin выполняет исключительно важную роль. С его помощью можно задавать поля вокруг текущего элемента управления (то есть элемента, для которого задается свойство Margin). В WPF предусмотрен объект ValueConverter, который принимает строку в формате «5,5,5,5». О чем это говорит? О том, что мы хотим оставить поля размером 5 пикселей со всех сторон элемента. Строка Margin определяет левое, верхнее, правое и нижнее поля. Это один из трех перегруженных конструкторов класса со странным именем Thickness, который используется при задании свойства Margin в коде программной части.

Элемент управления Canvas

Canvas— один из удобнейших элементов управления макета. Это простой контейнер положений X и Y. Чтобы располагаться в родительском элементе Canvas, каждый из его дочерних элементов должен задавать следующие четыре свойства:

  • Canvas.Left
  • Canvas.Right
  • Canvas.Top
  • Canvas.Bottom

Если эти четыре свойства заданы, элемент управления будет располагаться в родительском элементе управления Canvas с учетом этих значений. Эти свойства выглядят несколько странно, например Canvas.Left. Впрочем, они и правда немного странные. Это не обычные свойства, используемые в платформе .NET 2.0, а присоединенные свойства зависимости. Сейчас я не буду подробно останавливаться на этом понятии — о нем пойдет речь в одной из будущих статей этой серии.

Что еще нужно знать об элементе управления Canvas и его дочерних элементах? Как ни странно, это почти все. Еще только один момент: если элемент Canvas — простой контейнер положений X и Y, что происходит с наложением двух элементов управления и какой дочерний элемент будет находиться на переднем плане? Все это контролируется еще одним присоединенным свойством зависимости элемента управления Canvas. Это свойство под названием Canvas.ZIndex определяет, какой элемент управления должен находиться сверху. Как правило, чем больше значение Canvas.ZIndex, тем выше элемент управления, который задает это присоединенное свойство зависимости. Если свойство Canvas.ZIndex не задано ни для одного дочернего элемента, оно будет задаваться в порядке добавления дочерних элементов в элемент Canvas.

Рассмотрим пример. На следующем рисунке показан элемент управления Canvas с двумя дочерними элементами, один над другим. Он взят из файла CanvasDEMO.xaml в прилагаемом демонстрационном проекте.

И как будет выглядеть код?

Код XAML

<Window

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="WPF_Tour_Beginners_Layout.CanvasDEMO"

    x:Name="Window"

    Title="CanvasDEMO"

    Width="640" Height="480">

        <Canvas Margin="0,0,0,0" Background="White">

            <Rectangle Fill="Blue"

                Stroke="Blue"

                Width="145"

                Height="126"

                Canvas.Left="124" Canvas.Top="122"/>

            <Ellipse Fill="Green"

                Stroke="Green"

                Width="121" Height="100"

                Panel.ZIndex="1"

                Canvas.Left="195" Canvas.Top="191"/>

        </Canvas>

</Window>

Код C#

Canvas canv = new Canvas();

//добавить элемент Canvas в качестве единственного дочернего элемента Window

this.Content = canv;

canv.Margin = new Thickness(0, 0, 0, 0);

canv.Background = new SolidColorBrush(Colors.White);

//Прямоугольник

Rectangle r = new Rectangle();

r.Fill = new SolidColorBrush(Colors.Blue);

r.Stroke = new SolidColorBrush(Colors.Blue);

r.Width = 145;

r.Height = 126;

r.SetValue(Canvas.LeftProperty, (double)124);

r.SetValue(Canvas.TopProperty, (double)122);

canv.Children.Add(r);

//Эллипс

Ellipse el = new Ellipse();

el.Fill = new SolidColorBrush(Colors.Green);

el.Stroke = new SolidColorBrush(Colors.Green);

el.Width = 121;

el.Height = 100;

el.SetValue(Canvas.ZIndexProperty, 1);

el.SetValue(Canvas.LeftProperty, (double)195);

el.SetValue(Canvas.TopProperty, (double)191);

canv.Children.Add(el);

Код VB.NET

Dim canv As New Canvas()

'добавить элемент Canvas в качестве единственного дочернего элемента Window

Me.Content = canv

canv.Margin = New Thickness(0, 0, 0, 0)

canv.Background = New SolidColorBrush(Colors.White)

'Прямоугольник

Dim r As New Rectangle()

r.Fill = New SolidColorBrush(Colors.Blue)

r.Stroke = New SolidColorBrush(Colors.Blue)

r.Width = 145

r.Height = 126

r.SetValue(Canvas.LeftProperty, CDbl(124))

r.SetValue(Canvas.TopProperty, CDbl(122))

canv.Children.Add(r)

'Эллипс

Dim el As New Ellipse()

el.Fill = New SolidColorBrush(Colors.Green)

el.Stroke = New SolidColorBrush(Colors.Green)

el.Width = 121

el.Height = 100

el.SetValue(Canvas.ZIndexProperty, 1)

el.SetValue(Canvas.LeftProperty, CDbl(195))

el.SetValue(Canvas.TopProperty, CDbl(191))

canv.Children.Add(el)

И это, пожалуй, все о базовом элементе управления макета Canvas.

Элемент управления StackPanel

Элемент управления StackPanel также прост. Он располагает свое содержимое по вертикали или горизонтали в зависимости от значения свойства Orientation.

Рассмотрим пример. На следующем рисунке показан элемент управления StackPanel с двумя дочерними элементами, один над другим. Он взят из файла StackPanelDEMO.xaml в прилагаемом демонстрационном проекте.

И как будет выглядеть код?

Код XAML

<Window

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="WPF_Tour_Beginners_Layout.StackPanelDEMO"

    x:Name="Window"

    Title="StackPanelDEMO"

    WindowStartupLocation="CenterScreen"

    Width="640" Height="480">

    <StackPanel Margin="0,0,0,0" Background="White" Orientation="Vertical">

      <Button Content="Im Top of Stack"/>

      <Button Content="Im Bottom Of Stack"/>

  </StackPanel>

</Window>

Код C#

StackPanel sp = new StackPanel();

//добавить элемент StackPanel в качестве единственного дочернего элемента Window

this.Content = sp;

sp.Margin = new Thickness(0, 0, 0, 0);

sp.Background = new SolidColorBrush(Colors.White);

sp.Orientation = Orientation.Vertical;

//Кнопка 1

Button b1 = new Button();

b1.Content = "Im Top of Stack";

sp.Children.Add(b1);

//Кнопка 2

Button b2 = new Button();

b2.Content = "Im Bottom of Stack";

sp.Children.Add(b2);

Код VB.NET

Dim sp As New StackPanel()

'добавить элемент StackPanel в качестве единственного дочернего элемента Window

Me.Content = sp

sp.Margin = New Thickness(0, 0, 0, 0)

sp.Background = New SolidColorBrush(Colors.White)

sp.Orientation = Orientation.Vertical

'Кнопка 1

Dim b1 As New Button()

b1.Content = "Im Top of Stack"

sp.Children.Add(b1)

'Кнопка 2

Dim b2 As New Button()

b2.Content = "Im Bottom of Stack"

sp.Children.Add(b2)

И это, пожалуй, все о базовом элементе управления макета StackPanel.

Элемент управления WrapPanel

И этот элемент управления WrapPanelтакже очень просто использовать (улавливаете закономерность? Макет очень легко использовать в WPF), он просто «оборачивает» свое содержимое.

Рассмотрим пример. На следующем рисунке показан элемент управления WrapPanel с десятью дочерними элементами. Он взят из файла WrapPanelDEMO.xaml в прилагаемом демонстрационном проекте.

И как будет выглядеть код?

Код XAML

<Window

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="WPF_Tour_Beginners_Layout.WrapPanelDEMO"

    x:Name="Window"

    Title="WrapPanelDEMO"

    WindowStartupLocation="CenterScreen"

    Width="640" Height="480">

        <WrapPanel Margin="0,0,0,0" Background="White">

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

            <Rectangle Margin="10,10,10,10" Fill ="Blue" Width="60" Height="60"/>

    </WrapPanel>

</Window>

Код C#

WrapPanel wp = new WrapPanel();

//добавить элемент WrapPanel в качестве единственного дочернего элемента Window

this.Content = wp;

wp.Margin = new Thickness(0, 0, 0, 0);

wp.Background = new SolidColorBrush(Colors.White);

//Добавить прямоугольники

Rectangle r;

for (int i = 0; i <= 10; i++)

{

    r = new Rectangle();

    r.Fill = new SolidColorBrush(Colors.Blue);

    r.Margin = new Thickness(10, 10, 10, 10);

    r.Width = 60;

    r.Height = 60;

    wp.Children.Add(r);

}

Код VB.NET

Dim wp As New WrapPanel()

'добавить элемент WrapPanel в качестве единственного дочернего элемента Window

Me.Content = wp

wp.Margin = New Thickness(0, 0, 0, 0)

wp.Background = New SolidColorBrush(Colors.White)

'Добавить прямоугольники

Dim r As Rectangle

For i As Integer = 0 To 10

    r = New Rectangle()

    r.Fill = New SolidColorBrush(Colors.Blue)

    r.Margin = New Thickness(10, 10, 10, 10)

    r.Width = 60

    r.Height = 60

    wp.Children.Add(r)

Next

И это, пожалуй, все о базовом элементе управления макета WrapPanel.

Элемент управления DockPanel

Элемент управления DockPanel — один из самых полезных (ИМХО) элементов управления макета. Он, вероятно, будет использоваться как базовый в каждом новом окне. По сути, с помощью DockPanel (или двух таких элементов) можно реализовать основной макет большинства современных приложений. Можно закрепить строку меню сверху, затем левую и правую области основного содержимого, а строку состояния снизу. И все это благодаря паре свойств элемента управления DockPanel. Как правило, закрепление любого дочернего элемента в элементе DockPanel управляется следующим присоединенным свойством зависимости:

  • DockPanel.Dock

Этому свойству можно присвоить значения Left, Right, Top или Bottom. Есть еще одно полезное свойство (обычное свойство CLR) элемента управления DockPanel, называемое LastChildFill. Если этому свойству присвоено значение true, последний добавленный дочерний элемент будет занимать все оставшееся свободное пространство. Оно переопределяет свойство DockPanel.Dock, которое может быть уже задано дочерним элементом.

Рассмотрим пример. На следующем рисунке показан элемент управления DockPanel с двумя дочерними элементами, один из которых закреплен сверху, а другой занимает все оставшееся свободное пространство. Пример взят из файла DockPanelDEMO.xaml в прилагаемом демонстрационном проекте.

И как будет выглядеть код?

Код XAML

<Window

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="WPF_Tour_Beginners_Layout.DockPanelDEMO"

    x:Name="Window"

    Title="DockPanelDEMO"

    WindowStartupLocation="CenterScreen"

    Width="640" Height="480">

    <DockPanel Width="Auto" Height="Auto" LastChildFill="True">

        <Rectangle Fill="CornflowerBlue" Stroke="CornflowerBlue"

            Height="20" DockPanel.Dock="Top"/>

        <Rectangle Fill="Orange" Stroke="Orange"  />

    </DockPanel>

</Window>

Код C#

DockPanel dp = new DockPanel();

dp.LastChildFill = true;

//Это эквивалентно Width="Auto" в XAML, кроме элементов GridColumn Width/Height и GridRow Width/Height

dp.Width = Double.NaN;

dp.Height = Double.NaN;

//добавить элемент WrapPanel в качестве единственного дочернего элемента Window

this.Content = dp;

//Добавить прямоугольники

Rectangle rTop = new Rectangle();

rTop.Fill = new SolidColorBrush(Colors.CornflowerBlue);

rTop.Stroke = new SolidColorBrush(Colors.CornflowerBlue);

rTop.Height = 20;

dp.Children.Add(rTop);

rTop.SetValue(DockPanel.DockProperty,Dock.Top);

Rectangle rFill = new Rectangle();

rFill.Fill = new SolidColorBrush(Colors.Orange);

rFill.Stroke = new SolidColorBrush(Colors.Orange);

dp.Children.Add(rFill);

Код VB.NET

Dim dp As New DockPanel()

dp.LastChildFill = True

dp.Width = [Double].NaN

'Это эквивалентноWidth="Auto" в XAML

dp.Height = [Double].NaN

'Это эквивалентноHeight="Auto" в XAML

'добавить элемент DockPanel в качестве единственного дочернего элемента Window

Me.Content = dp

'Добавить прямоугольники

Dim rTop As New Rectangle()

rTop.Fill = New SolidColorBrush(Colors.CornflowerBlue)

rTop.Stroke = New SolidColorBrush(Colors.CornflowerBlue)

rTop.Height = 20

dp.Children.Add(rTop)

rTop.SetValue(DockPanel.DockProperty, Dock.Top)

Dim rFill As New Rectangle()

rFill.Fill = New SolidColorBrush(Colors.Orange)

rFill.Stroke = New SolidColorBrush(Colors.Orange)

dp.Children.Add(rFill)

И это, пожалуй, все о базовом элементе управления макета DockPanel.

Элемент Grid

Элемент Grid— самый сложный элемент управления макета в WPF (на сегодня). Он немного похож на табличный элемент управления HTML, в котором можно задавать строки и столбцы, а ячейки могут содержать несколько строк или столбцов. Для свойств Width и Height столбцов и строк может применяться странный синтаксис с использованием символа звездочки (*), предоставляемый с помощью класса GridLength. Его можно представить в виде процентного разделителя свободного места. Рассмотрим следующую разметку:

<Grid.ColumnDefinitions>

    <ColumnDefinition Width="40"/>

    <ColumnDefinition Width="*"/>

    <ColumnDefinition Width="2*"/>

</Grid.ColumnDefinitions>

Здесь в элементе Grid объявлены три элемента ColumnDefinition. Ширина первого элемента ColumnDefinition зафиксирована (40 пикселей), а оставшееся пространство делится между двумя другими элементами ColumnDefinition, причем последнему из них выделяется в два раза больше места, чем предпоследнему. Этот же принцип применяется в отношении RowDefinition.

Для уведомления системы макета WPF о ячейке, к которой относятся дочерние элементы элемента Grid, используются следующие присоединенные свойства зависимости (нумерация индекса начинается с 0):

  • Grid.Column
  • Grid.Row

А для задания количества строк или столбцов, занимаемых ячейкой, используются следующие присоединенные свойства зависимости (значения начинаются с 1):

  • Grid.ColumnSpan
  • Grid.RowSpan

При умелом использовании элемента управления Grid можно имитировать практически любые другие элементы управления макета. Оставляю эту задачу в качестве упражнения для читателя.

Рассмотрим пример использования Grid. На следующем рисунке показан элемент управления Grid с тремя столбцами, одной строкой и двумя дочерними элементами. Первый дочерний элемент находится в столбце 1, а второй занимает столбцы 2 и 3, поскольку его свойству Grid.ColumnSpan присвоено значение 2. Пример взят из файла GridDEMO.xaml в прилагаемом демонстрационном проекте.

И как будет выглядеть код?

Код XAML

<Window

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    x:Class="WPF_Tour_Beginners_Layout.GridDEMO"

    x:Name="Window"

    Title="GridDEMO"

    WindowStartupLocation="CenterScreen"

    Width="640" Height="480">

    <Grid Width="Auto" Height="Auto" >

        <Grid.ColumnDefinitions>

            <ColumnDefinition Width="40"/>

            <ColumnDefinition Width="*"/>

            <ColumnDefinition Width="2*"/>

        </Grid.ColumnDefinitions>

        <Rectangle Fill="Aqua" Grid.Column="0" Grid.Row="0"/>

        <Rectangle Fill="Plum" Grid.Column="1" Grid.ColumnSpan="2"/>

    </Grid>

</Window>

Код C#

Grid grid = new Grid();

grid.Width = Double.NaN;    //this is the same as Width="Auto" in XAML

grid.Height = Double.NaN;   //this is the same as Height="Auto" in XAML

//добавить элемент Grid в качестве единственного дочернего элемента Window

this.Content = grid;

//Столбец 1

ColumnDefinition cd1 = new ColumnDefinition();

cd1.Width = new GridLength(40);

grid.ColumnDefinitions.Add(cd1);

//Столбец 2

ColumnDefinition cd2 = new ColumnDefinition();

cd2.Width = new GridLength(1, GridUnitType.Star);

grid.ColumnDefinitions.Add(cd2);

//Столбец 3

ColumnDefinition cd3 = new ColumnDefinition();

cd3.Width = new GridLength(2, GridUnitType.Star);

grid.ColumnDefinitions.Add(cd3);

//Добавим ячейки в элемент grid

Rectangle r1c1 = new Rectangle();

r1c1.Fill = new SolidColorBrush(Colors.Aqua);

r1c1.SetValue(Grid.ColumnProperty, 0);

r1c1.SetValue(Grid.RowProperty, 0);

grid.Children.Add(r1c1);

Rectangle r1c23 = new Rectangle();

r1c23.Fill = new SolidColorBrush(Colors.Plum);

r1c23.SetValue(Grid.ColumnProperty, 1);

r1c23.SetValue(Grid.ColumnSpanProperty, 2);

grid.Children.Add(r1c23);

Код VB.NET

Dim grid As New Grid()

grid.Width = [Double].NaN

'Это эквивалентноWidth="Auto" в XAML

grid.Height = [Double].NaN

'Это эквивалентноHeight="Auto" в XAML

'добавить элемент Grid в качестве единственного дочернего элемента Window

Me.Content = grid

'Столбец 1

Dim cd1 As New ColumnDefinition()

cd1.Width = New GridLength(40)

grid.ColumnDefinitions.Add(cd1)

'Столбец 2

Dim cd2 As New ColumnDefinition()

cd2.Width = New GridLength(1, GridUnitType.Star)

grid.ColumnDefinitions.Add(cd2)

'Столбец 3

Dim cd3 As New ColumnDefinition()

cd3.Width = New GridLength(2, GridUnitType.Star)

grid.ColumnDefinitions.Add(cd3)

'Добавим ячейки в элемент grid

Dim r1c1 As New Rectangle()

r1c1.Fill = New SolidColorBrush(Colors.Aqua)

r1c1.SetValue(Grid.ColumnProperty, 0)

r1c1.SetValue(Grid.RowProperty, 0)

grid.Children.Add(r1c1)

Dim r1c23 As New Rectangle()

r1c23.Fill = New SolidColorBrush(Colors.Plum)

r1c23.SetValue(Grid.ColumnProperty, 1)

r1c23.SetValue(Grid.ColumnSpanProperty, 2)

grid.Children.Add(r1c23)

Элемент управления Gridдовольно сложный, поэтому я настоятельно рекомендую вам более подробно изучить его. С помощью элемента Gridможно делать массу полезного, например изменять размеры столбцов и строк с помощью элементов Grid Splitter, а также использовать единые размеры для нескольких сеток (SizeGroup). Так что уделите элементу Grid больше внимания.

Соберем все вместе

Теперь можно собрать вместе все полученные знания и попробовать создать нечто столь же прекрасное, как на рисунке ниже.

Я, конечно, шучу. Давайте лучше возьмем некоторый гипотетический макет. Возьмем типичный, проверенный годами макет, в котором есть строка меню сверху, основная область содержимого и строка состояния внизу. Попробуем воспроизвести такой макет (созданный в виде простого приложения WinForm).

Кажется, что дал вам все, что нужно для создания такого макета в WPF. В качестве подсказки могу сказать, что для выполнения этого упражнения вам потребуются элементы управления StackPanel, DockPanel и Grid.

Если интересно, вот как я сделал это (в этот раз только XAML).

<Window x:Class="WPF_Tour_Beginners_Layout.PuttingItAllTogether"

    xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"

    xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"

    WindowStartupLocation="CenterScreen"

    Title="PuttingItAllTogether" Width="640" Height="480" >

    <DockPanel Width="Auto" Height="Auto" LastChildFill="True">

        <!--Верхняя область меню-->

        <Menu Width="Auto" Height="20" Background="#FFA9D1F4"

            DockPanel.Dock="Top">

            <!--Меню File -->

            <MenuItem Header="File">

                <MenuItem Header="Save"/>

                <Separator/>

                <MenuItem Header="Exit"/>

            </MenuItem>

            <!--Меню About-->

            <MenuItem Header="Help">

                <MenuItem Header="About"/>

            </MenuItem>

        </Menu>

        <!--Нижняя область строки состояния объявляется до средней области,

        поскольку я хотел заполнить весь низ окна,

        что не удалось бы сделать при наличии зафиксированной слева панели-->

        <StackPanel Width="Auto" Height="31" Background="#FFCAC5C5"

            Orientation="Horizontal" DockPanel.Dock="Bottom">

            <Label Width="155" Height="23" Content="Status Bar Message...."

            FontFamily="Arial" FontSize="10"/>

        </StackPanel>

        <!--Левая область основного содержимого-->

        <StackPanel Width="136" Height="Auto" Background="White">

            <Button Margin="5,5,5,5" Width="Auto" Height="26" Content="button1"/>

            <Button Width="126" Height="26" Content="button2" Margin="5,5,5,5"/>

            <Button Width="126" Height="26" Content="button3" Margin="5,5,5,5"/>

        </StackPanel>

        <!--Правая область основного содержимого. ОБРАТИТЕ ВНИМАНИЕ, что этот элемент Grid — последний дочерний элемент,

        поэтому он занимает все оставшееся место -->

        <Grid Width="Auto" Height="Auto" Background="#FFCC9393">

            <Grid.ColumnDefinitions>

                <ColumnDefinition Width="*"/>

                <ColumnDefinition Width="*"/>

            </Grid.ColumnDefinitions>

            <Grid.RowDefinitions>

                <RowDefinition Height="*"/>

                <RowDefinition Height="*"/>

            </Grid.RowDefinitions>

            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="0" Grid.Column="0"/>

            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="0" Grid.Column="1"/>

            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="1" Grid.Column="0"/>

            <Rectangle Fill="Aqua" Margin="10,10,10,10" Grid.Row="1" Grid.Column="1"/>

        </Grid>

    </DockPanel>

</Window>

В результате получается следующее окно.

А вот еще пара полезных приемов:

  • Элемент Grid для правой области содержимого должен быть последним объявленным дочерним элементом, чтобы занимать оставшееся пространство, которое должен заполнить родительский элемент DockPanel, поскольку LastChildFill="True".
  • Элемент StackPanel, используемый для создания строки состояния, должен находиться до любого другого дочернего элемента с объявленными значениями DockPanel.Dock="Left" или DockPanel.Dock="Right". Если бы строке состояния StackPanel предшествовал другой элемент управления, строка состояния StackPanel не смогла бы занимать всю доступную ширину, поскольку это место было бы занято дочерним элементом со значениями свойства DockPanel.Dock="Left" или DockPanel.Dock="Right". Попробуйте, и вы увидите, что я имею в виду. Просто переместите код XAML строки состояния ниже в файле XAML, например в конец файла.

Проблемы эффективности

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

Но и не совсем плохо. В платформе WPF доступен еще один прием, который может помочь в этой ситуации. Присоединенное свойство зависимости VirtualizingStackPanel.IsVirtualizing в элементе ListBox обеспечит виртуализацию отображения элементов внутреннего элемента управления StackPanel в элементе ListBox. Но что такое этот элемент VirtualizingStackPanel?

Виртуализация панели означает, что создаются только видимые элементы. Остальные элементы не отображаются. Рассмотрим создание элемента ListBox с изображениями, привязанного к базе данных, содержащей 100 000 строк. На загрузку такого элемента уйдет очень много времени. Если используется виртуализированная панель, в интерфейсе пользователя будут созданы только видимые изображения. При прокрутке списка элементы, видимые в данный момент, будут уничтожаться, а новые видимые элементы загружаться в интерфейс пользователя. Только одна панель поддерживает виртуализацию, и это панель VirtualizingStackPanel. Новые панели виртуализации необходимо разрабатывать самостоятельно.

Пользовательские макеты

Также можно создавать собственные панели, реализующие все возможности пользовательских.

Я мог бы попробовать придумать какой-нибудь новый способ сделать это, но Пол Таллет (Paul Tallet) уже все сделал в своей статье Панель FishEye на сайте CodeProject. Так что за текст этого небольшого раздела благодарю Пола Таллета.

Если вам нужна собственная пользовательская панель, нужно создать класс, производный от System.Windows.Controls.Panel, и применить два переопределения: MeasureOverrideиLayoutOverride. Они реализуют двухпроходную систему макета, где на этапе Measure родительский элемент обращается к вашему элементу,чтобы узнать,сколько места ему требуется. Ваш элемент,как правило,опрашивает дочерние элементы о необходимом месте и передает полученную информацию родительскому элементу. На втором проходе кто-нибудь принимает решение о размерах и передает конечный размер в метод Arrange Override, где вы сообщаете своим дочерним элементам их размер и размещаете их. Обратите внимание,что при любом действии, которое влияет на макет (например, при изменении размера окна), все эти операции повторятся с новыми размерами.

Массу ссылок на пользовательские панели можно найти в блоге Роба Рели (Rob Relyea).

На этом закончим

Уф! Полагаю, мы хорошо поработали, и теперь у вас есть базовые сведения о макете в WPF.

Но... если вы хотите еще...

Как я уже говорил, эта серия статей для меня — задача не из легких, так что, если вы хотите большего, голосуйте и оставляйте комментарии, чтобы я знал, есть ли смысл заниматься подготовкой остальных статей на этом уровне.

С уважением,

Саша Барбер.