Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
WPF имеет возможность очень тонко настраивать внешний вид объектов. Внешний вид объектов зависит от свойств, которые задаются как при помощи прямых настроек, так и при помощи кистей (Brushes). Список свойств включает следующие:
Кисти используются для задания внешнего вида объектов и могут быть следующих типов:
Кисти можно конвертировать в ресурсы и многократно применять к различным объектам.
В WPF есть средства достигать специальных растровых эффектов (Bitmap effects):
Создайте новый пустой
проект ( Empty Project ) с именем WpfApp1 в
новом решении WpfApp. Проследите, чтобы
обязательно использовалась версия библиотеки не ниже .NET Framework
3.0
Вызовите контекстное
меню проекта и через команду Properties установите во вкладке Application параметр Output
type в значение Windows Application
Добавьте к узлу References проекта
ссылки на следующие библиотеки .NET Framework, составляющие основу платформы
WPFPresentationCore PresentationFrameworkWindowsBaseSystem - эта общая библиотека для запуска приложения
Добавьте к проекту
новый файл с именем CreateBrushes.cs
Заполните файл следующим
кодомusing System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Controls;
using System.Reflection;
namespace WpfApp1
{
class CreateBrushes : NavigationWindow
{
// Точка входа
[STAThread] // Атрибут для однопоточного приложения
public static void Main()
{
Application app = new Application();
app.Run(new CreateBrushes());
}
// Конструктор
public CreateBrushes()
{
this.Width = 300;
this.Height = 300;
this.Title = "Каркас приложения Упражнения 1";
//this.ShowsNavigationUI = false;// Скрыть навигационный интерфейс
}
}
}
Мы решили построить многостраничное приложение для многих примеров упражнения
и содали каркас для страниц содержимого с навигационным узлом. Навигационный
узел можно отключить через свойство ShowsNavigationUI. Мы создали каркас с
нуля и пока будем использовать только чистый код C#, не применяя разметку XAML.
Страничный каркас создается классом NavigationWindow, производным от класса Window. Мы расширяем библиотечный класс NavigationWindow пользовательским классом CreateBrushes, следовательно все наследуемые им от базового класса Window члены
будут доступны в коде и нашего класса-расширения.
Запустите приложение
- появится мертвый каркас
Далее, по мере выполнения примеров упражнения, мы будем добавлять в каркас
все новые страницы, которые построим из расширений библиотечного класса Page,
наделив их соответствующими свойствами. Одновременно мы будем задействовать
навигационный механизм каркаса, который предоставляет базовый класс NavigationWindow.
Начнем с создания первой страницы, на которой по всей клиентской области будет
расположена кнопка. Кнопку будем закрашивать однородным цветом градаций серого,
в зависимости от удаления курсора от центра кнопки. Страницу построим вручную
как класс-расширение на базе библиотечного класса Page.
Дополните файл CreateBrushes.cs кодом создания страницы Page1namespace WpfApp1
{
// Класс-расширение страницы Page1
class Page1 : Page
{
// Создаем и инициализируем поле кисти белым цветом
SolidColorBrush brush = new SolidColorBrush(Colors.White);
Button btnPage1 = new Button();
public Page1()
{
this.WindowTitle = "Page1: Кисть SolidColorBrush";
// Создали кнопку
btnPage1.Content = "Кнопка";
btnPage1.Background = brush;
// Зарегистрировали обработчики
btnPage1.Click += new RoutedEventHandler(btnPage1_Click);
btnPage1.MouseMove += new MouseEventHandler(btnPage1_MouseMove);
this.Content = btnPage1; // Поместили кнопку в страницу
}
// Меняет цвет фона кнопки на Page1
void btnPage1_MouseMove(object sender, MouseEventArgs e)
{
// Чистая ширина клиентской области без рамки окна
double width = btnPage1.ActualWidth;
// Чистая высота клиентской области без рамки и заголовка окна
double height = btnPage1.ActualHeight;
Point ptMouse = e.GetPosition(btnPage1);// Точка курсора на клиентской области окна
Point ptCenter = new Point(width / 2, height / 2);// Центр клиентской области
Vector vectMouse = ptMouse - ptCenter;// Отклонение курсора от центра
double angle = Math.Atan2(vectMouse.Y, vectMouse.X);// Угол нахождения курсора
Vector vectEllipse = new Vector(width / 2 * Math.Cos(angle),
height / 2 * Math.Sin(angle));// Вписанный эллипс
// Изоклина (в центре кнопки черная), округляется до одного байта
Byte byLevel = (byte)(255 * (Math.Min(1, vectMouse.Length / vectEllipse.Length)));
Color color = brush.Color;// Привязываем к полю
color.R = color.G = color.B = byLevel;// Цвета, пропорциональные изоклине
brush.Color = color;// Меняем цвет фона окна изоклиной от равномерного черного цвета
}
// Переход на следующую страницу
void btnPage1_Click(object sender, RoutedEventArgs e)
{
}
}
}
Добавьте код создания
объекта начальной страницы в конструктор класса каркаса приложенияnamespace WpfApp1
{
class CreateBrushes : NavigationWindow
{
// Точка входа
[STAThread] // Атрибут для однопоточного приложения
public static void Main()
{
Application app = new Application();
app.Run(new CreateBrushes());
}
// Конструктор
public CreateBrushes()
{
this.Width = 300;
this.Height = 300;
//this.Title = "Каркас приложения Упражнения 1";
//this.ShowsNavigationUI = false;// Скрыть навигационный интерфейс
// Создали начальную страницу
Page1 page1 = new Page1();
this.Content = page1; // Поместили страницу в каркас
}
}
}
Запустите приложение
и вы увидите первую страницу внутри каркаса, на которой расположена кнопка,
меняющая градации серого цвета при движении над ней курсора мыши по направлению
к центру кнопки
Кнопка нажимается, но ничего не делает, только меняет цвет.
Создайте новую страницу Page2, на которую пользователь перейдет по щелчку на кнопкеnamespace WpfApp1
{
// Класс-расширение страницы Page2
class Page2 : Page
{
int index = 0;// Номер цвета
PropertyInfo[] props;// Массив свойств
public Page2()
{
// Применяем рефлексию для чтения свойств класса Brushes
props = typeof(Brushes).GetProperties(
BindingFlags.Public | BindingFlags.Static);
// Компоновочная панель
StackPanel stackPanel = new StackPanel();// Создаем
this.Content = stackPanel;// Присоединяем к странице
Button btn = new Button();
btn.Name = "ButtonNextColor";
btn.Content = "NextColor >";
btn.Click += new RoutedEventHandler(btn_Click);
stackPanel.Children.Add(btn);// Добавляем в панель
btn = new Button();
btn.Content = "< PreviousColor";
btn.Click += new RoutedEventHandler(btn_Click);
stackPanel.Children.Add(btn);
btn = new Button();
btn.Content = "Next Page3";
btn.Click += new RoutedEventHandler(btnPage2_Click);
stackPanel.Children.Add(btn);
// Возбуждается при каждом отображении страницы
this.Loaded += new RoutedEventHandler(Page2_Loaded);
}
void Page2_Loaded(object sender, RoutedEventArgs e)
{
// Вариант
//this.NavigationService.LoadCompleted += NavigationService_LoadCompleted;
SetTitleAndBackground();
}
void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)
{
// Вариант
//SetTitleAndBackground();
}
// Обработчики кнопок смены заголовка и цвета страницы
void btn_Click(object sender, RoutedEventArgs e)
{
// Распознаем кнопку по имени и корректируем индекс
if (((Button)sender).Name == "ButtonNextColor")
index += 1;
else
index += props.Length - 1;
index %= props.Length;// Деление по модулю
SetTitleAndBackground();
}
// Установка заголовка и цвета фона страницы
void SetTitleAndBackground()
{
this.WindowTitle = "Page2: Имя цвета кисти - " + props[index].Name;
this.Background = (Brush)props[index].GetValue(null, null);
}
// Переход на следующую страницу
void btnPage2_Click(object sender, RoutedEventArgs e)
{
}
}
}
Мы разработали новый класс, определяющий функциональность страницы Page2.
Осталось добавить в обработчик щелчка кнопки первой страницы код для перехода
на эту новую страницу.
Добавьте в обработчик btnPage1_Click() класса Page1 следующий код// Переход на следующую страницу
Page2 page2;
void btnPage1_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page2 = new Page2();// Создаем только один раз
this.NavigationService.Navigate(page2);
}
Мы создаем объект страницы Page2 только при первом ее отображении
и сохраняем в поле, поэтому в процессе навигации при всех последующих ее отображениях
переустанавливать фон и заголовок необязательно, а обработчик события Loaded можно не регистрировать. Такой прием мы применим и для следующих страниц.
Запустите приложение
и испытайте работу класса Page2 в составе навигационного каркаса
Одну из альтернатив однородных кистей составляют градиентные кисти с постепенным
переходом шлейфа двух и более цветов. В простейшем случае для создания линейной
градиентной кисти достаточно двух крайних точек с определенными в них цветами.
Градиентный шлейф будет строиться вдоль соединяющей эти точки прямой линии
перпендикулярно ей и заливать всю поверхность
Конструктор объекта линейной градиентной кисти имеет несколько перегрузок,
в одних из которых задаются две точки и два цвета; два цвета, начальная точка
привязки вектора градиента и угол его направления закраски. Точки могут задавать
относительные (по умолчанию) и абсолютные координаты в рамках ограничивающего
прямоугольника, что определяется свойством MappingMode и его значением из перечисления BrushMappingMode.
Создайте новую страницу Page3 в файле CreateBrushes.cs, демонстрирующую один из вариантов
линейной градиентной
закраскиnamespace WpfApp1
{
class Page3 : Page
{
public Page3()
{
this.WindowTitle = "Page3: Кисть LinearGradientBrush";
Button btn = new Button();
btn.Content="Next Page4";
btn.Click += new RoutedEventHandler(btn3_Click);
this.Content = btn;
// Создание и присоединение градиента
LinearGradientBrush brush = new LinearGradientBrush(
Colors.Red, Colors.Blue, new Point(0, 0), new Point(1, 1));
btn.Background = brush;
}
// Переход на следующую страницу
void btn3_Click(object sender, RoutedEventArgs e)
{
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page2 код создания экземпляра Page3// Переход на следующую страницу
Page3 page3;
void btnPage2_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page3 = new Page3();// Создаем только один раз
this.NavigationService.Navigate(page3);
}
Запустите приложение
и испытайте работу класса Page3 в составе навигационного каркаса
Если отрезок меньше LinearGradientBrush определяется наследуемым от GradientBrush свойством SpreatMethod и
его значением из перечисления GradientSpreadMethod.
Создайте новую страницу Page4 в файле CreateBrushes.cs, демонстрирующую волнообразную градиентную
закраскуnamespace WpfApp1
{
class Page4 : Page
{
public Page4()
{
this.WindowTitle = "Page4: GradientSpreadMethod.Reflect";
Button btn = new Button();
btn.Content = "Next Page5";
btn.Click += new RoutedEventHandler(btn4_Click);
this.Content = btn;
// Создание и присоединение градиента
LinearGradientBrush brush = new LinearGradientBrush(
Colors.Red, Colors.Blue, new Point(0, 0), new Point(0.25, 0.25));
brush.SpreadMethod = GradientSpreadMethod.Reflect;
btn.Background = brush;
}
// Переход на следующую страницу
void btn4_Click(object sender, RoutedEventArgs e)
{
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page3 код создания экземпляра Page4// Переход на следующую страницу
Page4 page4;
void btn3_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page4 = new Page4();// Создаем только один раз
this.NavigationService.Navigate(page4);
}
Запустите приложение
и испытайте работу класса Page4 с периодической сменой направления линейного
градиента
У GradientStops, содержащая объекты GradientStop.
Каждый из объектов коллекции задает начальный цвет градиента и правую границу
полного перекрытия этим цветом предыдущего. В такой модели дополнительно используются
точки границ закраски, определяемые свойствами StartPoint и EndPoint. Для иллюстрации
создадим страницу, поместим на нее кнопку и закрасим кнопку вертикальными градиентами.
Создайте новую страницу Page5 в файле CreateBrushes.cs, поместите на нее кнопку и закрасьте вертикальными
градиентами с помощью следующего кодаnamespace WpfApp1
{
class Page5 : Page
{
public Page5()
{
this.WindowTitle = "Page5: Закраска цветами радуги";
Button btn = new Button();
this.Content = btn;
btn.Content = "Next Page6";
btn.Click += new RoutedEventHandler(btn5_Click);
// Создание и присоединение градиента
LinearGradientBrush brush = new LinearGradientBrush();
btn.Background = brush;
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 0);
// Цвета радуги
brush.GradientStops.Add(new GradientStop(Colors.Red, 0));
brush.GradientStops.Add(new GradientStop(Colors.Orange, .17));
brush.GradientStops.Add(new GradientStop(Colors.Yellow, .33));
brush.GradientStops.Add(new GradientStop(Colors.Green, .5));
brush.GradientStops.Add(new GradientStop(Colors.CornflowerBlue, .67));
brush.GradientStops.Add(new GradientStop(Colors.Blue, .84));
brush.GradientStops.Add(new GradientStop(Colors.BlueViolet, 1));
}
// Переход на следующую страницу
void btn5_Click(object sender, RoutedEventArgs e)
{
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page4 код создания экземпляра Page5// Переход на следующую страницу
Page5 page5;
void btn4_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page5 = new Page5();// Создаем только один раз
this.NavigationService.Navigate(page5);
}
Запустите приложение
и испытайте работу класса Page5, рисующего последовательные переливы цветов
радуги
RadialGradientBrush. Следующий пример
демонстрирует работу этого градиента
Создайте новую страницу Page6 в файле CreateBrushes.cs и закрасьте ее с помощью следующего кодаnamespace WpfApp1
{
class Page6 : Page
{
public Page6()
{
this.WindowTitle = "Page6: Радиальный градиент";
// Создание и присоединение градиента
RadialGradientBrush brush = new RadialGradientBrush();
this.Background = brush;
// Цвета радуги
brush.GradientStops.Add(new GradientStop(Colors.Red, 0));
brush.GradientStops.Add(new GradientStop(Colors.Orange, .17));
brush.GradientStops.Add(new GradientStop(Colors.Yellow, .33));
brush.GradientStops.Add(new GradientStop(Colors.Green, .5));
brush.GradientStops.Add(new GradientStop(Colors.CornflowerBlue, .67));
brush.GradientStops.Add(new GradientStop(Colors.Blue, .84));
brush.GradientStops.Add(new GradientStop(Colors.BlueViolet, 1));
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page5 код создания экземпляра Page6// Переход на следующую страницу
Page6 page6;
void btn5_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page6 = new Page6();// Создаем только один раз
this.NavigationService.Navigate(page6);
}
Запустите приложение
и испытайте работу кисти с 
Все элементы управления WPF исчерпывающе описаны в электронной справке http://msdn.microsoft.com/ru-ru/library/ms752324.aspx
В наших упражнениях мы проиллюстрируем применение некоторых из них.
Добавьте к решению
новый проект типа WPF Application с именем WpfApp2 и
назначьте его стартовым
Скорректируйте разметку
окна в файле Window1.xaml следующим образом<Window x:Class="WpfApp2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Menu, ToolBar, TabControl, ToolTip"
SizeToContent="WidthAndHeight"
ToolTipService.InitialShowDelay="0"
ToolTipService.ShowDuration="500000"
>
<Window.ToolTip>
<ToolTip x:Name="toolTip"
Placement="RelativePoint"
VerticalOffset="10"
/>
</Window.ToolTip>
<DockPanel Background="LightGray">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="E_xit" Click="ExitClicked" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Cut" />
<MenuItem Header="C_opy" />
<MenuItem Header="_Paste" />
</MenuItem>
</Menu>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Width="23">A</Button>
<Button Width="23">B</Button>
<Button Width="23">C</Button>
<Button Width="23">D</Button>
</ToolBar>
<ToolBar Header="Search">
<TextBox Width="100" />
<Button Width="23">Go</Button>
</ToolBar>
</ToolBarTray>
<TabControl>
<TabItem Header="Page1">
<StackPanel>
<Expander Header="Секция 1" IsExpanded="True">
<GroupBox Header="Подсекция A">
<Label>Некоторый текст!</Label>
</GroupBox>
</Expander>
</StackPanel>
</TabItem>
<TabItem Header="Page2">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Name="value" Margin="10" Text="255" Width="20" />
<Slider Name="slider" Width="240"
Minimum="0" Maximum="255" Value="255"
ValueChanged="slider_ValueChanged" />
</StackPanel>
</TabItem>
<TabItem Header="Page3">
<UniformGrid Rows="3" Columns="3">
<Button ToolTip="Кнопка 1" Margin="5">1</Button>
<Button ToolTip="Кнопка 2" Margin="5">2</Button>
<Button ToolTip="Кнопка 3" Margin="5">3</Button>
<Button ToolTip="Кнопка 4" Margin="5">4</Button>
<Button ToolTip="Кнопка 5" Margin="5">5</Button>
<Button ToolTip="Кнопка 6" Margin="5">6</Button>
<Button ToolTip="Кнопка 7" Margin="5">7</Button>
<Button ToolTip="Кнопка 8" Margin="5">8</Button>
<Button ToolTip="Кнопка 9" Margin="5">9</Button>
</UniformGrid>
</TabItem>
</TabControl>
</DockPanel>
</Window>
Зарегистрированные
обработчики в файле 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 WpfApp2
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void ExitClicked(object sender, RoutedEventArgs e)
{
this.Close();
}
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
value.Text = ((int)slider.Value).ToString();
}
}
}
Запустите приложение
- получим действующее меню, две панели инструментов и элемент с вкладками


Элемент управления Thumb (манипулятор) инкапсулирует область, которую можно перемещать, и генерирует все необходимые события.
Добавьте к решению WpfApp новый проект с именем WpfApp3 и
назначьте его стартовым
Наполните файл Window1.xaml следующим содержимым<Window x:Class="WpfApp3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Thumb, Border, Popup"
Height="300"
Width="300"
Background="LightGray"
>
<TabControl>
<!--Элемент, описывающий перемещаемую область-->
<TabItem Header="Thumb">
<Canvas>
<Thumb
Name="thumb1"
Background="Red"
Canvas.Left="5"
Canvas.Top="5"
Width="23"
Height="23"
DragStarted="thumb1_DragStarted"
DragDelta="thumb1_DragDelta"
/>
</Canvas>
</TabItem>
<!--Применение рамок Border-->
<TabItem Header="Border">
<StackPanel>
<Border
BorderThickness="3"
CornerRadius="0"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет1
</TextBlock>
</Border>
<Border
BorderThickness="3"
CornerRadius="3"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет2
</TextBlock>
</Border>
<Border
BorderThickness="10,1,10,1"
CornerRadius="10"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет3
</TextBlock>
</Border>
<Border
BorderThickness="4,1,4,1"
CornerRadius="0,15,0,15"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет4
</TextBlock>
</Border>
</StackPanel>
</TabItem>
<!--Применение элемента Popup-->
<TabItem Header="Popup">
<StackPanel>
<Button Name="btn" Click="btn_Click">Toggle</Button>
<Popup Name="popup"
PopupAnimation="Fade"
Placement="Mouse"
>
<Button Background="Yellow" Foreground="Red">
Кнопка создана в Popup
</Button>
</Popup>
</StackPanel>
</TabItem>
</TabControl>
</Window>
Свойство BorderThickness задает толщину границы элемента.
Наполните файл 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;
using System.Windows.Controls.Primitives;
namespace WpfApp3
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
// Возвращаем Thumb в исходное состояние
Canvas.SetLeft(thumb1, 5);
Canvas.SetTop(thumb1, 5);
}
double originalLeft, originalTop;
private void thumb1_DragStarted(object sender, DragStartedEventArgs e)
{
originalLeft = Canvas.GetLeft(thumb1);
originalTop = Canvas.GetTop(thumb1);
}
private void thumb1_DragDelta(object sender, DragDeltaEventArgs e)
{
double left = originalLeft + e.HorizontalChange;
double top = originalTop + e.VerticalChange;
Canvas.SetLeft(thumb1, left);
Canvas.SetTop(thumb1, top);
originalLeft = left;
originalTop = top;
}
private void btn_Click(object sender, RoutedEventArgs e)
{
popup.IsOpen = !popup.IsOpen;
}
}
}
Запустите приложение
- получим иллюстрацию элементов Thumb , Border, Popup


Элемент ScrollViewer прокручивает окно, если интерфейс с дочерними элементами
в нем не помещается. Элемент Viewbox принимает одного потомка (можно контейнер)
и масштабирует его пропорционально своим размерам.
Добавьте к решению WpfApp новый проект с именем WpfApp4 и
назначьте его стартовым
Заполните файл разметки
следующим кодом<Window x:Class="WpfApp4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ScrollViewer, Viewbox, StackPanel" Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="ScrollViewer">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Name="textBox" Grid.Row="0" Margin="5" />
<Button Grid.Row="1" Margin="5" Click="Add_Click">Add</Button>
<ListBox Name="listBox" Grid.Row="2" Margin="5" />
</Grid>
</ScrollViewer>
</TabItem>
<TabItem Header="Viewbox">
<Viewbox>
<TextBlock>Текст</TextBlock>
</Viewbox>
</TabItem>
<TabItem Header="StackPanel">
<StackPanel Orientation="Vertical" Background="Aqua">
<Button HorizontalAlignment="Center" Width="75" Margin="5">
Center
</Button>
<Button HorizontalAlignment="Left" Width="75">
<Button.LayoutTransform>
<RotateTransform Angle="-45" />
</Button.LayoutTransform>
Left (-45)
</Button>
<Button HorizontalAlignment="Right" Width="75">
<Button.LayoutTransform>
<RotateTransform Angle="45" />
</Button.LayoutTransform>
Right (45)
</Button>
<Button HorizontalAlignment="Stretch" Margin="5">Stretch</Button>
</StackPanel>
</TabItem>
</TabControl>
</Window>
Заполните кодовую часть
следующим кодом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 WpfApp4
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
textBox.Text = "Привет студентам от Снеткова В.М.!";
}
private void Add_Click(object sender, RoutedEventArgs e)
{
listBox.Items.Add(textBox.Text);
}
}
}
Запустите приложение

Попробуйте порастягивать
окно с дочерним элементом Viewbox - этот элемент масштабирует своего потомка
Наиболее употребительными панелями динамического размещения интерфейсных элементов являются:
CanvasStackPanelDockPanelUniformGridGridПанели обеспечивают типичные способы размещения дочерних элементов и в большинстве
случаев этого бывает достаточно. Все панели наследуют от абстрактного класса Panel, имеющего, в свою очередь, в качестве базового класс System.Windows.FrameworkElement.
Наиболее развитым менеджером размещения является панель Grid. Сами панели являются
невидимыми, если явно не задать их фон, а их присутствие обнаруживается только
по влиянию на дочерние элементы.
Добавьте к решению
новый проект WpfApp5 и назначьте его
стартовым
Заполните файл разметки
следующим кодом<Window x:Class="WpfApp5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Менеджер размещения Canvas" Height="300" Width="300"
Background="LightGray">
<TabControl>
<TabItem Header="Canvas 1">
<Canvas Width="200" Height="100" Background="Aqua">
<Button Canvas.Left="2" Canvas.Top="2">Left, Top</Button>
<Button Canvas.Right="2" Canvas.Top="2">Right, Top</Button>
<Button Canvas.Left="2" Canvas.Bottom="2">Left, Bottom</Button>
<Button Canvas.Right="2" Canvas.Bottom="2">Right, Bottom</Button>
</Canvas>
</TabItem>
<TabItem Header="Canvas 2">
<Canvas HorizontalAlignment="Center" VerticalAlignment="Center" Background="Aqua">
<Button Canvas.Left="2" Canvas.Top="2">Left, Top</Button>
<Button Canvas.Right="2" Canvas.Top="2">Right, Top</Button>
<Button Canvas.Left="2" Canvas.Bottom="2">Left, Bottom</Button>
<Button Canvas.Right="2" Canvas.Bottom="2">Right, Bottom</Button>
</Canvas>
</TabItem>
<TabItem Header="Canvas 3">
<StackPanel>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Canvas HorizontalAlignment="Center" Background="Aqua">
<Button Canvas.Left="-21" Canvas.Top="8">Canvas</Button>
</Canvas>
</StackPanel>
</TabItem>
</TabControl>
</Window>
В первой вкладке ширина менеджера размещения задана явно, для видимости панели
задан ее фон. Привязка к соответствующим углам панели и дочерних элементов
осуществляется через дополнительные присоединенные
свойства ( присоединенные атрибуты ) Canvas.Left, Canvas.Right, Canvas.Top и Canvas.Bottom.
Достаточно только пары свойств привязки, а остальные избыточные свойства привязки
игнорируются.
Присоединенные свойства ( присоединенные
атрибуты ) - это возможность расширять
типы за счет свойств, предоставляемых другими типами. Присоединенные
свойства введены для того, чтобы не увеличивать до бесконечности родные свойства
элементов, а заимствовать их у других. Поддержка присоединенных свойств
возможна только в том случае, если тип, в котором это свойство определено,
и тип, к которому оно присоединяется, наследуют одному и тому же классу DependencyObject.
Присоединенному свойству всегда предшествует имя предоставляющего его
типа, даже если такое свойство употребляется в качестве атрибута.

На второй вкладке размеры панели Canvas явно не заданы, поэтому панель
превратится в бесконечно малую точку с совмещенными углами и не будет видна,
хоть мы и задали цвет ее фона. Зато мы задали свойства HorizontalAlignment и VerticalAlignment,
позиционирующие панель относительно родителя (клиентской области вкладки).
Дочерние кнопки панели оказались привязанными к невидимой точке за соответствующие
углы. При изменении размеров точечная панель автоматически позиционируется
в центре и держит при себе привязанные к ней кнопки.

На третьей вкладке панель StackPanel организует вертикальную стопку из трех
элементов, последним из которых является точечная панель Canvas, позиционированная
относительно центра с помощью ее свойства HorizontalAlignment. Дочерняя кнопка
панели Canvas привязывается за угол со смещением, включая отрицательное смещение
по оси x.

StackPanel размещает своих потомков построчно, по умолчанию вертикальной стопкой.
Свойство Orientation позволяет располагать стопку горизонтально или вертикально.
По умолчанию StackPanel занимает всю клиентскую область родителя. Каждому дочернему
элементу StackPanel выделяет слот (жизненное
пространство), а свой предпочтительный размер вычисляет на основе максимальных
размеров своих потомков.
В рамках выделенного слота дочерний элемент волен позиционироваться как ему
угодно с помощью своих собственных свойств Margin (поле вокруг элемента за
пределами его содержимого), HorizontalAlignment и VerticalAlignment (положение
внутри оставшегося свободным пространства слота). Отступ между границами элемента
и внутренним содержимым задается свойством Padding.
Добавьте к решению
новый проект WpfApp6 и назначьте его
стартовым
Добавьте к проекту
следующую разметку<Window x:Class="WpfApp6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StackPanel, DockPanel" Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="StackPanel">
<Border BorderBrush="Red" BorderThickness="2"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="5" Background="Aqua">
<Button Margin="2">One</Button>
<Button Margin="2">Two</Button>
<Button Margin="2">Three</Button>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="5" Background="Yellow">
<Button Margin="2">One</Button>
<Button Margin="2">Two</Button>
<Button Margin="2">Three</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Width="100" Background="White">
<TextBlock FontSize="18pt" TextWrapping="Wrap">
Этот текст не переносится, потому что погружен в StackPanel
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</TabItem>
<!--Имитация пространства Windows Explorer с помощью DockPanel-->
<TabItem Header="DockPanel 1">
<DockPanel>
<Button DockPanel.Dock="Top">Menu Area (Dock=Top)</Button>
<Button DockPanel.Dock="Top">ToolBar Area (Dock=Top)</Button>
<Button DockPanel.Dock="Left">Folders (Dock=Left)</Button>
<Button>
<TextBlock TextWrapping="Wrap" Padding="5pt">
Content (Fills remaining space because LastChildFill="True" -
последний добавленный элемент заполняет остающееся место,
потому что LastChildFill="True" по умолчанию).
Этот текст переносится, потому что погружен в DockPanel
</TextBlock>
</Button>
</DockPanel>
</TabItem>
<TabItem Header="DockPanel 2">
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top">Menu Area (Dock=Top)</Button>
<Button DockPanel.Dock="Left">Folders (Dock=Left)</Button>
<Button DockPanel.Dock="Top">ToolBar Area (Dock=Top)</Button>
<Button>
<TextBlock TextWrapping="Wrap" FontSize="10pt" Padding="5pt">
Content (Fills remaining space because LastChildFill="True" -
последний добавленный элемент заполняет остающееся место,
потому что LastChildFill="True"). Этот текст переносится,
потому что погружен в DockPanel
</TextBlock>
</Button>
</DockPanel>
</TabItem>
</TabControl>
</Window>
На первой вкладке рисуется рамка заданной ширины и цвета, в которую погружается StackPanel с вертикальной ориентацией выравнивания. В нее вкладываются еще
три панели с разной ориентацией выравнивания элементов и заданным фоном. В
последней панели содержится текстовый блок, который не переносит строку, хотя
задано свойство TextWrapping="Wrap". Функциональность переноса в текстовом
блоке возможна только тогда, когда он погружен в панель DockPanel.

Следующие две вкладки демонстрируют работу менеджера DockPanel. Структура
размещения определяется порядком добавления элементов в панель. Свойство LastChildFill позволяет последнему потомку занять все свободное пространство. Для дочерних
элементов DockPanel действует присоединенное свойство, которое указывает, к
какой стороне свободного места должен быть пристыкован очередной элемент.
DockPanel наиболее употребителен из всех менеджеров размещения. По
умолчанию DockPanel не позволяет пользователю перетаскивать границы между областями.
Для этого используется GridSplitter, тесно связанный с менеджером Grid и
мы его рассмотрим ниже (даже будет контрольное задание!).
В упражнении в качестве размещаемых элементов используются кнопки, которые
имитируют структуру Windows Explorer
![]() |
![]() |
Обратите внимание на работу менеджеров при изменении размеров окна.
Если DockPanel ведет себя как контейнер, элементы которого приклеиваются к
одной из сторон свободного места в порядке их следования, то WrapPanel переносит
невмещающиеся элементы на новую строку. При вертикальной ориентации панели WrapPanel перенос элементов не производится, но ширина элементов выравнивается
по самому широкому. По умолчанию WrapPanel устанавливает размеры своих потомков,
адаптируясь к их содержимому, хотя можно задать иные размеры с помощью свойств ItemWidth и ItemHeight.
Панель UniformGrid ( Uniform - однородный, одинаковый, равномерный; Grid -
сетка) распределяет потомков в равномерной сетке с заданным числом строк и
столбцов. Можно задавать только количество строк или только количество столбцов,
тогда второй параметр будет вычислен исходя из общего количества потомков.
Добавьте к решению
новый проект WpfApp7 типа WPF Application и назначьте его стартовым
Отредактируйте файл
разметки следующим образом<Window x:Class="WpfApp7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WrapPanel, UniformGrid"
Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="WrapPanel 1">
<StackPanel>
<TextBlock Margin="5">ItemWidth="NaN" ItemHeight="23"</TextBlock>
<WrapPanel ItemWidth="NaN" ItemHeight="23">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
</WrapPanel>
<TextBlock Margin="5">ItemWidth="75" ItemHeight="23pt"</TextBlock>
<WrapPanel ItemWidth="75" ItemHeight="23pt">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
</WrapPanel>
</StackPanel>
</TabItem>
<TabItem Header="WrapPanel 2">
<StackPanel>
<TextBlock Margin="5">WrapPanel Orientation="Vertical"</TextBlock>
<WrapPanel Orientation="Vertical">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
<Button>Двенадцать</Button>
</WrapPanel>
</StackPanel>
</TabItem>
<TabItem Header="UniformGrid">
<StackPanel>
<TextBlock Margin="5" Background="White" Foreground="Red"
FontWeight="Bold" FontStyle="Italic" TextDecorations="Underline">
UniformGrid Rows="3" Columns="4"
</TextBlock>
<UniformGrid Rows="3" Columns="4">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
<Button>Двенадцать</Button>
</UniformGrid>
<TextBlock Margin="5" Background="Aqua">
<Bold>
<Italic>
<Underline>
UniformGrid Rows="2"
</Underline>
</Italic>
</Bold>
</TextBlock>
<UniformGrid Rows="2">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
</UniformGrid>
<TextBlock Margin="5">
<Run FontWeight="Bold" FontStyle="Italic" TextDecorations="Underline">
UniformGrid Columns="4"
</Run>
</TextBlock>
<UniformGrid Columns="4">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
</UniformGrid>
</StackPanel>
</TabItem>
</TabControl>
</Window>
Результаты работы панелей будут следующими:

Когда не задана ширина потомков, то они сами устанавливают свою ширину, в зависимости от содержимого. Размещение потомков выполняется слева направо в порядке их следования в описании панели. Если общие размеры потомков заданы явно в параметрах панели, то для всех потомков они одинаковы независимо от того, вмещается в них свое содержимое или нет. Обратите внимание, что высота потомков задана в разных единицах: верхний вариант задан в аппаратно-независимых единицах ( логических пикселах ) для разрешения 96 точек на дюйм ( DPI - dots per inch ), а нижний задан в пунктах.
По умолчанию WPF использует аппаратно-независимые единицы - логические
пикселы (только SmallIconWidth и SmallIconHeight используют
физические пикселы). При таком подходе представления элементов на экране мало
зависят от его разрешения, а в большей степени определяются размерами экрана.
Сами экраны пользователи меняют значительно реже, чем разрешения, но и здесь,
если применять панели размещения, компоновка интерфейса останется почти прежней.

При вертикальной ориентации элементов перенос на следующий столбец панелью WrapPanel не выполняется.

Равномерная сетка UniformGrid распределяет потомков построчно в порядке их
следования. Ширина и высота всех ячеек сетки одинакова и определяется размерами
потомка с самым габаритным содержимым. Обратите внимание на средства стилизации
текстовых заголовков, определяемых элементами TextBlock. Одинакового эффекта
мы достигли тремя разными способами.
В панели UniformGrid все ячейки имеют одинаковый размер. В некоторых случаях
этого может быть недостаточно, тогда применяют более развитую панель Grid, у которой соседние строки могут иметь разную высоту, а соседние столбцы
- разную ширину. Элемент Grid самый гибкий и универсальный из всех
менеджеров размещения. Он восполняет применительно к окнам широко используемую
в HTML функциональность элемента Table. Недаром мастер заготовки WPF -приложения первоначально включает в разметку именно элемент Grid.
Размещение в сетке Grid состоит из двух этапов: вначале выполняется определение
строк и столбцов, а затем производится задание потомков и распределение их
по слотам. Самый простой способ использования Grid, это задать свойства RowDefinitions и ColumnDefinitions, добавить несколько дочерних элементов и с помощью присоединенных
к ним свойств Grid.Row и Grid.Column указать, какого потомка в какой слот (заготовленное
место, ячейка) поместить. Но существуют и более тонкие настройки Grid, которые
мы и рассмотрим в данном упражнении.
Добавьте к решению
новый проект WpfApp8 типа WPF Application и
назначьте его стартовым
Заполните файл разметки Window1.xaml следующим кодом<Window x:Class="WpfApp8.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Упражнение 8. Панель Grid"
Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="Grid 0">
</TabItem>
<TabItem Header="Grid 1">
</TabItem>
<TabItem Header="Grid 2">
</TabItem>
<TabItem Header="Grid 3">
</TabItem>
<TabItem Header="Grid 4">
</TabItem>
<TabItem Header="Grid 5">
</TabItem>
<TabItem Header="Grid 6">
</TabItem>
<TabItem Header="Grid 7">
</TabItem>
</TabControl>
</Window>
Запустите приложение
Первоначальный код является заготовкой для демонстрации вариантов работы
с панелью Grid и в данном упражнении мы последовательно заполним все вкладки
заготовки, изучая и иллюстрируя ее свойства. В ячейки сетки будем помещать
кнопки и другие контейнеры, чтобы ощутить ее реакцию. Но нестоит забывать,
что сама сетка как контейнер остается невидимой (если не задать ее фон), видна
только выполнямая ею работа по размещению потомков.
Заполните вкладку Grid 0 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">Первый</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Aquamarine">Третий (длинное содержимое)</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Две секции <Grid.RowDefinitions> и <Grid.ColumnDefinitions> определяют
строки и столбцы сетки. Дескрипторы <RowDefinition /> и <ColumnDefinition/> задают количество строк и столбцов. Здесь они применяются без параметров,
поэтому устанавливают равные условия для всех слотов (зарезервированного места)
сетки. Далее следуют размещаемые элементы, в качестве которых мы используем
разноцветные кнопки. Порядок следования потомков здесь не играет значения,
поскольку каждому из них явно указывается закрепленный за ним слот. Если содержимое
потомка не умещается в отведенный слот, то по умолчанию оно усекается.
Поизменяйте размеры
окна, чтобы почувствовать работу панели при данных настройках
Заполните вкладку Grid 1 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 1">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">Третий (длинное содержимое)</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Дескрипторы <RowDefinition Height="Auto" /> и <ColumnDefinition
Width="Auto" /> не только определяют количество строк и столбцов сетки,
но и могут устанавливать режимы подстройки под содержимое. Значения "Auto" параметров
высоты и ширины как раз это и означают.
Заполните вкладку Grid 2 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" MinWidth="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" MinWidth="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" MinWidth="0" Background="Aquamarine">Третий</Button>
<Button Grid.Row="1" Grid.Column="1" MinWidth="0" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
В дескрипторах определения строк и столбцов для рассматриваемого варианта
жестко задаются высота первой строки и ширина первого столбца. Установка размера
в значение "*" означает, что он нелимитирован и соответствующие строка
или столбец займут оставшееся от других свободное место родителя. Параметры MinWidth в определениях потомков являются в данном случае необязательными и
указывают минимальную границу изменения размера потомка по ширине.
Поизменяйте размеры
окна и убедитесь, что жестко заданные размеры первого слота строго выдерживаются
сеткой
Заполните вкладку Grid 3 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" MinWidth="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" MinWidth="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" MinWidth="0" Background="Aquamarine">Третий</Button>
<Button Grid.Row="1" Grid.Column="1" MinWidth="0" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Приведенные в каждой секции строк и столбцов данного кода определения размеров
устанавливают пропорции, которые должны поддерживаться между этим строками
или столбцами. Для любой из приведенных секций можно было бы задать и другие
размеры, например <RowDefinition
Height="20*" /> и <RowDefinition Height="10*" />, все равно пропорции
останутся прежними ( 2/1 ).
Заполните вкладку Grid 4 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 4">
<Grid HorizontalAlignment="Center" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Background="LightPink">Первый</Button>
<Button Grid.Row="1" Background="Lime">Второй</Button>
<Button Grid.Row="2" Background="Aquamarine">Третий - самое длинное содержимое</Button>
<Button Grid.Row="3" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Мы определили четыре строки и один столбец и задали автоматическую подстройку
под наибольшее содержимое элементов столбца. Сетка подстраивается по высоте
под свободное пространство родителя (вкладки), поскольку для нее мы задали VerticalAlignment="Stretch" (он
же и по умолчанию), а высоту ячеек не задали.
Заполните вкладку Grid 5 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 5">
<Grid Grid.IsSharedSizeScope="True" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" MinWidth="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" MinWidth="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" MinWidth="0" Background="Aquamarine">Наибольшей ширины</Button>
<Button Grid.Row="1" Grid.Column="1" MinWidth="0" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Оба столбца подстраиваются под свое наибольшее содержимое с помощью параметра Auto и имеют равную ширину, потому что объединены в группу с одинаковым ключом.
Это будет работать только при условии, что присоединенное к сетке свойство Grid.IsSharedSizeScope установлено
в True. Ключ выбирается произвольно, но должен быть алфавитно-цифровым. Он
не должен начинаться с цифры и не должен иметь пробелов или иных разделителей.
Связать ключом группы можно любое количество столбцов или строк и тогда все
они будут иметь равный размер.
Заполните вкладку Grid 6 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 6">
<StackPanel Grid.IsSharedSizeScope="True">
<TextBlock HorizontalAlignment="Center">
Сетки с обобществлением размеров столбцов
</TextBlock>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey1" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey2" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">
Шире для всех первых столбцов
</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">Четвертый</Button>
</Grid>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey1" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">1</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">2</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">3</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">4</Button>
</Grid>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey2" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">1</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">2</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">3</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">4</Button>
</Grid>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey1" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey2" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">1</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">2</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">3</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">Шире для вторых</Button>
</Grid>
</StackPanel>
</TabItem>
В StackPanel упаковано несколько сеток и текстовый блок с вертикальным по
умолчанию размещением. Столбцы сеток включены в разные группы и выравнивают
ширину по наибольшему содержимому своей группы. Свободные столбцы сеток подстраиваются
под свое содержимое индивидуально, потому что у них установлено Width="Auto".
Отступы между сетками обеспечиваются пустыми элементами StackPanel с параметром Margin="5". Свойство Grid.IsSharedSizeScope="True" для включения механизма
выравнивания размеров групп присоединено к родительской StackPanel.
Заполните вкладку Grid 7 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 7">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" HorizontalAlignment="Center" Margin="5">
Объединение столбцов первой строки
</TextBlock>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="50" MaxHeight="150" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" MinWidth="40" MaxWidth="200" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.ColumnSpan="3" Background="LightPink">Перый и второй</Button>
<GridSplitter
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
ResizeDirection="Rows"
ResizeBehavior="PreviousAndNext"
HorizontalAlignment="Stretch" />
<Button Grid.Row="2" Grid.Column="0" Background="Aquamarine">Третий</Button>
<GridSplitter
Grid.Row="2"
Grid.Column="1"
Width="2"
ResizeDirection="Columns"
ResizeBehavior="PreviousAndNext"
VerticalAlignment="Stretch" />
<Button Grid.Row="2" Grid.Column="2" Background="Yellow">Четвертый</Button>
</Grid>
</Grid>
</TabItem>
Вначале строится сетка с двумя строками и одним столбцом. В первую строку помещен текстовый блок с заголовком и она подстраивается под его размеры, а вторая строка по высоте занимает оставшееся пространство родителя. Во вторую строку погружена внутренняя сетка из трех строк и трех столбцов. Ее последний слот позволяет потомку автоматически подстраиваться под свое содержимое, но на размеры по ширине и высоте установлены ограничения. Крайние слоты внутренней сетки заняты тремя цветными кнопками.
Кнопка, привязанная к верхней строке
внутренней сетки, занимает сразу три соседних слота в соответствии с требованием
присоединенного к ней свойства Grid.ColumnSpan="3". Всю вторую строку занимает
элемент GridSplitter, ориентированный на разделение строк свойством ResizeDirection="Rows".
Он дает возможность пользователю мышью перетаскивать границы между крайними
строками внутренней сетки. В крайних столбцах последней строки размещены кнопки,
а в средний слот погружен элемент GridSplitter, ориентированный на разделение
столбцов.
Дополните упражнение вкладкой MyGrid, которая бы повторяла интерфейс
вкладки Grid 3, но с возможностью изменения пользователем горизонтальной
и вертикальной границ между рядами кнопок. За основу возьмите код вкладки Grid 7.
Должно получиться нечто подобное изображенному на рисунке, только с изменяющим
размеры перекрестным разделителем между кнопками.

Предположим, что некоторые элементы управления мы хотим расположить равномерно по кругу с соответствующим угловым поворотом каждого из них. Это нестандартная задача и для ее решения типовых менеджеров размещения разработчиками не создано, зато предусмотрены возможности выполнить эту работу самостоятельно. Вряд ли нам понадобится в будущем это делать, но познакомиться стоит, чтобы знать, что такое возможно.
Все стандартные панели WPF наследуют абстрактному базовому классу Panel и
наш собственный менеджер тоже должен наследовать этот библиотечный класс. Класс Panel, в свою очередь, наследует от класса UIElement, поэтому наш класс расширения
приобретет от своих предков все необходимое. Класс Panel является такой вершиной
цепочки наследования, которая включает в себя все паттерны (образец,
кирпичик, строительный блок, базовый функциональный объект), необходимые для
разработки любых менеджеров размещения. В нашем случае в классе расширения
нам останется только перекрыть пару виртуальных методов.
Та часть объектной модели UIElement, которая относится к менеджеру размещения,
довольно проста. Методы Measure, MeasureCore, Arrange и ArrangeCore реализуют
два этапа размещения, а свойство Visibility говорит, должен ли потомок отображаться
и учитываться при размещении.
Свойство Visibility позволяет задать три способа участия дочернего элемента
в размещении. По умолчанию оно равно Visible: элемент отображается и занимает
место на экране. При значении Hidden элемент скрыт, но место на экране занимает.
При Collapsed дочерний элемент не отображается и место на экране не занимает.
Механизм взаимодействия размещаемого дочернего элемента и менеджера размещения состоит из двух частей:
Никакого встроенного размещения нет, все конкретные способы размещения построены по принципу расширения базовых классов.
Все элементы WPF, включая и менеджеры размещения, адаптируются к своему содержимому. Эта концепция применяется на всех уровнях построения пользовательского интерфейса: окна могут подгонять свой размер под находящиеся в них элементы, кнопки - подстраиваться под надписи на них, текстовые поля - изменять свой размер так, чтобы поместились все символы.
Менеджеры реализуют двухэтапное размещение своих дочерних элементов: измерение и установка. Вначале менеджер опрашивает каждого из своих дочерних элементов на предмет того, какой фактический размер тот предпочитает, чтобы полностью показать свое содержимое на экране. Это этап измерения. Затем менеджер соотносит эту информацию со своими настройками и возможностями, сколько он может вместить, и приступает к этапу установки.
В ходе установки родитель безусловно требует от каждого из своих дочерних элементов, чтобы тот установил определенный размер и положение. Эта модель позволяет родителю и потомку договариваться о месте на экране (месте под Солнцем). При этом фигурируют три размера:
На размеры элемента могут налагаться ограничения 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,
обозначив его произвольным именем.
Запустите упражнение
- окончательный
результат выполнения будет выглядеть примерно так
В данной работе на ряде примеров мы познакомились с принципами размещения интерфейсных элементов, которые пронизывают всю библиотеку WPF как новое средство борьбы с непредсказуемым разрешением экрана.

Все необходимые для выполнения данной работы программы можно найти в прилагаемом каталоге.
WPF имеет возможность очень тонко настраивать внешний вид объектов. Внешний вид объектов зависит от свойств, которые задаются как при помощи прямых настроек, так и при помощи кистей (Brushes). Список свойств включает следующие:
Кисти используются для задания внешнего вида объектов и могут быть следующих типов:
Кисти можно конвертировать в ресурсы и многократно применять к различным объектам.
В WPF есть средства достигать специальных растровых эффектов (Bitmap effects):
Создайте новый пустой
проект ( Empty Project ) с именем WpfApp1 в
новом решении WpfApp. Проследите, чтобы
обязательно использовалась версия библиотеки не ниже .NET Framework
3.0
Вызовите контекстное
меню проекта и через команду Properties установите во вкладке Application параметр Output
type в значение Windows Application
Добавьте к узлу References проекта
ссылки на следующие библиотеки .NET Framework, составляющие основу платформы
WPFPresentationCore PresentationFrameworkWindowsBaseSystem - эта общая библиотека для запуска приложения
Добавьте к проекту
новый файл с именем CreateBrushes.cs
Заполните файл следующим
кодомusing System;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Navigation;
using System.Windows.Controls;
using System.Reflection;
namespace WpfApp1
{
class CreateBrushes : NavigationWindow
{
// Точка входа
[STAThread] // Атрибут для однопоточного приложения
public static void Main()
{
Application app = new Application();
app.Run(new CreateBrushes());
}
// Конструктор
public CreateBrushes()
{
this.Width = 300;
this.Height = 300;
this.Title = "Каркас приложения Упражнения 1";
//this.ShowsNavigationUI = false;// Скрыть навигационный интерфейс
}
}
}
Мы решили построить многостраничное приложение для многих примеров упражнения
и содали каркас для страниц содержимого с навигационным узлом. Навигационный
узел можно отключить через свойство ShowsNavigationUI. Мы создали каркас с
нуля и пока будем использовать только чистый код C#, не применяя разметку XAML.
Страничный каркас создается классом NavigationWindow, производным от класса Window. Мы расширяем библиотечный класс NavigationWindow пользовательским классом CreateBrushes, следовательно все наследуемые им от базового класса Window члены
будут доступны в коде и нашего класса-расширения.
Запустите приложение
- появится мертвый каркас
Далее, по мере выполнения примеров упражнения, мы будем добавлять в каркас
все новые страницы, которые построим из расширений библиотечного класса Page,
наделив их соответствующими свойствами. Одновременно мы будем задействовать
навигационный механизм каркаса, который предоставляет базовый класс NavigationWindow.
Начнем с создания первой страницы, на которой по всей клиентской области будет
расположена кнопка. Кнопку будем закрашивать однородным цветом градаций серого,
в зависимости от удаления курсора от центра кнопки. Страницу построим вручную
как класс-расширение на базе библиотечного класса Page.
Дополните файл CreateBrushes.cs кодом создания страницы Page1namespace WpfApp1
{
// Класс-расширение страницы Page1
class Page1 : Page
{
// Создаем и инициализируем поле кисти белым цветом
SolidColorBrush brush = new SolidColorBrush(Colors.White);
Button btnPage1 = new Button();
public Page1()
{
this.WindowTitle = "Page1: Кисть SolidColorBrush";
// Создали кнопку
btnPage1.Content = "Кнопка";
btnPage1.Background = brush;
// Зарегистрировали обработчики
btnPage1.Click += new RoutedEventHandler(btnPage1_Click);
btnPage1.MouseMove += new MouseEventHandler(btnPage1_MouseMove);
this.Content = btnPage1; // Поместили кнопку в страницу
}
// Меняет цвет фона кнопки на Page1
void btnPage1_MouseMove(object sender, MouseEventArgs e)
{
// Чистая ширина клиентской области без рамки окна
double width = btnPage1.ActualWidth;
// Чистая высота клиентской области без рамки и заголовка окна
double height = btnPage1.ActualHeight;
Point ptMouse = e.GetPosition(btnPage1);// Точка курсора на клиентской области окна
Point ptCenter = new Point(width / 2, height / 2);// Центр клиентской области
Vector vectMouse = ptMouse - ptCenter;// Отклонение курсора от центра
double angle = Math.Atan2(vectMouse.Y, vectMouse.X);// Угол нахождения курсора
Vector vectEllipse = new Vector(width / 2 * Math.Cos(angle),
height / 2 * Math.Sin(angle));// Вписанный эллипс
// Изоклина (в центре кнопки черная), округляется до одного байта
Byte byLevel = (byte)(255 * (Math.Min(1, vectMouse.Length / vectEllipse.Length)));
Color color = brush.Color;// Привязываем к полю
color.R = color.G = color.B = byLevel;// Цвета, пропорциональные изоклине
brush.Color = color;// Меняем цвет фона окна изоклиной от равномерного черного цвета
}
// Переход на следующую страницу
void btnPage1_Click(object sender, RoutedEventArgs e)
{
}
}
}
Добавьте код создания
объекта начальной страницы в конструктор класса каркаса приложенияnamespace WpfApp1
{
class CreateBrushes : NavigationWindow
{
// Точка входа
[STAThread] // Атрибут для однопоточного приложения
public static void Main()
{
Application app = new Application();
app.Run(new CreateBrushes());
}
// Конструктор
public CreateBrushes()
{
this.Width = 300;
this.Height = 300;
//this.Title = "Каркас приложения Упражнения 1";
//this.ShowsNavigationUI = false;// Скрыть навигационный интерфейс
// Создали начальную страницу
Page1 page1 = new Page1();
this.Content = page1; // Поместили страницу в каркас
}
}
}
Запустите приложение
и вы увидите первую страницу внутри каркаса, на которой расположена кнопка,
меняющая градации серого цвета при движении над ней курсора мыши по направлению
к центру кнопки
Кнопка нажимается, но ничего не делает, только меняет цвет.
Создайте новую страницу Page2, на которую пользователь перейдет по щелчку на кнопкеnamespace WpfApp1
{
// Класс-расширение страницы Page2
class Page2 : Page
{
int index = 0;// Номер цвета
PropertyInfo[] props;// Массив свойств
public Page2()
{
// Применяем рефлексию для чтения свойств класса Brushes
props = typeof(Brushes).GetProperties(
BindingFlags.Public | BindingFlags.Static);
// Компоновочная панель
StackPanel stackPanel = new StackPanel();// Создаем
this.Content = stackPanel;// Присоединяем к странице
Button btn = new Button();
btn.Name = "ButtonNextColor";
btn.Content = "NextColor >";
btn.Click += new RoutedEventHandler(btn_Click);
stackPanel.Children.Add(btn);// Добавляем в панель
btn = new Button();
btn.Content = "< PreviousColor";
btn.Click += new RoutedEventHandler(btn_Click);
stackPanel.Children.Add(btn);
btn = new Button();
btn.Content = "Next Page3";
btn.Click += new RoutedEventHandler(btnPage2_Click);
stackPanel.Children.Add(btn);
// Возбуждается при каждом отображении страницы
this.Loaded += new RoutedEventHandler(Page2_Loaded);
}
void Page2_Loaded(object sender, RoutedEventArgs e)
{
// Вариант
//this.NavigationService.LoadCompleted += NavigationService_LoadCompleted;
SetTitleAndBackground();
}
void NavigationService_LoadCompleted(object sender, NavigationEventArgs e)
{
// Вариант
//SetTitleAndBackground();
}
// Обработчики кнопок смены заголовка и цвета страницы
void btn_Click(object sender, RoutedEventArgs e)
{
// Распознаем кнопку по имени и корректируем индекс
if (((Button)sender).Name == "ButtonNextColor")
index += 1;
else
index += props.Length - 1;
index %= props.Length;// Деление по модулю
SetTitleAndBackground();
}
// Установка заголовка и цвета фона страницы
void SetTitleAndBackground()
{
this.WindowTitle = "Page2: Имя цвета кисти - " + props[index].Name;
this.Background = (Brush)props[index].GetValue(null, null);
}
// Переход на следующую страницу
void btnPage2_Click(object sender, RoutedEventArgs e)
{
}
}
}
Мы разработали новый класс, определяющий функциональность страницы Page2.
Осталось добавить в обработчик щелчка кнопки первой страницы код для перехода
на эту новую страницу.
Добавьте в обработчик btnPage1_Click() класса Page1 следующий код// Переход на следующую страницу
Page2 page2;
void btnPage1_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page2 = new Page2();// Создаем только один раз
this.NavigationService.Navigate(page2);
}
Мы создаем объект страницы Page2 только при первом ее отображении
и сохраняем в поле, поэтому в процессе навигации при всех последующих ее отображениях
переустанавливать фон и заголовок необязательно, а обработчик события Loaded можно не регистрировать. Такой прием мы применим и для следующих страниц.
Запустите приложение
и испытайте работу класса Page2 в составе навигационного каркаса
Одну из альтернатив однородных кистей составляют градиентные кисти с постепенным
переходом шлейфа двух и более цветов. В простейшем случае для создания линейной
градиентной кисти достаточно двух крайних точек с определенными в них цветами.
Градиентный шлейф будет строиться вдоль соединяющей эти точки прямой линии
перпендикулярно ей и заливать всю поверхность
Конструктор объекта линейной градиентной кисти имеет несколько перегрузок,
в одних из которых задаются две точки и два цвета; два цвета, начальная точка
привязки вектора градиента и угол его направления закраски. Точки могут задавать
относительные (по умолчанию) и абсолютные координаты в рамках ограничивающего
прямоугольника, что определяется свойством MappingMode и его значением из перечисления BrushMappingMode.
Создайте новую страницу Page3 в файле CreateBrushes.cs, демонстрирующую один из вариантов
линейной градиентной
закраскиnamespace WpfApp1
{
class Page3 : Page
{
public Page3()
{
this.WindowTitle = "Page3: Кисть LinearGradientBrush";
Button btn = new Button();
btn.Content="Next Page4";
btn.Click += new RoutedEventHandler(btn3_Click);
this.Content = btn;
// Создание и присоединение градиента
LinearGradientBrush brush = new LinearGradientBrush(
Colors.Red, Colors.Blue, new Point(0, 0), new Point(1, 1));
btn.Background = brush;
}
// Переход на следующую страницу
void btn3_Click(object sender, RoutedEventArgs e)
{
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page2 код создания экземпляра Page3// Переход на следующую страницу
Page3 page3;
void btnPage2_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page3 = new Page3();// Создаем только один раз
this.NavigationService.Navigate(page3);
}
Запустите приложение
и испытайте работу класса Page3 в составе навигационного каркаса
Если отрезок меньше LinearGradientBrush определяется наследуемым от GradientBrush свойством SpreatMethod и
его значением из перечисления GradientSpreadMethod.
Создайте новую страницу Page4 в файле CreateBrushes.cs, демонстрирующую волнообразную градиентную
закраскуnamespace WpfApp1
{
class Page4 : Page
{
public Page4()
{
this.WindowTitle = "Page4: GradientSpreadMethod.Reflect";
Button btn = new Button();
btn.Content = "Next Page5";
btn.Click += new RoutedEventHandler(btn4_Click);
this.Content = btn;
// Создание и присоединение градиента
LinearGradientBrush brush = new LinearGradientBrush(
Colors.Red, Colors.Blue, new Point(0, 0), new Point(0.25, 0.25));
brush.SpreadMethod = GradientSpreadMethod.Reflect;
btn.Background = brush;
}
// Переход на следующую страницу
void btn4_Click(object sender, RoutedEventArgs e)
{
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page3 код создания экземпляра Page4// Переход на следующую страницу
Page4 page4;
void btn3_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page4 = new Page4();// Создаем только один раз
this.NavigationService.Navigate(page4);
}
Запустите приложение
и испытайте работу класса Page4 с периодической сменой направления линейного
градиента
У GradientStops, содержащая объекты GradientStop.
Каждый из объектов коллекции задает начальный цвет градиента и правую границу
полного перекрытия этим цветом предыдущего. В такой модели дополнительно используются
точки границ закраски, определяемые свойствами StartPoint и EndPoint. Для иллюстрации
создадим страницу, поместим на нее кнопку и закрасим кнопку вертикальными градиентами.
Создайте новую страницу Page5 в файле CreateBrushes.cs, поместите на нее кнопку и закрасьте вертикальными
градиентами с помощью следующего кодаnamespace WpfApp1
{
class Page5 : Page
{
public Page5()
{
this.WindowTitle = "Page5: Закраска цветами радуги";
Button btn = new Button();
this.Content = btn;
btn.Content = "Next Page6";
btn.Click += new RoutedEventHandler(btn5_Click);
// Создание и присоединение градиента
LinearGradientBrush brush = new LinearGradientBrush();
btn.Background = brush;
brush.StartPoint = new Point(0, 0);
brush.EndPoint = new Point(1, 0);
// Цвета радуги
brush.GradientStops.Add(new GradientStop(Colors.Red, 0));
brush.GradientStops.Add(new GradientStop(Colors.Orange, .17));
brush.GradientStops.Add(new GradientStop(Colors.Yellow, .33));
brush.GradientStops.Add(new GradientStop(Colors.Green, .5));
brush.GradientStops.Add(new GradientStop(Colors.CornflowerBlue, .67));
brush.GradientStops.Add(new GradientStop(Colors.Blue, .84));
brush.GradientStops.Add(new GradientStop(Colors.BlueViolet, 1));
}
// Переход на следующую страницу
void btn5_Click(object sender, RoutedEventArgs e)
{
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page4 код создания экземпляра Page5// Переход на следующую страницу
Page5 page5;
void btn4_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page5 = new Page5();// Создаем только один раз
this.NavigationService.Navigate(page5);
}
Запустите приложение
и испытайте работу класса Page5, рисующего последовательные переливы цветов
радуги
RadialGradientBrush. Следующий пример
демонстрирует работу этого градиента
Создайте новую страницу Page6 в файле CreateBrushes.cs и закрасьте ее с помощью следующего кодаnamespace WpfApp1
{
class Page6 : Page
{
public Page6()
{
this.WindowTitle = "Page6: Радиальный градиент";
// Создание и присоединение градиента
RadialGradientBrush brush = new RadialGradientBrush();
this.Background = brush;
// Цвета радуги
brush.GradientStops.Add(new GradientStop(Colors.Red, 0));
brush.GradientStops.Add(new GradientStop(Colors.Orange, .17));
brush.GradientStops.Add(new GradientStop(Colors.Yellow, .33));
brush.GradientStops.Add(new GradientStop(Colors.Green, .5));
brush.GradientStops.Add(new GradientStop(Colors.CornflowerBlue, .67));
brush.GradientStops.Add(new GradientStop(Colors.Blue, .84));
brush.GradientStops.Add(new GradientStop(Colors.BlueViolet, 1));
}
}
}
Вставьте в обработчик
кнопки перехода на следующую страницу в классе Page5 код создания экземпляра Page6// Переход на следующую страницу
Page6 page6;
void btn5_Click(object sender, RoutedEventArgs e)
{
if (!this.NavigationService.CanGoForward)
page6 = new Page6();// Создаем только один раз
this.NavigationService.Navigate(page6);
}
Запустите приложение
и испытайте работу кисти с 
Все элементы управления WPF исчерпывающе описаны в электронной справке http://msdn.microsoft.com/ru-ru/library/ms752324.aspx
В наших упражнениях мы проиллюстрируем применение некоторых из них.
Добавьте к решению
новый проект типа WPF Application с именем WpfApp2 и
назначьте его стартовым
Скорректируйте разметку
окна в файле Window1.xaml следующим образом<Window x:Class="WpfApp2.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Menu, ToolBar, TabControl, ToolTip"
SizeToContent="WidthAndHeight"
ToolTipService.InitialShowDelay="0"
ToolTipService.ShowDuration="500000"
>
<Window.ToolTip>
<ToolTip x:Name="toolTip"
Placement="RelativePoint"
VerticalOffset="10"
/>
</Window.ToolTip>
<DockPanel Background="LightGray">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="E_xit" Click="ExitClicked" />
</MenuItem>
<MenuItem Header="_Edit">
<MenuItem Header="_Cut" />
<MenuItem Header="C_opy" />
<MenuItem Header="_Paste" />
</MenuItem>
</Menu>
<ToolBarTray DockPanel.Dock="Top">
<ToolBar>
<Button Width="23">A</Button>
<Button Width="23">B</Button>
<Button Width="23">C</Button>
<Button Width="23">D</Button>
</ToolBar>
<ToolBar Header="Search">
<TextBox Width="100" />
<Button Width="23">Go</Button>
</ToolBar>
</ToolBarTray>
<TabControl>
<TabItem Header="Page1">
<StackPanel>
<Expander Header="Секция 1" IsExpanded="True">
<GroupBox Header="Подсекция A">
<Label>Некоторый текст!</Label>
</GroupBox>
</Expander>
</StackPanel>
</TabItem>
<TabItem Header="Page2">
<StackPanel Orientation="Horizontal" Margin="5">
<TextBlock Name="value" Margin="10" Text="255" Width="20" />
<Slider Name="slider" Width="240"
Minimum="0" Maximum="255" Value="255"
ValueChanged="slider_ValueChanged" />
</StackPanel>
</TabItem>
<TabItem Header="Page3">
<UniformGrid Rows="3" Columns="3">
<Button ToolTip="Кнопка 1" Margin="5">1</Button>
<Button ToolTip="Кнопка 2" Margin="5">2</Button>
<Button ToolTip="Кнопка 3" Margin="5">3</Button>
<Button ToolTip="Кнопка 4" Margin="5">4</Button>
<Button ToolTip="Кнопка 5" Margin="5">5</Button>
<Button ToolTip="Кнопка 6" Margin="5">6</Button>
<Button ToolTip="Кнопка 7" Margin="5">7</Button>
<Button ToolTip="Кнопка 8" Margin="5">8</Button>
<Button ToolTip="Кнопка 9" Margin="5">9</Button>
</UniformGrid>
</TabItem>
</TabControl>
</DockPanel>
</Window>
Зарегистрированные
обработчики в файле 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 WpfApp2
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
private void ExitClicked(object sender, RoutedEventArgs e)
{
this.Close();
}
private void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
value.Text = ((int)slider.Value).ToString();
}
}
}
Запустите приложение
- получим действующее меню, две панели инструментов и элемент с вкладками


Элемент управления Thumb (манипулятор) инкапсулирует область, которую можно перемещать, и генерирует все необходимые события.
Добавьте к решению WpfApp новый проект с именем WpfApp3 и
назначьте его стартовым
Наполните файл Window1.xaml следующим содержимым<Window x:Class="WpfApp3.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Thumb, Border, Popup"
Height="300"
Width="300"
Background="LightGray"
>
<TabControl>
<!--Элемент, описывающий перемещаемую область-->
<TabItem Header="Thumb">
<Canvas>
<Thumb
Name="thumb1"
Background="Red"
Canvas.Left="5"
Canvas.Top="5"
Width="23"
Height="23"
DragStarted="thumb1_DragStarted"
DragDelta="thumb1_DragDelta"
/>
</Canvas>
</TabItem>
<!--Применение рамок Border-->
<TabItem Header="Border">
<StackPanel>
<Border
BorderThickness="3"
CornerRadius="0"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет1
</TextBlock>
</Border>
<Border
BorderThickness="3"
CornerRadius="3"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет2
</TextBlock>
</Border>
<Border
BorderThickness="10,1,10,1"
CornerRadius="10"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет3
</TextBlock>
</Border>
<Border
BorderThickness="4,1,4,1"
CornerRadius="0,15,0,15"
BorderBrush="Black"
Padding="5"
Margin="10"
Background="Violet"
>
<TextBlock HorizontalAlignment="Center">
Привет4
</TextBlock>
</Border>
</StackPanel>
</TabItem>
<!--Применение элемента Popup-->
<TabItem Header="Popup">
<StackPanel>
<Button Name="btn" Click="btn_Click">Toggle</Button>
<Popup Name="popup"
PopupAnimation="Fade"
Placement="Mouse"
>
<Button Background="Yellow" Foreground="Red">
Кнопка создана в Popup
</Button>
</Popup>
</StackPanel>
</TabItem>
</TabControl>
</Window>
Свойство BorderThickness задает толщину границы элемента.
Наполните файл 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;
using System.Windows.Controls.Primitives;
namespace WpfApp3
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
}
protected override void OnMouseDoubleClick(MouseButtonEventArgs e)
{
base.OnMouseDoubleClick(e);
// Возвращаем Thumb в исходное состояние
Canvas.SetLeft(thumb1, 5);
Canvas.SetTop(thumb1, 5);
}
double originalLeft, originalTop;
private void thumb1_DragStarted(object sender, DragStartedEventArgs e)
{
originalLeft = Canvas.GetLeft(thumb1);
originalTop = Canvas.GetTop(thumb1);
}
private void thumb1_DragDelta(object sender, DragDeltaEventArgs e)
{
double left = originalLeft + e.HorizontalChange;
double top = originalTop + e.VerticalChange;
Canvas.SetLeft(thumb1, left);
Canvas.SetTop(thumb1, top);
originalLeft = left;
originalTop = top;
}
private void btn_Click(object sender, RoutedEventArgs e)
{
popup.IsOpen = !popup.IsOpen;
}
}
}
Запустите приложение
- получим иллюстрацию элементов Thumb , Border, Popup


Элемент ScrollViewer прокручивает окно, если интерфейс с дочерними элементами
в нем не помещается. Элемент Viewbox принимает одного потомка (можно контейнер)
и масштабирует его пропорционально своим размерам.
Добавьте к решению WpfApp новый проект с именем WpfApp4 и
назначьте его стартовым
Заполните файл разметки
следующим кодом<Window x:Class="WpfApp4.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="ScrollViewer, Viewbox, StackPanel" Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="ScrollViewer">
<ScrollViewer
HorizontalScrollBarVisibility="Auto"
VerticalScrollBarVisibility="Auto"
>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Name="textBox" Grid.Row="0" Margin="5" />
<Button Grid.Row="1" Margin="5" Click="Add_Click">Add</Button>
<ListBox Name="listBox" Grid.Row="2" Margin="5" />
</Grid>
</ScrollViewer>
</TabItem>
<TabItem Header="Viewbox">
<Viewbox>
<TextBlock>Текст</TextBlock>
</Viewbox>
</TabItem>
<TabItem Header="StackPanel">
<StackPanel Orientation="Vertical" Background="Aqua">
<Button HorizontalAlignment="Center" Width="75" Margin="5">
Center
</Button>
<Button HorizontalAlignment="Left" Width="75">
<Button.LayoutTransform>
<RotateTransform Angle="-45" />
</Button.LayoutTransform>
Left (-45)
</Button>
<Button HorizontalAlignment="Right" Width="75">
<Button.LayoutTransform>
<RotateTransform Angle="45" />
</Button.LayoutTransform>
Right (45)
</Button>
<Button HorizontalAlignment="Stretch" Margin="5">Stretch</Button>
</StackPanel>
</TabItem>
</TabControl>
</Window>
Заполните кодовую часть
следующим кодом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 WpfApp4
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
textBox.Text = "Привет студентам от Снеткова В.М.!";
}
private void Add_Click(object sender, RoutedEventArgs e)
{
listBox.Items.Add(textBox.Text);
}
}
}
Запустите приложение

Попробуйте порастягивать
окно с дочерним элементом Viewbox - этот элемент масштабирует своего потомка
Наиболее употребительными панелями динамического размещения интерфейсных элементов являются:
CanvasStackPanelDockPanelUniformGridGridПанели обеспечивают типичные способы размещения дочерних элементов и в большинстве
случаев этого бывает достаточно. Все панели наследуют от абстрактного класса Panel, имеющего, в свою очередь, в качестве базового класс System.Windows.FrameworkElement.
Наиболее развитым менеджером размещения является панель Grid. Сами панели являются
невидимыми, если явно не задать их фон, а их присутствие обнаруживается только
по влиянию на дочерние элементы.
Добавьте к решению
новый проект WpfApp5 и назначьте его
стартовым
Заполните файл разметки
следующим кодом<Window x:Class="WpfApp5.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Менеджер размещения Canvas" Height="300" Width="300"
Background="LightGray">
<TabControl>
<TabItem Header="Canvas 1">
<Canvas Width="200" Height="100" Background="Aqua">
<Button Canvas.Left="2" Canvas.Top="2">Left, Top</Button>
<Button Canvas.Right="2" Canvas.Top="2">Right, Top</Button>
<Button Canvas.Left="2" Canvas.Bottom="2">Left, Bottom</Button>
<Button Canvas.Right="2" Canvas.Bottom="2">Right, Bottom</Button>
</Canvas>
</TabItem>
<TabItem Header="Canvas 2">
<Canvas HorizontalAlignment="Center" VerticalAlignment="Center" Background="Aqua">
<Button Canvas.Left="2" Canvas.Top="2">Left, Top</Button>
<Button Canvas.Right="2" Canvas.Top="2">Right, Top</Button>
<Button Canvas.Left="2" Canvas.Bottom="2">Left, Bottom</Button>
<Button Canvas.Right="2" Canvas.Bottom="2">Right, Bottom</Button>
</Canvas>
</TabItem>
<TabItem Header="Canvas 3">
<StackPanel>
<Button>Button 1</Button>
<Button>Button 2</Button>
<Canvas HorizontalAlignment="Center" Background="Aqua">
<Button Canvas.Left="-21" Canvas.Top="8">Canvas</Button>
</Canvas>
</StackPanel>
</TabItem>
</TabControl>
</Window>
В первой вкладке ширина менеджера размещения задана явно, для видимости панели
задан ее фон. Привязка к соответствующим углам панели и дочерних элементов
осуществляется через дополнительные присоединенные
свойства ( присоединенные атрибуты ) Canvas.Left, Canvas.Right, Canvas.Top и Canvas.Bottom.
Достаточно только пары свойств привязки, а остальные избыточные свойства привязки
игнорируются.
Присоединенные свойства ( присоединенные
атрибуты ) - это возможность расширять
типы за счет свойств, предоставляемых другими типами. Присоединенные
свойства введены для того, чтобы не увеличивать до бесконечности родные свойства
элементов, а заимствовать их у других. Поддержка присоединенных свойств
возможна только в том случае, если тип, в котором это свойство определено,
и тип, к которому оно присоединяется, наследуют одному и тому же классу DependencyObject.
Присоединенному свойству всегда предшествует имя предоставляющего его
типа, даже если такое свойство употребляется в качестве атрибута.

На второй вкладке размеры панели Canvas явно не заданы, поэтому панель
превратится в бесконечно малую точку с совмещенными углами и не будет видна,
хоть мы и задали цвет ее фона. Зато мы задали свойства HorizontalAlignment и VerticalAlignment,
позиционирующие панель относительно родителя (клиентской области вкладки).
Дочерние кнопки панели оказались привязанными к невидимой точке за соответствующие
углы. При изменении размеров точечная панель автоматически позиционируется
в центре и держит при себе привязанные к ней кнопки.

На третьей вкладке панель StackPanel организует вертикальную стопку из трех
элементов, последним из которых является точечная панель Canvas, позиционированная
относительно центра с помощью ее свойства HorizontalAlignment. Дочерняя кнопка
панели Canvas привязывается за угол со смещением, включая отрицательное смещение
по оси x.

StackPanel размещает своих потомков построчно, по умолчанию вертикальной стопкой.
Свойство Orientation позволяет располагать стопку горизонтально или вертикально.
По умолчанию StackPanel занимает всю клиентскую область родителя. Каждому дочернему
элементу StackPanel выделяет слот (жизненное
пространство), а свой предпочтительный размер вычисляет на основе максимальных
размеров своих потомков.
В рамках выделенного слота дочерний элемент волен позиционироваться как ему
угодно с помощью своих собственных свойств Margin (поле вокруг элемента за
пределами его содержимого), HorizontalAlignment и VerticalAlignment (положение
внутри оставшегося свободным пространства слота). Отступ между границами элемента
и внутренним содержимым задается свойством Padding.
Добавьте к решению
новый проект WpfApp6 и назначьте его
стартовым
Добавьте к проекту
следующую разметку<Window x:Class="WpfApp6.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="StackPanel, DockPanel" Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="StackPanel">
<Border BorderBrush="Red" BorderThickness="2"
HorizontalAlignment="Center" VerticalAlignment="Center">
<StackPanel Orientation="Vertical">
<StackPanel Orientation="Horizontal" Margin="5" Background="Aqua">
<Button Margin="2">One</Button>
<Button Margin="2">Two</Button>
<Button Margin="2">Three</Button>
</StackPanel>
<StackPanel Orientation="Vertical" Margin="5" Background="Yellow">
<Button Margin="2">One</Button>
<Button Margin="2">Two</Button>
<Button Margin="2">Three</Button>
</StackPanel>
<StackPanel Orientation="Horizontal" Margin="5" Width="100" Background="White">
<TextBlock FontSize="18pt" TextWrapping="Wrap">
Этот текст не переносится, потому что погружен в StackPanel
</TextBlock>
</StackPanel>
</StackPanel>
</Border>
</TabItem>
<!--Имитация пространства Windows Explorer с помощью DockPanel-->
<TabItem Header="DockPanel 1">
<DockPanel>
<Button DockPanel.Dock="Top">Menu Area (Dock=Top)</Button>
<Button DockPanel.Dock="Top">ToolBar Area (Dock=Top)</Button>
<Button DockPanel.Dock="Left">Folders (Dock=Left)</Button>
<Button>
<TextBlock TextWrapping="Wrap" Padding="5pt">
Content (Fills remaining space because LastChildFill="True" -
последний добавленный элемент заполняет остающееся место,
потому что LastChildFill="True" по умолчанию).
Этот текст переносится, потому что погружен в DockPanel
</TextBlock>
</Button>
</DockPanel>
</TabItem>
<TabItem Header="DockPanel 2">
<DockPanel LastChildFill="True">
<Button DockPanel.Dock="Top">Menu Area (Dock=Top)</Button>
<Button DockPanel.Dock="Left">Folders (Dock=Left)</Button>
<Button DockPanel.Dock="Top">ToolBar Area (Dock=Top)</Button>
<Button>
<TextBlock TextWrapping="Wrap" FontSize="10pt" Padding="5pt">
Content (Fills remaining space because LastChildFill="True" -
последний добавленный элемент заполняет остающееся место,
потому что LastChildFill="True"). Этот текст переносится,
потому что погружен в DockPanel
</TextBlock>
</Button>
</DockPanel>
</TabItem>
</TabControl>
</Window>
На первой вкладке рисуется рамка заданной ширины и цвета, в которую погружается StackPanel с вертикальной ориентацией выравнивания. В нее вкладываются еще
три панели с разной ориентацией выравнивания элементов и заданным фоном. В
последней панели содержится текстовый блок, который не переносит строку, хотя
задано свойство TextWrapping="Wrap". Функциональность переноса в текстовом
блоке возможна только тогда, когда он погружен в панель DockPanel.

Следующие две вкладки демонстрируют работу менеджера DockPanel. Структура
размещения определяется порядком добавления элементов в панель. Свойство LastChildFill позволяет последнему потомку занять все свободное пространство. Для дочерних
элементов DockPanel действует присоединенное свойство, которое указывает, к
какой стороне свободного места должен быть пристыкован очередной элемент.
DockPanel наиболее употребителен из всех менеджеров размещения. По
умолчанию DockPanel не позволяет пользователю перетаскивать границы между областями.
Для этого используется GridSplitter, тесно связанный с менеджером Grid и
мы его рассмотрим ниже (даже будет контрольное задание!).
В упражнении в качестве размещаемых элементов используются кнопки, которые
имитируют структуру Windows Explorer
![]() |
![]() |
Обратите внимание на работу менеджеров при изменении размеров окна.
Если DockPanel ведет себя как контейнер, элементы которого приклеиваются к
одной из сторон свободного места в порядке их следования, то WrapPanel переносит
невмещающиеся элементы на новую строку. При вертикальной ориентации панели WrapPanel перенос элементов не производится, но ширина элементов выравнивается
по самому широкому. По умолчанию WrapPanel устанавливает размеры своих потомков,
адаптируясь к их содержимому, хотя можно задать иные размеры с помощью свойств ItemWidth и ItemHeight.
Панель UniformGrid ( Uniform - однородный, одинаковый, равномерный; Grid -
сетка) распределяет потомков в равномерной сетке с заданным числом строк и
столбцов. Можно задавать только количество строк или только количество столбцов,
тогда второй параметр будет вычислен исходя из общего количества потомков.
Добавьте к решению
новый проект WpfApp7 типа WPF Application и назначьте его стартовым
Отредактируйте файл
разметки следующим образом<Window x:Class="WpfApp7.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WrapPanel, UniformGrid"
Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="WrapPanel 1">
<StackPanel>
<TextBlock Margin="5">ItemWidth="NaN" ItemHeight="23"</TextBlock>
<WrapPanel ItemWidth="NaN" ItemHeight="23">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
</WrapPanel>
<TextBlock Margin="5">ItemWidth="75" ItemHeight="23pt"</TextBlock>
<WrapPanel ItemWidth="75" ItemHeight="23pt">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
</WrapPanel>
</StackPanel>
</TabItem>
<TabItem Header="WrapPanel 2">
<StackPanel>
<TextBlock Margin="5">WrapPanel Orientation="Vertical"</TextBlock>
<WrapPanel Orientation="Vertical">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
<Button>Двенадцать</Button>
</WrapPanel>
</StackPanel>
</TabItem>
<TabItem Header="UniformGrid">
<StackPanel>
<TextBlock Margin="5" Background="White" Foreground="Red"
FontWeight="Bold" FontStyle="Italic" TextDecorations="Underline">
UniformGrid Rows="3" Columns="4"
</TextBlock>
<UniformGrid Rows="3" Columns="4">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
<Button>Одиннадцать</Button>
<Button>Двенадцать</Button>
</UniformGrid>
<TextBlock Margin="5" Background="Aqua">
<Bold>
<Italic>
<Underline>
UniformGrid Rows="2"
</Underline>
</Italic>
</Bold>
</TextBlock>
<UniformGrid Rows="2">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
</UniformGrid>
<TextBlock Margin="5">
<Run FontWeight="Bold" FontStyle="Italic" TextDecorations="Underline">
UniformGrid Columns="4"
</Run>
</TextBlock>
<UniformGrid Columns="4">
<Button>One</Button>
<Button>Two</Button>
<Button>Three</Button>
<Button>Four</Button>
<Button>Five</Button>
<Button>Six</Button>
<Button>Семь</Button>
<Button>Восемь</Button>
<Button>Девять</Button>
<Button>Десять</Button>
</UniformGrid>
</StackPanel>
</TabItem>
</TabControl>
</Window>
Результаты работы панелей будут следующими:

Когда не задана ширина потомков, то они сами устанавливают свою ширину, в зависимости от содержимого. Размещение потомков выполняется слева направо в порядке их следования в описании панели. Если общие размеры потомков заданы явно в параметрах панели, то для всех потомков они одинаковы независимо от того, вмещается в них свое содержимое или нет. Обратите внимание, что высота потомков задана в разных единицах: верхний вариант задан в аппаратно-независимых единицах ( логических пикселах ) для разрешения 96 точек на дюйм ( DPI - dots per inch ), а нижний задан в пунктах.
По умолчанию WPF использует аппаратно-независимые единицы - логические
пикселы (только SmallIconWidth и SmallIconHeight используют
физические пикселы). При таком подходе представления элементов на экране мало
зависят от его разрешения, а в большей степени определяются размерами экрана.
Сами экраны пользователи меняют значительно реже, чем разрешения, но и здесь,
если применять панели размещения, компоновка интерфейса останется почти прежней.

При вертикальной ориентации элементов перенос на следующий столбец панелью WrapPanel не выполняется.

Равномерная сетка UniformGrid распределяет потомков построчно в порядке их
следования. Ширина и высота всех ячеек сетки одинакова и определяется размерами
потомка с самым габаритным содержимым. Обратите внимание на средства стилизации
текстовых заголовков, определяемых элементами TextBlock. Одинакового эффекта
мы достигли тремя разными способами.
В панели UniformGrid все ячейки имеют одинаковый размер. В некоторых случаях
этого может быть недостаточно, тогда применяют более развитую панель Grid, у которой соседние строки могут иметь разную высоту, а соседние столбцы
- разную ширину. Элемент Grid самый гибкий и универсальный из всех
менеджеров размещения. Он восполняет применительно к окнам широко используемую
в HTML функциональность элемента Table. Недаром мастер заготовки WPF -приложения первоначально включает в разметку именно элемент Grid.
Размещение в сетке Grid состоит из двух этапов: вначале выполняется определение
строк и столбцов, а затем производится задание потомков и распределение их
по слотам. Самый простой способ использования Grid, это задать свойства RowDefinitions и ColumnDefinitions, добавить несколько дочерних элементов и с помощью присоединенных
к ним свойств Grid.Row и Grid.Column указать, какого потомка в какой слот (заготовленное
место, ячейка) поместить. Но существуют и более тонкие настройки Grid, которые
мы и рассмотрим в данном упражнении.
Добавьте к решению
новый проект WpfApp8 типа WPF Application и
назначьте его стартовым
Заполните файл разметки Window1.xaml следующим кодом<Window x:Class="WpfApp8.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Упражнение 8. Панель Grid"
Height="300" Width="300"
Background="LightGray"
>
<TabControl>
<TabItem Header="Grid 0">
</TabItem>
<TabItem Header="Grid 1">
</TabItem>
<TabItem Header="Grid 2">
</TabItem>
<TabItem Header="Grid 3">
</TabItem>
<TabItem Header="Grid 4">
</TabItem>
<TabItem Header="Grid 5">
</TabItem>
<TabItem Header="Grid 6">
</TabItem>
<TabItem Header="Grid 7">
</TabItem>
</TabControl>
</Window>
Запустите приложение
Первоначальный код является заготовкой для демонстрации вариантов работы
с панелью Grid и в данном упражнении мы последовательно заполним все вкладки
заготовки, изучая и иллюстрируя ее свойства. В ячейки сетки будем помещать
кнопки и другие контейнеры, чтобы ощутить ее реакцию. Но нестоит забывать,
что сама сетка как контейнер остается невидимой (если не задать ее фон), видна
только выполнямая ею работа по размещению потомков.
Заполните вкладку Grid 0 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">Первый</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Aquamarine">Третий (длинное содержимое)</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Две секции <Grid.RowDefinitions> и <Grid.ColumnDefinitions> определяют
строки и столбцы сетки. Дескрипторы <RowDefinition /> и <ColumnDefinition/> задают количество строк и столбцов. Здесь они применяются без параметров,
поэтому устанавливают равные условия для всех слотов (зарезервированного места)
сетки. Далее следуют размещаемые элементы, в качестве которых мы используем
разноцветные кнопки. Порядок следования потомков здесь не играет значения,
поскольку каждому из них явно указывается закрепленный за ним слот. Если содержимое
потомка не умещается в отведенный слот, то по умолчанию оно усекается.
Поизменяйте размеры
окна, чтобы почувствовать работу панели при данных настройках
Заполните вкладку Grid 1 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 1">
<Grid HorizontalAlignment="Center" VerticalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">Третий (длинное содержимое)</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Дескрипторы <RowDefinition Height="Auto" /> и <ColumnDefinition
Width="Auto" /> не только определяют количество строк и столбцов сетки,
но и могут устанавливать режимы подстройки под содержимое. Значения "Auto" параметров
высоты и ширины как раз это и означают.
Заполните вкладку Grid 2 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 2">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="50" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" MinWidth="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" MinWidth="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" MinWidth="0" Background="Aquamarine">Третий</Button>
<Button Grid.Row="1" Grid.Column="1" MinWidth="0" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
В дескрипторах определения строк и столбцов для рассматриваемого варианта
жестко задаются высота первой строки и ширина первого столбца. Установка размера
в значение "*" означает, что он нелимитирован и соответствующие строка
или столбец займут оставшееся от других свободное место родителя. Параметры MinWidth в определениях потомков являются в данном случае необязательными и
указывают минимальную границу изменения размера потомка по ширине.
Поизменяйте размеры
окна и убедитесь, что жестко заданные размеры первого слота строго выдерживаются
сеткой
Заполните вкладку Grid 3 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 3">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="2*" />
<RowDefinition Height="1*" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="2*" />
<ColumnDefinition Width="1*" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" MinWidth="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" MinWidth="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" MinWidth="0" Background="Aquamarine">Третий</Button>
<Button Grid.Row="1" Grid.Column="1" MinWidth="0" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Приведенные в каждой секции строк и столбцов данного кода определения размеров
устанавливают пропорции, которые должны поддерживаться между этим строками
или столбцами. Для любой из приведенных секций можно было бы задать и другие
размеры, например <RowDefinition
Height="20*" /> и <RowDefinition Height="10*" />, все равно пропорции
останутся прежними ( 2/1 ).
Заполните вкладку Grid 4 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 4">
<Grid HorizontalAlignment="Center" VerticalAlignment="Stretch">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Background="LightPink">Первый</Button>
<Button Grid.Row="1" Background="Lime">Второй</Button>
<Button Grid.Row="2" Background="Aquamarine">Третий - самое длинное содержимое</Button>
<Button Grid.Row="3" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Мы определили четыре строки и один столбец и задали автоматическую подстройку
под наибольшее содержимое элементов столбца. Сетка подстраивается по высоте
под свободное пространство родителя (вкладки), поскольку для нее мы задали VerticalAlignment="Stretch" (он
же и по умолчанию), а высоту ячеек не задали.
Заполните вкладку Grid 5 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 5">
<Grid Grid.IsSharedSizeScope="True" HorizontalAlignment="Center">
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" MinWidth="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" MinWidth="0" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" MinWidth="0" Background="Aquamarine">Наибольшей ширины</Button>
<Button Grid.Row="1" Grid.Column="1" MinWidth="0" Background="Yellow">Четвертый</Button>
</Grid>
</TabItem>
Оба столбца подстраиваются под свое наибольшее содержимое с помощью параметра Auto и имеют равную ширину, потому что объединены в группу с одинаковым ключом.
Это будет работать только при условии, что присоединенное к сетке свойство Grid.IsSharedSizeScope установлено
в True. Ключ выбирается произвольно, но должен быть алфавитно-цифровым. Он
не должен начинаться с цифры и не должен иметь пробелов или иных разделителей.
Связать ключом группы можно любое количество столбцов или строк и тогда все
они будут иметь равный размер.
Заполните вкладку Grid 6 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 6">
<StackPanel Grid.IsSharedSizeScope="True">
<TextBlock HorizontalAlignment="Center">
Сетки с обобществлением размеров столбцов
</TextBlock>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey1" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey2" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">Первый</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">Второй</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">
Шире для всех первых столбцов
</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">Четвертый</Button>
</Grid>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey1" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">1</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">2</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">3</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">4</Button>
</Grid>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey2" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">1</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">2</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">3</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">4</Button>
</Grid>
<StackPanel Margin="5" />
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey1" />
<ColumnDefinition Width="Auto" SharedSizeGroup="myKey2" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.Column="0" Background="LightPink">1</Button>
<Button Grid.Row="0" Grid.Column="1" Background="Lime">2</Button>
<Button Grid.Row="1" Grid.Column="0" Background="Aquamarine">3</Button>
<Button Grid.Row="1" Grid.Column="1" Background="Yellow">Шире для вторых</Button>
</Grid>
</StackPanel>
</TabItem>
В StackPanel упаковано несколько сеток и текстовый блок с вертикальным по
умолчанию размещением. Столбцы сеток включены в разные группы и выравнивают
ширину по наибольшему содержимому своей группы. Свободные столбцы сеток подстраиваются
под свое содержимое индивидуально, потому что у них установлено Width="Auto".
Отступы между сетками обеспечиваются пустыми элементами StackPanel с параметром Margin="5". Свойство Grid.IsSharedSizeScope="True" для включения механизма
выравнивания размеров групп присоединено к родительской StackPanel.
Заполните вкладку Grid 7 приведенным ниже кодом и запустите приложение
<TabItem Header="Grid 7">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" HorizontalAlignment="Center" Margin="5">
Объединение столбцов первой строки
</TextBlock>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto" MinHeight="50" MaxHeight="150" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" MinWidth="40" MaxWidth="200" />
</Grid.ColumnDefinitions>
<Button Grid.Row="0" Grid.ColumnSpan="3" Background="LightPink">Перый и второй</Button>
<GridSplitter
Grid.Row="1"
Grid.ColumnSpan="3"
Height="2"
ResizeDirection="Rows"
ResizeBehavior="PreviousAndNext"
HorizontalAlignment="Stretch" />
<Button Grid.Row="2" Grid.Column="0" Background="Aquamarine">Третий</Button>
<GridSplitter
Grid.Row="2"
Grid.Column="1"
Width="2"
ResizeDirection="Columns"
ResizeBehavior="PreviousAndNext"
VerticalAlignment="Stretch" />
<Button Grid.Row="2" Grid.Column="2" Background="Yellow">Четвертый</Button>
</Grid>
</Grid>
</TabItem>
Вначале строится сетка с двумя строками и одним столбцом. В первую строку помещен текстовый блок с заголовком и она подстраивается под его размеры, а вторая строка по высоте занимает оставшееся пространство родителя. Во вторую строку погружена внутренняя сетка из трех строк и трех столбцов. Ее последний слот позволяет потомку автоматически подстраиваться под свое содержимое, но на размеры по ширине и высоте установлены ограничения. Крайние слоты внутренней сетки заняты тремя цветными кнопками.
Кнопка, привязанная к верхней строке
внутренней сетки, занимает сразу три соседних слота в соответствии с требованием
присоединенного к ней свойства Grid.ColumnSpan="3". Всю вторую строку занимает
элемент GridSplitter, ориентированный на разделение строк свойством ResizeDirection="Rows".
Он дает возможность пользователю мышью перетаскивать границы между крайними
строками внутренней сетки. В крайних столбцах последней строки размещены кнопки,
а в средний слот погружен элемент GridSplitter, ориентированный на разделение
столбцов.
Дополните упражнение вкладкой MyGrid, которая бы повторяла интерфейс
вкладки Grid 3, но с возможностью изменения пользователем горизонтальной
и вертикальной границ между рядами кнопок. За основу возьмите код вкладки Grid 7.
Должно получиться нечто подобное изображенному на рисунке, только с изменяющим
размеры перекрестным разделителем между кнопками.

Предположим, что некоторые элементы управления мы хотим расположить равномерно по кругу с соответствующим угловым поворотом каждого из них. Это нестандартная задача и для ее решения типовых менеджеров размещения разработчиками не создано, зато предусмотрены возможности выполнить эту работу самостоятельно. Вряд ли нам понадобится в будущем это делать, но познакомиться стоит, чтобы знать, что такое возможно.
Все стандартные панели WPF наследуют абстрактному базовому классу Panel и
наш собственный менеджер тоже должен наследовать этот библиотечный класс. Класс Panel, в свою очередь, наследует от класса UIElement, поэтому наш класс расширения
приобретет от своих предков все необходимое. Класс Panel является такой вершиной
цепочки наследования, которая включает в себя все паттерны (образец,
кирпичик, строительный блок, базовый функциональный объект), необходимые для
разработки любых менеджеров размещения. В нашем случае в классе расширения
нам останется только перекрыть пару виртуальных методов.
Та часть объектной модели UIElement, которая относится к менеджеру размещения,
довольно проста. Методы Measure, MeasureCore, Arrange и ArrangeCore реализуют
два этапа размещения, а свойство Visibility говорит, должен ли потомок отображаться
и учитываться при размещении.
Свойство Visibility позволяет задать три способа участия дочернего элемента
в размещении. По умолчанию оно равно Visible: элемент отображается и занимает
место на экране. При значении Hidden элемент скрыт, но место на экране занимает.
При Collapsed дочерний элемент не отображается и место на экране не занимает.
Механизм взаимодействия размещаемого дочернего элемента и менеджера размещения состоит из двух частей:
Никакого встроенного размещения нет, все конкретные способы размещения построены по принципу расширения базовых классов.
Все элементы WPF, включая и менеджеры размещения, адаптируются к своему содержимому. Эта концепция применяется на всех уровнях построения пользовательского интерфейса: окна могут подгонять свой размер под находящиеся в них элементы, кнопки - подстраиваться под надписи на них, текстовые поля - изменять свой размер так, чтобы поместились все символы.
Менеджеры реализуют двухэтапное размещение своих дочерних элементов: измерение и установка. Вначале менеджер опрашивает каждого из своих дочерних элементов на предмет того, какой фактический размер тот предпочитает, чтобы полностью показать свое содержимое на экране. Это этап измерения. Затем менеджер соотносит эту информацию со своими настройками и возможностями, сколько он может вместить, и приступает к этапу установки.
В ходе установки родитель безусловно требует от каждого из своих дочерних элементов, чтобы тот установил определенный размер и положение. Эта модель позволяет родителю и потомку договариваться о месте на экране (месте под Солнцем). При этом фигурируют три размера:
На размеры элемента могут налагаться ограничения 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,
обозначив его произвольным именем.
Запустите упражнение
- окончательный
результат выполнения будет выглядеть примерно так
В данной работе на ряде примеров мы познакомились с принципами размещения интерфейсных элементов, которые пронизывают всю библиотеку WPF как новое средство борьбы с непредсказуемым разрешением экрана.

Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.