Windows Presentation Foundation


Министерство образования Республики Беларусь
Учреждение образования
«Белорусский государственный университет
информатики и радиоэлектроники»
Кафедраинформатики
А.А. Волосевич
WINDOWSPRESENTATIONFOUNDATION
Курс лекций
для студентов специальности I-31 03 04 Информатика
всех форм обучения
Минск 2011

СОДЕРЖАНИЕ TOC \o "1-3" \h \z \u
1. общее описание WPF PAGEREF _Toc286142925 \h 42. ПРостейшее Приложение WPF PAGEREF _Toc286142926 \h 53. XAML PAGEREF _Toc286142927 \h 74. Базовые концепции WPF PAGEREF _Toc286142928 \h 12Иерархия классов PAGEREF _Toc286142929 \h 12Свойства зависимостей и присоединённые свойства PAGEREF _Toc286142930 \h 14Маршрутизируемые события PAGEREF _Toc286142931 \h 17Многопоточность в WPF PAGEREF _Toc286142932 \h 185. СТРУКТУРа Оконного приложения WPF PAGEREF _Toc286142933 \h 19Класс Window PAGEREF _Toc286142934 \h 20Класс Application PAGEREF _Toc286142935 \h 216. Компоновка PAGEREF _Toc286142936 \h 22Размер и выравнивание PAGEREF _Toc286142937 \h 23Основные контейнеры компоновки PAGEREF _Toc286142938 \h 25Прокрутка и декорирование содержимого PAGEREF _Toc286142939 \h 307. Обзор ЭЛЕМЕНТов УПРАВЛЕНИЯ PAGEREF _Toc286142940 \h 32Элементы управления содержимым PAGEREF _Toc286142941 \h 33Списковые элементы управления PAGEREF _Toc286142942 \h 37Прочие элементы управления PAGEREF _Toc286142943 \h 428. Фигуры PAGEREF _Toc286142944 \h 459. Цвет, кисти, прозрачность PAGEREF _Toc286142945 \h 52Представление цвета в WPF PAGEREF _Toc286142946 \h 52Лучшие кисти PAGEREF _Toc286142947 \h 53Прозрачность PAGEREF _Toc286142948 \h 5710. трансформации и эффекты PAGEREF _Toc286142949 \h 5811. Классы drawing и visual PAGEREF _Toc286142950 \h 6112. РЕСУРСЫ PAGEREF _Toc286142951 \h 65Двоичные ресурсы PAGEREF _Toc286142952 \h 65Логические ресурсы PAGEREF _Toc286142953 \h 6613. ПРИВЯЗКА данных PAGEREF _Toc286142954 \h 67Базовые концепции привязки данных PAGEREF _Toc286142955 \h 67Практическое использование привязки данных PAGEREF _Toc286142956 \h 69Конвертеры значений PAGEREF _Toc286142957 \h 73Проверка данных PAGEREF _Toc286142958 \h 7514. СТИЛИ И триггеры PAGEREF _Toc286142959 \h 7815. ПРИвязкакколлекциямиШАБЛОНЫДАННЫХ PAGEREF _Toc286142960 \h 8416. представления Данных PAGEREF _Toc286142961 \h 9017. ШАблоны ЭЛЕМЕНТОВ УПРАВЛЕНИЯ PAGEREF _Toc286142962 \h 94

1. общееописаниеWPFWindowsPresentationFoundation (WPF) – технологиядля построенияпользовательскогоинтерфейса, являющаясячастьюплатформы .NET.WPF разработана как альтернатива технологии WindowsForms, которая базируется на стандартном системном программном интерфейсе для работы с элементами управления. Ниже перечислены основные особенности WPF.
1. Собственные методы построения и отрисовки элементов. ВWindowsForms классы для элементов управления делегируют функции отображения системным библиотекам, таким как user32.dll. В WPF любой элемент управления полностью строится (рисуется) самой WPF. Для аппаратного ускорения отрисовки применяется технология DirectX (рис. 1).

Рис. 1. Отрисовка в WindowsFormsи в WPF.
2. Независимость от разрешения. WPF ориентирована на использование векторных примитивов, что делает эту технологию независимой от разрешения монитора. В WPF используется особая единица измерения, равная 1/96 дюйма.
3. Декларативный пользовательский интерфейс.В WPFвизуальное содержимое отображаемого окна можнополностью описать в виде документа XAML.XAML – это язык разметки, основанный на XML.Так как описание интерфейса отделено от кода, графические дизайнеры могут использовать профессиональные инструменты, чтобы редактировать файлы XAML, улучшая внешний вид всего приложения. Применение XAML является предпочтительным, но не обязательным – приложение WPF можно конструировать, используя только код.
4. Веб-подобная модель компоновки. WPF поддерживает гибкий визуальный поток, размещающий элементы управления на основе их содержимого. В результате получается пользовательский интерфейс, который может быть адаптирован для отображения высокодинамичного содержимого.
5. Стили и шаблоны. Стили стандартизируют форматирование и позволяют повторно использовать его по всему приложению. Шаблоны дают возможность изменить способ отображения любых элементов управления, даже таких основополагающих, как кнопки или поля ввода.
6. Анимация. В WPF анимация – неотъемлемая часть программного каркаса. Анимация определяется декларативными дескрипторами, и WPF запускает её в действие автоматически.
7. Приложения на основе страниц.В WPF можно строить браузер-подобные приложения с кнопками навигации, которые позволяют перемещаться по коллекции страниц. Кроме этого, специальный тип WPF-приложения – XBAP–может быть запущен внутри браузера.
2. ПРостейшее ПриложениеWPFСледующий листинг описывает простейшее приложение WPF, включающее единственное окноинтерфейса:
//файлprogram.cs
using System;
using System.Windows;
classProgram
{
[STAThread]
staticvoid Main()
{
Window myWin = newWindow();
myWin.Title = "First Program";
myWin.Content = "Hello, world";
Application myApp = newApplication();
myApp.Run(myWin);
}
}
Проанализируем данный код. Пространство имён System.Windows содержит классы Window и Application, описывающее окно и приложение соответственно.Точка входа помечена атрибутом [STAThread]. Это является обязательным условием для любого приложенияWPF и связано с моделью многопоточности, применяемой в WPF.В методе Main() создаётся и настраивается объект окна, затем создаётся объект приложения. Вызов myApp.Run(myWin) приводит к отображению окна myWin и запуску цикл обработки событий.
Скомпилируем программу при помощи компилятора командной строки:
csc.exe/r:PresentationCore.dll;PresentationFramework.dll;WindowsBase.dll
/t:winexeprogram.cs
Припомощиключа/rустанавливаютсяссылкинасборкиPresentationFramework.dll, PresentationCore.dllиWindowsBase.dllсклассамиWPF.Ключ /t:winexe указывает, что создаётся оконное приложение.
Отметим, что наше приложение допускает другую организацию. Вместо настройки объекта Window можно создать наследник этого класс и выполнить установку параметров в конструкторе наследника или в специальном методе:
// наследник класса Window, описывающий пользовательское окно
classMyWindow : Window
{
public MyWindow()
{
Title = "First Program";
Content = "Hello, world";
}
}
ВVisualStudioприложениям WPFсоответствует отдельный шаблон проекта. Этот шаблон ориентирован на использование XAML, поэтому в случае однооконного приложения VisualStudioсоздаст следующий набор файлов:
файлы Window1.xaml.csи Window1.xaml (на языке XAML) описывают класс Window1, являющийся наследником класса Window;
файлы App.xaml.csи App.xamlописывают класс App– наследник класса Application.
Ниже приведён файлWindow1.xamlдля простейшего окна:
<Window x:Class="WpfApplication.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="First Program" Height="300" Width="300">
Hello, world
</Window>
VisualStudioвыполняеткомпиляциюпроектаWPFвдваэтапа.Вначале для каждого файлаXAML генерируется два файла, сохраняемых в подкаталогахobj\Debug или obj\Release (в зависимости от цели компиляции):
файл с расширением *.baml – двоичное представление XAML-файла, внедряемое в сборку в виде ресурса (BAML-файл);
файл с расширением *.g.cs – разделяемый класс, который соответствует XAML-описанию. Этот класс включает поля для всех именованных элементов файла XAML и реализацию метода InitializeComponent(), загружающего BAML-данные из ресурсов сборки. Кроме этого, класс содержит метод, подключающий все обработчики событий.
На втором этапе сгенерированные файлы компилируются вместе с исходными файламиC# в единую сборку (рис. 2).

Рис. 2. Компиляция WPF-приложения в VisualStudio.
3. XAMLXAML (ExtensibleApplicationMarkupLanguage, расширяемый язык разметки приложений) –это язык разметки, предлагающий основанный на XML синтаксис для представления логического дерева объектов .NET.
Существует несколько подмножеств XAML:
WPF XAML включает элементы, описывающие содержимое WPF вроде векторной графики и элементов управления;
XPS XAML– часть WPF XAML, определяющая XML-представление форматированных электронных документов. Эта часть опубликована как отдельный стандарт XML PaperSpecification (XPS);
Silverlight XAML– подмножество WPF XAML, предназначенное для Silverlight-приложений. Silverlight– это межплатформенный браузерный подключаемый модуль, позволяющий создавать веб-содержимое с двумерной графикой, анимацией, аудио и видео;
WF XAML включает элементы, описывающие содержимое WindowsWorkflowFoundation.
Рассмотрим основные правила XAML.Документ XAMLзаписан в формате XML. Это означает, что именаэлементовXAML чувствительны к регистру,нужна правильная вложенность элементов, некоторые символы требуют особого обозначения (например, &amp;–это символ &).
Объектные элементы XAMLописывают объект некоторого типа .NET (класса или структуры).Имя объектного элемента совпадает с именем типа. Необходимо, чтобы тип обладал открытым конструктором без параметров. Например, следующий объектный элемент описывает объект Button (кнопку):
<Button>Click me!</Button>
Типы .NET обычно вложены в пространства имён. В XAMLпространство имён .NETставится в соответствие пространству имён XML. Для этого используется следующий синтаксис:
xmlns:префикс="clr-namespace:ПространствоИмён"
При необходимости указывается сборка, содержащая пространство имён:
xmlns:префикс="clr-namespace:ПространствоИмён;assembly=ИмяСборки"
WPFXAMLобычноиспользует два стандартных пространства имён:
http://schemas.microsoft.com/winfx/2006/xaml/presentation–соответствует набору пространств имён .NETс классамиWPF;
http://schemas.microsoft.com/winfx/2006/xaml– содержит классы, используемые анализатором XAML (XAMLparser).
Любые пространства имён,как правило, описываются в корневом элементе XAML. При этом первое из стандартных пространств имён обычно указывается без префикса, а значит, применяется по умолчанию ко всем элементам.Пространство имён анализатора XAML традиционно указывается с префиксом x.
<Windowxmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<Button>Click me!</Button>
</Window>
Описание объекта подразумевает заданиезначений его свойств. В XAMLдля этого применяются атрибуты, элементы свойств и содержимое элемента.При использовании атрибутов указывается имя свойства и значение свойства в виде строки:
<Button Width="100" Foreground="Red">Click me!</Button>
Анализатор XAML применяет для преобразования строки в значение свойства специальные конвертеры типов. Набор стандартных конвертеров достаточно богат.При необходимости можно разработать собственный конвертер, используя базовый класс TypeConverter.
Элемент свойства – это дочерний элемент объектного элемента, имеющий вид<TypeName.Property>. Содержимое элемента свойства рассматривается как значение свойства:
<Button>
<Button.Width>100</Button.Width>
<Button.Foreground>Red</Button.Foreground>
Click me!
</Button>
Тип, соответствующий объектному элементу, может быть помеченатрибутом[ContentProperty]с указанием имени свойства содержимого.В этом случае анализатор XAMLрассматриваетсодержимое объектного элемента как значение свойства содержимого. Например, в классе Button свойством содержимого является Content:
[System.Windows.Markup.ContentProperty("Content")]
publicclassButton{ . . . }
Это означает, что следующие два фрагмента XAMLэквиваленты:
<ButtonContent="Click me!"/>
<Button>Click me!</Button>
Если тип реализует интерфейсы IList или IDictionary, при описании объектаэтого типа в XAMLдочерние элементы автоматически добавляются в соответствующую коллекцию. Например, свойство ItemsклассаListBox имеет тип ItemCollection, который реализует IList:
<ListBox>
<ListBox.Items>
<ListBoxItem Content="Item 1"/>
<ListBoxItem Content="Item 2"/>
</ListBox.Items>
</ListBox>
Кроме этого, Items – это свойство содержимого для ListBox, а значит,XAML-описание можно упростить:
<ListBox>
<ListBoxItem Content="Item 1"/>
<ListBoxItem Content="Item 2"/>
</ListBox>
Во всех предыдущих примерах использовалось конкретное указание значения свойства. Механизм расширений разметки (markupextensions) позволяет вычислять значение свойства при выполнении приложения. Встретив в XAMLрасширение разметки, анализатор генерирует код, который создаёт объект расширения разметки и вызывает особый метод объекта для получения значения.
Технически, любое расширение разметки – это класс, унаследованный от класса MarkupExtension и перекрывающий функцию ProvideValue():
using System;
using System.Windows.Markup;
namespace MarkupExtensions
{
publicclassShowTimeExtension : MarkupExtension
{
public ShowTimeExtension() { }
public ShowTimeExtension(string header)
{
Header = header;
}
publicstring Header { get; set; }
publicoverrideobject ProvideValue(IServiceProviderprovider)
{
returnstring.Format("{0}: {1}", Header, DateTime.Now);
}
}
}
Если расширение разметки используется в XAMLкак значение атрибута, то оно записывается в фигурных скобках. Если имя расширения имеет суффикс Extension, этот суффикс можно не указывать. В фигурных скобках также перечисляются параметры конструктора расширения и пары для настройки свойств расширения в видеСвойство=Значение.
<Window xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:local="clr-namespace:MarkupExtensions">
<StackPanel>
<Button Content="{local:ShowTime First}"/>
<Button Content="{local:ShowTime Header=Second}"/>
</StackPanel>
</Window>
Расширения разметки могут применяться и в виде вложенных элементов:
<Button>
<Button.Content>
<local:ShowTime Header="First" />
</Button.Content>
</Button>
Втабл. 1 представлены стандартные расширения разметки, доступные после подключения пространства имён анализатора XAML.
Таблица 1
Стандартные расширения разметки
Имя Описание
x:Array Представляет массив. Дочерние элементы становятся элементами массива:
<x:Array Type="{x:Type Button}">
<Button />
<Button />
</x:Array>
x:Null Значениеnull
Background="{x:Null}"
x:Static Представляет статическое свойство, поле или константу:
Height="{x:Static SystemParameters.IconHeight}"
x:Type Аналог применения оператора typeof из языка C#
Анализатор XAMLгенерирует код, выполняющий по документу XAMLсоздание и настройку объектов. Действия с объектами описываются в отдельном классе кода. Чтобы сослаться на объект в коде, объект должен иметь имя. Для этого в пространстве имён анализатора XAMLопределён атрибут Name:
<Buttonx:Name="okButton"Content="Click me!"/>
Заметим, что многие элементы управления WPF имеют свойство Name. Анализатор XAML использует соглашение, по которому задание свойства Name эквивалентно указанию XAML-атрибута x:Name.
Чтобы связать класс кода с документом XAMLиспользуется атрибут Class из пространства имён анализатора XAML. Этот атрибут применяется только к корневому элементу и содержит имя класса, являющегося наследником класса корневого элемента:
<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Buttonx:Name="okButton" Content="Click me!"/>
</Window>
В заключение заметим, что WPF XAMLописывает логическое дерево элементов. Наряду с этим термином используется понятие визуального дерева элементов. Визуальное дерево составляют отображаемые объекты. Некоторые одиночные логические объекты распадаются на несколько визуальных составляющих, так как строятся из нескольких визуальных примитивов. Например, любое окно Window включает визуальный примитив Border, который содержит примитив AdornerDecoratorc объектами ContentPresenterи AdornerLayer.
4. Базовые концепции WPFИерархия классовТипы, связанные с технологией WPF, сгруппированы в несколько сборок. Сборка PresentationFramework.dll содержит классы верхнего уровня–окна, панели, элементы управления. В этой сборке находятся типы для реализации высокоуровневых программных абстракций, например, стилей. Сборка PresentationCore.dll содержит базовые классы, от которых унаследованы все фигуры и элементы управления. В WindowsBase.dll описаны ещё более базовые ингредиенты, которые потенциально могут использоваться вне WPF. Кроме этого, частью WPFявляется библиотека milcore.dll, написанная на неуправляемом коде. Функции библиотекиmilcore.dll транслируют визуальные элементы в примитивы Direct3D.
Рассмотрим базу иерархии классов WPF(рис. 3).

Рис. 3. Базовые классы иерархии типов WPF.
1. System.Threading.DispatcherObject. Приложения WPF используют однопоточную модель (single-threadaffinity, STA)–весь пользовательский интерфейс принадлежит единственному потоку. Чтобы содействовать работе моделиSTA, каждое приложение WPF управляется диспетчером, координирующим обработку сообщений. Будучи унаследованным от DispatcherObject, объект может удостовериться, выполняется ли его код в правильном потоке, и обратиться к диспетчеру, чтобы направить код в поток интерфейса.
2. System.Windows.DependencyObject. WPFподдерживает мощную модель свойств зависимостей (dependencyproperty), которая положена в основу таких средств, как уведомления об изменениях, наследуемые значения по умолчанию и более экономичное хранилище информации свойств. Наследуясь от DependencyObject, классы WPF получают поддержку свойств зависимости.
3. System.Windows.Media.Visual. Любой класс, унаследованный от Visual, обладает способностью отображаться в окне. Класс Visual инкапсулирует инструкции рисования, включая отсечения, прозрачность и настройки трансформации фигур. Этот класс также обеспечивает связь между управляемыми библиотеками WPF и библиотекойmilcore.dll.
4. System.Windows.UIElement. Этот класс добавляет поддержку таких сущностей WPF, как компоновка, ввод, фокус и события (layout, input, focus, events – LIFE).В UIElementопределён двухшаговый процесс измерения и организации компоновки. Этот класс вводит поддержку расширенной системы передачи событий, именуемоймаршрутизируемыми событиями (routedevents).И, наконец,UIElement добавляет поддержку команд.
5. System.Windows.FrameworkElement. КлассFrameworkElementдобавляет поддержку привязки данных, анимации, стилей и ресурсов. Этот класс также реализует некоторые абстрактные концепции UIElement.
6. System.Windows.Controls.Control. Элемент управления (control) – это элемент, который может взаимодействовать с пользователем.Примерами элементов управления являются классы TextBox (поле для ввода текста) иButton(кнопка). Класс Control добавляет к FrameworkElement свойства для установки шрифта, а также цветов переднего плана и фона. Но наиболее интересная деталь, которую он предоставляет– это поддержка шаблонов, которая позволяет заменять стандартный внешний вид элемента управления вашим собственным.
7. System.Windows.Controls.ContentControl. Это базовый класс для всех элементов управления, которые имеют отдельный «кусок» содержимого. Наиболее впечатляющая часть этой модели заключается в том, что единственный кусок содержимого может быть чем угодно – от обычной строки до панели компоновки, содержащей комбинацию других фигур и элементов управления.
8. System.Windows.Controls.ItemsControl. Это базовый класс для всех элементов управления, которые отображают коллекцию каких-то единиц информации (например, ListBox и TreeView). Списковый элемент управления гибок–используя встроенные средства класса ItemsControl, можно трансформировать обычный ListBox в список переключателей, список флажков, ряд картинок или комбинацию разных элементов по своему выбору.
9. System.Windows.Shapes.Shape. От этого класса наследуются базовые фигуры, такие как Rectangle,Polygon,Ellipse,Line и Path. Эти фигуры могут быть использованы наряду с более традиционными визуальными элементами вроде кнопок и текстовых полей.
10. System.Windows.Controls.Panel. Это базовый класс для всех контейнеров компоновки– элементов, которые содержат в себе один или более дочерних элементов и упорядочивают их в соответствии с определёнными правилами компоновки.
Свойства зависимостейи присоединённые свойстваСвойства зависимостей(dependencyproperties)– новая модель свойств, используемая в WPFдля реализации следующих механизмов:
уведомление об изменении значения свойства;
сброс свойства к значению по умолчанию;
наследование значения свойства дочерними объектами;
эффективная модель хранения данных свойства;
привязка свойства к данным произвольного объекта.
При вычислении значения любого свойства зависимостей WPFиспользует алгоритм, состоящий из следующих пяти шагов.
Шаг 1. Определение базового значения. Этот шаг подразумевает использование следующих источников, указанных в порядке убывания приоритета:
–локально указанное (в разметке или коде) значение.
– значение, полученное от триггера стиля.
– значение,полученное от триггера шаблона.
– значение, заданное в стиле.
– значение, полученное от триггера общего стиля.
– значение, заданное вобщем стиле.
– значение, унаследованное от родительского элемента.
– значение по умолчанию.
Шаг 2. Вычисление значения.Если базовое значение является выражением привязки данных или ресурсов, производится вычисление выражения.
Шаг 3. Применение анимации. Если со свойством связана анимация, значение вычисляется по алгоритму этой анимации.
Шаг 4. Выполнение функции CoerceValueCallback.Эта функция может быть определена при регистрации свойства зависимостей. Её назначение – корректировка значения свойства на основании пользовательского алгоритма.
Шаг 5. Проверка значения. Если при регистрации свойства была задана функция ValidateValueCallback, она вызывается для проверки значения. В случае провала проверки генерируется исключение.
Рассмотрим схему описания свойства зависимостей в пользовательском типе. Эта схема включает три этапа:
Создание объекта, характеризующего метаданные свойства зависимостей. Этот этап является необязательным – для метаданных могут использоваться значения по умолчанию.
Регистрация свойства зависимостей.
Создание экземплярной оболочки для свойства зависимостей. Это также необязательный этап.
Объект метаданных свойства зависимостей – это экземпляр класса PropertyMetadata или его наследников. В табл. 2 представлены некоторые элементы FrameworkPropertyMetadata – класса-наследника PropertyMetadata.
Таблица 2
Элементы метаданных, доступные в FrameworkPropertyMetadata
Имя Описание
AffectsArrange,
AffectsMeasureЕсли эти элементы установлены в true, свойство зависимостейвлияет на расположение соседних элементов при компоновке
AffectsRenderЕсли установлено в true, свойство зависимостей может повлиять на способ отрисовки элемента
BindsTwoWayByDefault Если установлено в true, свойство использует по умолчанию двухстороннюю привязку данных
InheritsЗадаётвозможность наследования значения свойства зависимостей дочерними элементами в дереве элементов
IsAnimationProhibited Если установлено в true, свойство зависимостей не может использоваться в анимации
IsNotDataBindable Если установлено в true, свойство зависимостей не может быть задано в выражении привязки данных
DefaultValue Значение по умолчанию для свойства зависимостей
CoerceValueCallback Обеспечивает обратный вызов, при котором производится попытка «исправить» значение свойства перед его проверкой
PropertyChangedCallback Функция обратного вызова, который производится в случае изменения значения свойства
Любое свойство зависимостей должно быть зарегистрировано перед использованием. Для регистрации свойства необходимо объявить статическое поле с типом DependencyProperty, которое инициализируется при объявлении или в статическом конструкторе. Экземпляр DependencyProperty возвращается статическим методом DependencyProperty.Register(). Простейшая перегрузка метода Register() требует задания имени и типа свойства зависимостей, а также типа владельца свойства. Кроме этого, может быть указан объект метаданных свойства зависимостей и функция обратного вызоваValidateValueCallback.
Ниже приведён фрагмент кода, демонстрирующий регистрацию свойства зависимостей в некотором классе. Обратите внимание на используемое соглашение об именах (суффикс Property в имени поля для свойства зависимостей).
publicclassMyControl : DependencyObject
{
publicstaticDependencyProperty TitleProperty;
static MyControl()
{
// в метаданных указываем только значение по умолчанию
var meta = newPropertyMetadata {DefaultValue = "My title"};
TitleProperty = DependencyProperty.Register(
"Title",// имясвойства
typeof(string),// типсвойства
typeof(MyControl),// типвладельца
meta,// метаданные
IsTitleValid); // функцияпроверки
}
privatestaticbool IsTitleValid(object value)
{
return !string.IsNullOrEmpty((string)value);
}
}
Для удобства использования свойства зависимостей можно создать экземплярную оболочку свойства. Аксессор и мутатор оболочки должны использовать методы класса DependencyObject для установки и чтения значения свойства зависимостей. Дополнительную работу в аксессоре и мутаторе выполнять не рекомендуется, так как некоторые классы WPFмогут обращаться к свойству зависимостей, минуя экземплярную оболочку.
publicclassMyControl : DependencyObject
{
// продолжениекодакласса
publicstringTitle
{
get { return (string) GetValue(TitleProperty); }
set { SetValue(TitleProperty, value);}
}
}
ВклассеDependencyObjectопределёнметодClearValue(), сбрасывающийсвойствозависимостейвзначениепоумолчанию.Класс DependencyPropertyтакже имеет экземплярный метод AddOwner(), чтобы одно свойство зависимостей разделялось между несколькими классами-владельцами.
Присоединённое свойство (attached property) определяется в одном типе, а применяется в объектах других типов. Характерный пример присоединённых свойств дают контейнеры компоновки. Например, контейнерCanvas определяет присоединённые свойства Left,Right,Тор и Bottom для задания позиции элемента. Ниже приведён фрагмент XAML, описывающий абсолютно позиционированную кнопку (обратите внимание на синтаксис использования присоединённых свойств):
<Canvas>
<Button Canvas.Left="30" Canvas.Top="40" Content="OK"/>
</Canvas>
Технически, присоединённые свойства – особый вид свойств зависимостей. Для регистрации присоединённого свойства следует использовать метод DependencyProperty.RegisterAttached(). Экземплярная оболочка для присоединённого свойства не создаётся. Вместо этого нужно описать статические методы доступа и установки значения свойства. Имена методов должны включать префиксы Setи Get (это условие важно для анализатора XAML).
// сделаем свойство Titleприсоединённым свойством
publicclassMyControl : DependencyObject
{
publicstaticDependencyProperty TitleProperty =
DependencyProperty.RegisterAttached("Title",
typeof(string),
typeof(MyControl));
publicstaticvoid SetTitle(DependencyObject element, object value)
{
element.SetValue(TitleProperty, value);
}
publicstaticstring GetTitle(DependencyObject element)
{
return (string)element.GetValue(TitleProperty);
}
}
МаршрутизируемыесобытияМаршрутизируемые события (routedevents) – модель событий WPF, созданная для использования в дереве визуальных элементов. При генерации маршрутизируемого события информация о нём может быть передана как родительским, так и дочерним элементам источника событий.
Реализация и использование маршрутизируемых событий имеет много общего со свойствами зависимостей. Рассмотрим в качестве примера реализацию события Clickв элементе управления Button:
publicclassButton : ButtonBase
{
// статическое поле для маршрутизируемого события
publicstaticRoutedEventClickEvent=
EventManager.RegisterRoutedEvent("Click",
RoutingStrategy.Bubble,
typeof(RoutedEventHandler),
typeof(Button));
// экземплярнаяоболочкасобытия
publiceventRoutedEventHandler Click
{
add { AddHandler(ClickEvent, value); }
remove { RemoveHandler(ClickEvent, value); }
}
// внутреннийметоддлягенерациисобытия
protectedoverridevoidOnClick(EventArgse)
{
RaiseEvent(newRoutedEventArgs(ClickEvent, this));
}
}
МетодыAddHandler(),RemoveHandler()иRaiseEvent() – этометодыклассаUIElement. Листинг показывает, что при регистрации маршрутизируемого события используется метод EventManager.RegisterRoutedEvent(). Одним из аргументов метода является элемент перечисления RoutingStrategy, описывающего стратегию маршрутизации события:
Tunnel – событие генерируется в корневом элементе, затем в каждом дочернем элементе, пока не будет достигнут элемент-источник.
Bubble – событиегенерируетсявэлементеисточнике, затемвкаждомродительскомэлементе, вплотьдокорнядереваэлементов.
Direct – событиегенерируетсятольковэлементе-источнике.
Обработчики маршрутизируемых событий принимают аргумент RoutedEventArgs. Этот класс содержит четыре полезных свойства:
Source – источник события в логическом дереве элементов;
OriginalSource – источник события в визуальном дереве элементов;
Handled – это значение можно установить в true для остановки маршрутизации события в дереве;
RoutedEvent – объект, описывающий маршрутизируемое событие.
КлассUIElementопределяетмножествомаршрутизируемыхсобытий, связанных с клавиатурой, мышью, стилусом. БольшинствособытийиспользуютBubble-стратегию. МногиеBubble-события имеют сопряжённое Tunnel-событие, которое генерируется перед Bubble-событием (Tunnel-событие отличает префикс Preview в названии).
Многопоточность в WPFЭлементы WPF, отображаемые в одном окне, обладают потоковым родством (thread affinity). Это означает, что поток, который создал их, владеет ими,а другие потоки не могут взаимодействовать с этими элементами напрямую.
С набором элементов, обладающих потоковым родством, связан диспетчер, который принимает методы и ставит их на выполнение в очередь потока элементов. Чтобы элемент мог обладать потоковым родством и иметь доступ к диспетчеру, он должен наследоваться от класса DispatcherObject. Этот класс имеет всего три члена:
Dispatcher – свойство, возвращающее диспетчер потока;
CheckAccess()– метод, который возвращает true, если код находится в потоке элемента;
VerifyAccess() – если вызывающий код находится не в потоке элемента, данный метод генерирует исключение InvalidOperationException.
Диспетчер– это экземпляр класса System.Windows.Threading.Dispatcher. Метод диспетчера BeginInvoke() используется для того, чтобы спланировать код в качестве задачи для диспетчера:
// предположим, что это обработчик нажатия некоторой кнопки
privatevoid btn_Click(object sender, RoutedEventArgs e)
{
newThread(UpdateTextRight).Start(); // запускаемновыйпоток
}
privatevoid UpdateTextRight()
{
Thread.Sleep(3000); // имитируемработу
// обновим интерфейс
// для этого получаем ссылку на диспетчер (у текущего окна)
// а затем используем метод BeginInvoke()
Dispatcher.BeginInvoke(DispatcherPriority.Normal,
(Action)(() =>txt.Text = "Новыйтекст"));
}
Для организации в приложении WPFасинхронного выполнения можно использовать объект класса System.ComponentModel.BackgroundWorker.Перечислим основные элементы этого класса.
RunWorkerAsync()– вызов метода начинает выполнение асинхронной операции. Имеется перегруженная версия, принимающая параметр асинхронной операции (аргумент типа object).
CancelAsync()– вызов метода означает запрос на отмену асинхронной операции.
DoWork–событие; его обработчик становится асинхронной операцией.
RunWorkerCompleted– это событие генерируется, если асинхронная операция завершилась, была отменена или прекращена из-за исключения.
IsBusy–булево свойство; позволяет узнать, выполняется ли операция.
5. СТРУКТУРа Оконного приложения WPFПриложения WPF допускают несколько вариантов организации. Самый распространённый вариант – приложение в виде набора окон.
Класс WindowТехнология WPFиспользует для отображения стандартныеокна операционной системы. Отдельное окно представлено классомSystem.Windows.Window. Это элемент управления со свойством содержимого Content. Обычносодержимым окна является контейнер компоновки.
Рассмотрим основные элементы класса Window. Внешний вид окна контролируют свойства Icon, Title (заголовок окна) и WindowStyle (стиль рамки). Начальная позиция окна управляется свойствами Leftи Top или свойством WindowStartupLocationсо значениямиCenterScreen, CenterOwner, Manual. Чтобы окно отображалось поверх всех окон, нужно установить свойство Topmostв true. Свойство ShowInTaskbarуправляет показом окна на панели задач.
<Window x:Class="WpfLayout.MainWindow" Title="Main Window"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="230" Width="250" Topmost="True"
WindowStartupLocation="CenterScreen" WindowStyle="ToolWindow">
<!-- содержимое окна -->
</Window>
Чтобы программно показать окно, нужно создать экземпляр окна и вызвать его метод Show()или ShowDialog(). Первый метод отображает немодальное окно. Второй метод обычно применяется для модальных диалоговых окон, так как ожидает закрытия окна и возвращает то значение, которое было установлено свойствуDialogResult. Метод окна Hide()прячет окно, метод Close() – закрывает окно.
MyDialogWindow dialog = newMyDialogWindow();
// отображаем диалог и ждём, пока его не закроют
// у DialogResult типbool?
if (dialog.ShowDialog()==true)
{
// диалогзакрыли, нажавнаOK
}
Класс Window поддерживает отношение владения. Если отношение установлено, дочернее окно минимизируется или закрывается, когда соответствующие операции выполняет его владелец. Чтобы установить отношение, задайте у дочернего окна свойство Owner. Коллекция окна OwnedWindows содержит все дочерние окна (если они есть).
Опишем некоторые события, связанные с окном. Заметим, что класс FrameworkElement определяетсобытия времени существования, генерируемые при инициализации (Initialized), загрузке (Loaded) или выгрузке (Unloaded) элемента. СобытиеInitialized генерируется после создания элемента и определения его свойств в соответствии с разметкой XAML. Это событие генерируется сначала вложенными элементами, затем их родителями. Событие Loaded возникает после того, как все окно было инициализировано, и были применены стили и привязка данных. Событие Loaded сначала генерирует вмещающее окно, после чего его генерируют остальные вложенные элементы.
При изменении статуса активности окна генерируются события ActivatedиDeactivated. Событие Closing генерируется при попытке закрытия окна и даёт возможность отклонить эту операцию. Если закрытие окна все же происходит, генерируется событие Closed.
Ниже приведён листинг, демонстрирующий работу с некоторыми событиями окна. Обратите внимание – вместо назначения обработчиков событий используется перекрытие виртуальных методов, вызывающих события.
publicpartialclassMainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
protectedoverridevoid OnClosing(CancelEventArgs e)
{
base.OnClosing(e);
if (MessageBox.Show("Do you want to exit?","Exit",
MessageBoxButton.YesNo,
MessageBoxImage.Question)
== MessageBoxResult.No)
{
e.Cancel = true;
}
}
protectedoverridevoid OnClosed(EventArgs e)
{
base.OnClosed(e);
// здесь можно сохранить состояние окна
}
protectedoverridevoid OnInitialized(EventArgs e)
{
base.OnInitialized(e);
// здесь можно восстановить состояние окна
}
}
КлассApplicationКлассSystem.Windows.Applicationпомогаеторганизоватьточкувхода дляоконногоприложения WPF. Этот класс содержит метод Run(), поддерживающий цикл обработки сообщений системы для указанного окна до тех пор, пока окно не будет закрыто:
Window myWin = newWindow();
Application myApp = newApplication();
myApp.Run(myWin);
СвойствоStartupUriклассаApplicationслужитдляуказанияглавногоокнаприложения. Если главное окно задано, метод Run()можно вызывать без аргументов:
Applicationapp = newApplication();
app.StartupUri = newUri("MainWindow.xaml", UriKind.Relative);
app.Run();
При разработке в VisualStudioдля каждого оконного приложения WPF создаётся класс, наследуемыйот Application, который разделён на XAML-разметку и часть с кодом. ИменновразметкеXAMLзадаётсяStartupUri:
<Applicationx:Class="WpfApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml">
<Application.Resources></Application.Resources>
</Application>
Класс Applicationсодержит несколько полезных свойств. При помощи статического свойства Application.Currentможно получить ссылку на объект, представляющий приложение. Коллекция Windows содержит все открытые окна приложения. Стартовое окно хранится в свойстве MainWindow (оно доступно для чтения и записи). Свойство ShutdownMode принимает значения из одноимённого перечисления и задаёт условие закрытия приложения. Словарь Properties позволяет хранить произвольную информацию с ключами любого типа. Этот словарь может использоваться для данных, разделяемых между окнами.
СобытияклассаApplicationвключаютStartupиExit, ActivatedиDeactivated, а также событиеSessionEnding, генерируемоепривыключениикомпьютера или окончании сессии Windows. События обычно обрабатываются путём перекрытия виртуальных методов, вызывающих их генерацию.
6. КомпоновкаВ WPFкомпоновка (layout) – это процесс размещения визуальных элементов на поверхности родительского элемента. Компоновка состоит из двух фаз:
Фаза измерения (measure). В этой фазе родительский контейнер запрашивает желаемый размер у каждого дочернего элемента, которые, в свою очередь, выполняют фазу измерения рекурсивно.
Фаза расстановки (arrange). Родительский контейнер сообщает дочерним элементам их истинные размеры и позицию, в зависимости от выбранного способа компоновки.
Размер и выравниваниеРассмотрим некоторые свойства элементов WPF, связанные с процессом компоновки. Свойство Visibility, определённое в классе UIElement, управляет видимостью элемента. Это свойство принимает значение из перечисления System.Windows.Visibility:
Visible – элемент виден на визуальной поверхности.
Collapsed – элемент не виден на визуальной поверхности и не участвует в процессе компоновки.
Hidden– элемент не виден на визуальной поверхности, но участвует в процессе компоновки («занимает место»).
В классе FrameworkElement определён набор свойств, ответственных за размер, отступы и выравнивание отдельного элемента (табл. 3).
Таблица 3
Свойства размера, отступа, выравнивания
Имя Описание
HorizontalAlignment Определяет позиционирование дочернего элемента внутри контейнера компоновки, если доступно дополнительное пространство по горизонтали. Доступны значенияCenter, Left, Right, Stretch
VerticalAlignment Определяет позиционирование дочернего элемента внутри контейнера компоновки, когда доступно дополнительное пространство по вертикали. Доступны значенияCenter, Top, Bottomили Stretch
Margin Добавляет пространство вокруг элемента. Margin– это экземпляр структуры System.Windows.Thickness, с отдельными компонентами для верхней, нижней, левой и правой стороны
MinWidth и MinHeight Устанавливает минимальные размеры элемента. Если элементслишком велик, он будет усечен
MaxWidth и MaxHeight Устанавливает максимальные размеры элемента. Если контейнеримеет свободное пространство, элемент не будет увеличен сверх указанных пределов, даже если свойства HorizontalAlignmentи VerticalAlignmentустановлены в Stretch
Width и Height Явно устанавливают размеры элемента. Эта установка переопределяет значение Stretchдля свойств HorizontalAlignmentи VerticalAlignment. Однако размер не будет установлен, если выходит за пределы, заданные в MinWidth иMinHeight
В FrameworkElement свойства Width и Height установлены по умолчанию в значение Double.NaN. Это означает, что элемент будет иметь такие размеры, которые нужны для отображения его содержимого. В разметке XAMLзначению Double.NaN для свойств размера соответствует строка "NaN" или (что более предпочтительно) строка "Auto". Также в классе FrameworkElementопределены свойства только для чтения ActualWidth и ActualHeight, содержащие действительные отображаемые размеры элемента после фазы расстановки.
Следующий пример демонстрирует компоновку с элементами, у которых установлены некоторые свойствами размера и позиционирования. Обратите внимание на различныеспособы установки свойства Margin:
Одно значение – одинаковые отступы для всех четырёх сторон;
Два значения – отступы для левой/правой и верхней/нижней сторон;
Четыре числа – отступы для левой, верхней, правой и нижней стороны.
<StackPanel>
<Button HorizontalAlignment="Left">Button 1</Button>
<Button HorizontalAlignment="Right">Button 2</Button>
<Button Margin="10" Height="35">Button 3</Button>
<Button Margin="10,5,25,10">Button 4</Button>
<Button Margin="10,20" MaxWidth="70">Button 5</Button>
</StackPanel>

Рис. 4. Использование свойств размера и позиционирования.
В элементах управления, унаследованных от класса Control, определены свойства отступа и выравнивания длядочернего содержимого. За выравнивание отвечают свойства HorizontalContentAlignment и VerticalContentAlignment. Они поддерживают те же значения, что и свойства HorizontalAlignment и VerticalAlignment. Свойство Padding позволяет вставить пустое пространство между краями элемента управления и краями содержимого. Его тип и способ задания аналогичны свойствуMargin.
Основные контейнеры компоновкиКонтейнер компоновки – это класс, реализующий определённую логику компоновки дочерних элементов. WPF предлагает ряд стандартных контейнеров, основные контейнерыперечислены в табл. 4.
Таблица 4
Основные контейнеры компоновки
Имя Описание
Canvas Позволяет элементам позиционироваться по фиксированным координатам
StackPanel Размещает элементы в горизонтальный или вертикальный стек. Этот контейнер обычно используется в небольших секциях сложного окна
WrapPanel Размещает элементы в сериях строк с переносом. Например, в горизонтальной ориентации WrapPanel располагает элементы в строке слева направо, затем переходит к следующей строке
DockPanel Выравнивает элементы по краю контейнера
Grid Выстраивает элементы в строки и колонки невидимой таблицы
Все контейнеры компоновки WPF являются панелями, которые унаследованы от абстрактного класса System.Windows.Controls.Panel. Этот класс включаетнесколькополезных свойств:
Background – кисть, используемая для рисования фона панели. Кисть нужно задать, если панель должна принимать события мыши (как вариант, это может быть прозрачная кисть).
Children–коллекция элементов, находящихся в панели. Это первый уровень вложенности– другими словами, это элементы, которые сами могут содержать дочерние элементы.
IsItemsHost–булево значение, равное true, если панель используется для показа элементов, ассоциированных с ItemsControl.
ZIndex–присоединяемое свойство класса Panel, целое число для задания высоты визуального слоя элемента.Элементы с большим ZIndex отрисовываются поверх элементов с меньшим значением.
Canvas – контейнер компоновки, реализующий «классическое» позиционирование элементов путём указания фиксированных координат. Для задания позиции элемента следует использовать присоединённые свойства Canvas.Left, Canvas.Right, Canvas.Top, Canvas.Buttom. Эти свойства определяют расстояние от соответствующей стороны Canvas до ближайшей грани элемента. Использование контейнера Canvas демонстрируется в следующем примере:
<Canvas>
<ButtonBackground="Red">Left=0, Top=0</Button>
<Button Canvas.Left="18" Canvas.Top="18"Background="Orange">
Left=18, Top=18</Button>
<Button Canvas.Right="18" Canvas.Bottom="18"Background="Yellow">
Right=18, Bottom=18</Button>
<Button Canvas.Right="0" Canvas.Bottom="0"Background="Lime">
Right=0, Bottom=0</Button>
<Button Canvas.Right="0" Canvas.Top="0"Background="Aqua">
Right=0, Top=0</Button>
<Button Canvas.Left="0" Canvas.Bottom="0"Background="Magenta">
Left=0, Bottom=0</Button>
</Canvas>

Рис. 5. Кнопки в контейнереCanvas.
StackPanel– популярный контейнер компоновки, который размещает дочерние элементы последовательно, по мере их объявления в контейнере. StackPanelимеет единственное свойство для настройки. Свойство Orientation управляет направлением размещения дочерних элементов и принимает значения из одноимённого перечисления:Vertical (по умолчанию) или Horizontal.
<StackPanel Orientation="Horizontal">
<Button Background="Red" Content="1" />
<Button Background="Orange" Content="2" />
<Button Background="Yellow" Content="3" />
<Button Background="Lime" Content="4" />
<Button Background="Aqua" Content="5" />
<Button Background="Magenta" Content="6" />
</StackPanel>

Рис. 6. StackPanel с горизонтальной ориентацией.
WrapPanel – это контейнер компоновки, который во многом аналогичен StackPanel. ОднакоWrapPanel использует автоматический перенос элементов, для которых не хватает вертикального (горизонтального) пространства, в новый столбец (строку). WrapPanelподдерживает несколько свойств настройки:
Orientation –свойство аналогично одноименному свойству уStackPanel, но по умолчанию использует значение Horizontal.
ItemHeight – единая мера высоты для всех дочерних элементов. В рамках заданной единой высоты каждый дочерний элемент располагается в соответствие со своим свойством VerticalAlignment или усекается.
ItemWidth – единая мера ширины для всех дочерних элементов. В рамках заданной единой ширины каждый дочерний элемент располагается в соответствие со своим свойством HorizontalAlignment или усекается.
По умолчанию, свойства ItemHeight и ItemWidth не заданы (имеют значение Double.NaN). В этой ситуации ширина столбца (высота строки) определяется по самому широкому (самому высокому) дочернему элементу.
<WrapPanel ItemHeight="80">
<Button Width="60" Height="40" Background="Red" Content="1" />
<Button Width="60" Height="20"Background="Orange" Content="2" />
<Button Width="60" Height="40"Background="Yellow" Content="3" />
<Button Width="60" Height="20" VerticalAlignment="Top"
Background="Lime" Content="4" />
<Button Width="60" Height="40" Background="Aqua" Content="5" />
<Button Width="60" Height="20"Background="Magenta" Content="6" />
</WrapPanel>

Рис. 7. Элементы на WrapPanel для разной ширины окна.
DockPanel(док) реализует примыкание (docking) дочерних элементов к одной из своих сторон. Примыкание настраивается при помощи присоединённого свойства Dock, принимающего значения Left, Top, Right и Bottom(перечислениеSystem.Windows.Controls.Dock). Примыкающий элемент растягивается на всё свободное пространство дока по вертикали или горизонтали (в зависимости от стороны примыкания), если у элемента явно не задан размер. Порядок дочерних элементов в доке имеет значение. Если в DockPanel свойство LastChildFill установлено в true (это значение по умолчанию), последний дочерний элемент занимает всё свободное пространство дока.
<DockPanel>
<Button DockPanel.Dock="Top" Background="Red">1 (Top)</Button>
<Button DockPanel.Dock="Left" Background="Orange">2 (Left)</Button>
<Button DockPanel.Dock="Right" Background="Yellow">3 (Right)</Button>
<Button DockPanel.Dock="Bottom" Background="Lime">4 (Bottom)</Button>
<Button Background="Aqua">5</Button>
</DockPanel>

Рис. 8.Док с набором кнопок.
Grid–один из наиболее гибких и широко используемых контейнеров компоновки. Gridорганизует пространство как таблицу с настраиваемыми столбцами и строками. Дочерние элементы размещаются в указанных ячейках таблицы. Ниже показан пример работы с контейнером Grid:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<Button Grid.Column="0" Grid.Row="0"
Background="Red"Content="1" />
<Button Grid.Column="1" Grid.Row="0" Width="60"
Background="Orange" Content="2" />
<Button Grid.Column="2" Grid.Row="0"
Background="Yellow" Content="3" />
<Button Grid.Column="0" Grid.Row="1"
Background="Lime" Content="4" />
<Button Grid.Column="1" Grid.Row="1" Grid.ColumnSpan="2"
Background="Aqua" Content="5" />
</Grid>

Рис. 9.Демонстрация Grid.
При настройке Grid необходимо задать набор столбцов и строк с помощью коллекций ColumnDefinitions и RowDefinitions. Для столбцов может быть указана ширина, для строк – высота. При этом допустимо использовать абсолютное значение, автоподбор по содержимому или пропорциональный размер. В последнем случае применяется символ * и необязательный коэффициент пропорциональности. В примере, приведённом выше, высота второй строки равна удвоенной высоте первой строки, независимо от высоты окна.
Дочерние элементы связываются с ячейками Grid при помощи присоединённых свойств Grid.Column и Grid.Row. Если несколько элементов расположены в одной ячейке, они наслаиваются друг на друга. Один элемент может занять несколько ячеек, определяя значениядля присоединённых свойствGrid.ColumnSpan и Grid.RowSpan.
Контейнер Grid позволяет изменять размеры столбцов и строк при помощи перетаскивания, если используется элемент управления GridSplitter. Вот несколько правил работы с этим элементом:
GridSplitter должен быть помещён в ячейку Grid. Лучший подход заключается в резервировании специального столбца или строки для GridSplitter со значениями Height или Width, равными Auto.
GridSplitter всегда изменяет размер всей строки или столбца,а не отдельной ячейки. Чтобы сделать внешний вид GridSplitter соответствующим такому поведению, растяните его по всей строке или столбцу, используя свойства RowSpan или ColumnSpan.
Изначально GridSplitter настолько мал, что его не видно. Дайтеему минимальный размер. Например, в случае вертикальной разделяющей полосы установитеVerticalAlignment в Stretch, a Width– в фиксированный размер.
Выравнивание GridSplitter определяет, будет ли разделительная полоса горизонтальной (используемой для изменения размеров строк) или вертикальной (для изменения размеров столбцов). В случае горизонтальной разделительной полосы установите VerticalAlignment в Center (что принято по умолчанию). В случае вертикальной разделительной полосы установите HorizontalAlignment в Center.
Ниже приведён фрагмент разметки, использующей горизонтальный GridSplitter:
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="100" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto" />
<RowDefinition Height="2*"/>
</Grid.RowDefinitions>
<GridSplitter Grid.Column="0" Grid.Row="1" Grid.ColumnSpan="3"
HorizontalAlignment="Stretch" Height="3"
VerticalAlignment="Center"/>
</Grid>
Классы RowDefinition и ColumnDefinition обладают строковым свойством SharedSizeGroup, при помощи которого строки или столбцы объединяются в группу, разделяющую размер. Это означает, что при изменении размера одного элемента группы (строки или столбца), другие элементы автоматически получают такой же размер. Разделение размеров может быть выполнено как в рамках одного контейнера Grid, так и между несколькими Grid. В последнем случае необходимо установить свойство Grid.IsSharedSizeScope в значение trueдля внешнего контейнера компоновки:
<Grid IsSharedSizeScope="True">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="myGroup"/>
<ColumnDefinition/>
<ColumnDefinition SharedSizeGroup="myGroup"/>
</Grid.ColumnDefinitions>
<!-- определение элементов Grid -->
</Grid>
Прокрутка и декорирование содержимогоНеобходимость в прокрутке возникает, если визуальное содержимое выходит за границы родительского элемента. Панель прокрутки – это элемент управления содержимымScrollViewer. Его свойства VerticalScrollBarVisibility и HorizontalScrollBarVisibilityуправляют видимостью полос прокрутки и принимают значение из перечисления ScrollBarVisibility:
Visible – полоса прокрутки видима, даже если в ней нет необходимости.
Auto – полоса прокрутки появляется только тогда, когда содержимое не помещается в визуальных границах панели прокрутки.
Hidden – полоса прокрутки не видна, но прокрутку можно выполнить программно или используя клавиатуру.
Disabled – полоса прокрутки не видна, прокрутку выполнить нельзя.
ЭлементScrollViewer имеет методы для программной прокрутки:
Вертикальнаяпрокрутка: LineUp(), LineDown(), PageUp(), PageDown(), ScrollToHome(), ScrollToEnd().
Горизонтальнаяпрокрутка: LineLeft(), LineRight(), PageLeft(), PageRight(),ScrollToHorizontalOffset(),ScrollToLeftEnd(), ScrollToRightEnd().
Одной из особенностей ScrollViewer является возможность участия содержимого в процессе прокрутки. Такое содержимое должно быть представлено объектом, реализующим интерфейс IScrollInfo. Кроме этого, необходимо установить свойствоScrollViewer.CanContentScroll в значение true. Интерфейс IScrollInfoреализуют всего несколько элементов. Одним из них является контейнер StackPanel. Его реализация интерфейса IScrollInfoобеспечиваетлогическую прокрутку, которая осуществляет переход от элемента к элементу, а не от строки к строке.
<ScrollViewer CanContentScroll="True">
<StackPanel>
<Button Height="100" Content="1"/>
<Button Height="100" Content="2"/>
<Button Height="100" Content="3"/>
<Button Height="100" Content="4"/>
</StackPanel>
</ScrollViewer>
Декораторыобычно служат для того, чтобы графически разнообразить и украсить область вокруг объекта.Все декораторы являются наследниками класса System.Windows.Controls.Decorator. Большинство декораторов – это специальные классы, предназначенные для использования вместе с определёнными элементами управления.Есть два общих декоратора, применять которые имеет смысл при создании пользовательских интерфейсов: Border и Viewbox.
Декоратор Borderпринимает вложенное содержимое и добавляет к нему фон или рамку. Для управления Border можно использовать свойства размера и отступа, а также особые свойства, перечисленные в табл. 5.
Таблица 5
Свойства декоратора Border
Имя Описание
Background С помощью объекта Brushзадаёт фон, который отображается за всем содержимым в рамке
BorderBrush,
BorderThickness Свойства задают цвет (объект Brush) и ширину рамки. Чтобы показать рамку, нужно задать оба свойства. У рамки можно отдельно настроить ширину каждой из четырёх сторон
CornerRadius Позволяет закруглить углы рамки. Можно отдельно настроить радиус закругления каждого угла
<Border Margin="5" Padding="5" VerticalAlignment="Top"
Background="LightYellow" BorderBrush="SteelBlue"
BorderThickness="3,5,3,5" CornerRadius="3">
<StackPanel>
<Button Margin="3" Content="One" />
<Button Margin="3" Content="Two" />
<Button Margin="3" Content="Three" />
</StackPanel>
</Border>

Рис. 10. Декоратор Border.
Декоратор Viewbox масштабирует своё содержимое так, чтобы оно умещалось в этом декораторе.По умолчанию Viewbox выполняет масштабирование, которое сохраняет пропорции содержимого. Это поведение можно изменить при помощи свойстваStretch. Например, если свойству присвоить значение Fill, содержимое внутри Viewbox будетрастянуто в обоих направлениях.Кроме этого, можно использовать свойство StretchDirection. Оно управляет масштабированием, когда содержимое достаточно мало (или слишком велико), чтобы уместиться в Viewbox.
7. Обзор ЭЛЕМЕНТов УПРАВЛЕНИЯ
Рис. 11. Элементы управления.
Формально, к элементам управления относятся классы, унаследованные от классаControl. Рассмотрим некоторые собственные свойства этого класса, разбив их на группы:
Внешний вид: свойство Templateзадаёт шаблон, полностью определяющий внешний вид элемента управления.
Позиционирование:свойстваPadding,HorizontalContentAlignmentиVerticalContentAlignment.
Цвета и окантовка:
Foreground–кисть (цвет) для отображения содержимого;
Background–кисть для фона элемента;
BorderBrush и BorderThickness–кисть и ширина окантовки.
Шрифт содержимого:
FontFamily– имя семейства шрифтов (например, "Arial").Можно указать несколько шрифтов, разделяя их запятыми– WPF выберет первый установленный в системе шрифт из списка;
FontSize– размер шрифта в единицах WPF. Обычные Windows-приложения измеряют шрифты с помощью точек (points), которые равны 1/72 дюйма на стандартном мониторе. Этот размер нужно умножить на 4/3, чтобы получить размер в единицах WPF;
FontStyle– наклон текста (объект FontStyle). Необходимый предварительно заданный набор можно получить из статических свойств класса FontStyles: Normal, Italic или Oblique;
FontWeight–вес текста (объект FontWeight).Предварительно заданный набор можно получить, используя статические свойства класса FontWeight;
FontStretch– величина, на которую растягивается или сжимается текст (объект FontStretch).
Табуляция: целочисленное свойство TabIndexи булево IsTabStop.
Элементы управления содержимымЭлементы управления содержимым – это элементы, допускающие размещение единственного дочернего элемента, представляющего их содержимое. Элементы управления содержимым наследуются от класса ContentControl. У этого класса имеется объектное свойство Content и булево свойство HasContent.Если в Content помещается объект-наследник UIElement, для отображения вызывается метод OnRender()этого объекта. Для объектов других типов делается попытка применить шаблон данных. Если шаблон данных не задан в свойстве ContentTemplateили в ресурсах приложения, у дочернего элемента вызывается ToString(), а результат обрамляется в элемент TextBlock.
Элементы управления содержимым можно разбить на три подгруппы: кнопки, простые контейнеры, контейнеры с заголовком.
Все кнопки наследуются от абстрактного класса ButtonBase. Этот класс содержит событие Click, булево свойство IsPressedи свойство ClickMode– оноопределяет момент генерирования Click. Значениями ClickMode являются элементы одноимённого перечисления: Release (по умолчанию), Press иHover.
Элемент управленияButton определяет обычную кнопку, которая может быть кнопкой по умолчанию или кнопкой отмены. Это поведение контролируется булевыми свойствами IsDefault и IsCancel. Кнопка по умолчанию срабатывает при нажатии на клавиатуреENTER, кнопка отмены – при нажатии ESC.
ЭлементRepeatButton–кнопка, непрерывно генерирующая событие Click, если на ней задержать нажатие. Частота генерации события Clickопределяется свойствами Delayи Interval (целое число миллисекунд).
ToggleButton–кнопка с «залипанием». Будучи нажатой, она остаётся в таком состоянии, пока по ней не произведут повторное нажатие. Текущее состояние кнопки определяется свойствомIsChecked с типом bool?(если свойство IsThreeStateустановлено в true, нажатия на кнопку заставляют IsCheckedменяться по схеме «true-null-false»). При изменении свойства IsChecked кнопка ToggleButton генерирует события Checked, UncheckedиIndeterminate.
В WPFфлажок-переключатель формально относится к кнопкам и представлен классом CheckBox. Этот класс является прямым наследником ToggleButton с переписанным шаблоном внешнего вида.
Элемент управления RadioButton также унаследован от ToggleButton. Особенность RadioButton– поддержка группового поведения, при котором установка одного переключателя сбрасывает остальные. По умолчанию к одной группе относятся переключатели, имеющие общий родительский контейнер. Можно задать строковое свойство RadioButton.GroupName, чтобы определить собственный способ выделения группы переключателей.
<StackPanel Margin="10">
<Button Content="Ordinary Button" FontFamily="Arial"
Click="Button_Click" />
<CheckBox IsChecked="True" Content="CheckBox" />
<RadioButton GroupName="A">Option 1</RadioButton>
<RadioButton GroupName="A">Option 2</RadioButton>
<RadioButton GroupName="B">A Different Option 1</RadioButton>
<RadioButton GroupName="B">A Different Option 2</RadioButton>
</StackPanel>
privatevoid Button_Click(object sender, RoutedEventArgs e)
{
Title = "Button pressed";
}

Рис. 12. Кнопка, флажок, две группы RadioButton.
Перейдём к разборупростых контейнеров, которые (согласно своему названию) предназначены для простого обрамления дочернего содержимого. Один из простых контейнеров–элемент Label. Обычно он используется для представления текста, но в WPFего содержимое может быть любым. ОсобенностьLabel – поддержка «горячих клавиш», при помощи которых фокус ввода передаётся заданному элементу управления. Вот типичный пример такого использования Label (обратите внимание на синтаксис задания свойства Target и на применение символа подчёркивания для указания «горячей клавиши»U):
<Label Target="{Binding ElementName=userName}"Content="_User:"/>
<TextBoxx:Name="userName"/>
Элемент управления ToolTip отображает своё содержимое в виде всплывающей подсказки. ToolTip может содержать любые элементы управления, но с ними нельзя взаимодействовать.ToolTip определяет события Openи Close, а также несколько свойств для настройки своего поведения и внешнего вида. Особенность ToolTip в том, что этот элемент нельзя поместить в дерево элементов. Вместо этого требуется задавать одноимённое свойство, определённое в классе FrameworkElement.
<CheckBox Content="Simple CheckBox">
<CheckBox.ToolTip>
<StackPanel>
<Label FontWeight="Bold" Background="Blue"
Foreground="White" Content="The CheckBox"/>
<TextBlock Padding="10" TextWrapping="WrapWithOverflow"
Width="200">
CheckBox is a familiar control. But in WPF, it’s not
muchmore than a ToggleButton styled differently!
</TextBlock>
</StackPanel>
</CheckBox.ToolTip>
</CheckBox>

Рис. 13. Сложная всплывающая подсказка.
Элемент управления Frameпредназначен для изолирования своего содержимого от остальной части пользовательского интерфейса. Этот элемент напоминает аналог из HTML. Более того, Frameдействительно может отображать HTMLили XAML-страницу, если использовать его свойство Source:
<Frame Source="http://www.pinvoke.net"/>
Последняя группа элементов управления содержимым – контейнеры с заголовком. Эти элементы наследуются от класса HeaderedContentControl, который добавляет объектное свойство Headerк классу ContentControl.
Простейший контейнер с заголовком представлен элементомGroupBox. Следующий пример демонстрирует использование GroupBox для группировки нескольких флажков:
<GroupBox Header="Grammar">
<StackPanel>
<CheckBox Content="Check grammar as you type" />
<CheckBox Content="Hide grammatical errors" />
<CheckBox Content="Check grammar with spelling" />
</StackPanel>
</GroupBox>
Подчеркнём, что в заголовок контейнера может быть помещено любое содержимое– например, кнопка:
<GroupBox.Header>
<Button>Grammar</Button>
</GroupBox.Header>
Элемент управления Expander напоминает GroupBox, но содержит в заголовке кнопку, которая позволяет спрятать и показать содержимое контейнера. Можно настроить направление «разворачивания» контейнера.
<Expander Header="Grammar">
<StackPanel>
<!-- содержимое StackPanel- три переключателя -->
</StackPanel>
</Expander>

Рис. 14. Expander, раскрытый вниз.
Списковые элементы управленияСписковые элементы управления(кратко –списки) могут хранить и представлять коллекцию элементов списка.Для большинства списков доступна гибкая настройка внешнего вида, а также функции фильтрации, группировки и сортировки данных, однако в данном параграфе рассматриваются только простейшие аспекты работы со списками.
Все списки наследуются от класса ItemsControl, который определяет несколько полезных свойств:
Items– коллекция ItemCollection, сохраняющая элементы списка (свойство доступно только для чтения).
ItemsSource–свойство позволяет связать список с коллекцией данных. Свойство доступно для установки во время выполнения приложения.
HasItems– булево свойство только для чтения. Показывает, имеются ли элементы в списке.
DisplayMemberPath– строковое свойство, определяющее отображаемое значение для элемента списка.DisplayMemberPath поддерживает синтаксис, который называется путь к свойству.Путь к свойству напоминает запись свойства агрегированного объекта, но без операций приведения типа. Например, если у кнопки btn задать в качестве содержимого строку, путь к свойству в виде "btn.Content[0]"будет означать первый символ строки.
В разметке XAMLдочерние объекты списка автоматически рассматриваются как элементы списка. Правила отображения отдельного элемента аналогичны правилам, применяемым при показе содержимого ContentControl.
Списковые элементы управления можно разделить на три подгруппы:
Селекторы.
Меню.
Списки без категории.
Селекторы допускают индексацию и выбор элементов списка. К селекторам относятся элементы управления ComboBox, ListBox, ListView, DataGridи TabControl.Все селекторы наследуются от абстрактного класса Selector, которыйпредоставляет следующие свойства и события:
SelectedIndex–целочисленный индекс (первого) выбранного элемента (или -1, если ничего не выбрано).
SelectedItem–(первый) выбранный элемент списка.
SelectedValue – значение выбранного элемента. Если не используется SelectedValuePath, значение совпадает с SelectedItem.
SelectedValuePath –путь к свойству, определяющий значение выбранного элемента.
SelectionChanged – событие, генерируемое при изменении SelectedItem.
В классе Selector определены два присоединяемых булевых свойства, используемых с элементами списка,–IsSelected и IsSelectionActive. Второе свойство является свойством только для чтения и позволяет узнать, установлен ли на выделенном элементе фокус. Кроме этого, класс Selectorимеет два присоединяемых события – Selectedи Unselected.
Элемент управления ListBox отображает список и допускает множественный выбор элементов. Эта возможность настраивается при помощи свойства SelectionMode, принимающего значения из одноимённого перечисления:
Single(по умолчанию) – можно выбрать только один элемент списка.
Multiple – можно выбрать несколько элементов. Щелчок по невыбранному элементу выбирает его, и наоборот.
Extended– можно выбрать несколько элементов, используя клавиши SHIFTи CTRL.
У ListBox имеется коллекция SelectedItems, содержащая выбранные элементы списка. Каждый элемент в ListBox представлен объектом класса ListBoxItem, в котором определено булево свойство IsSelectedи событияSelected и Unselected.В разметке XAMLListBoxItem можно не использовать. Анализатор XAMLвыполняет самостоятельное «оборачивание» дочерних элементов в объекты ListBoxItem(замечание справедливо и для других списков).
<ListBox SelectionMode="Multiple">
<TextBlock>First Item</TextBlock>
<TextBlock>Second Item</TextBlock>
<Button>Third Item</Button>
</ListBox>

Рис. 15.Элемент управленияListBox.
Класс ComboBox позволяет выбрать один элемент из списка, отображая текущий выбор и раскрывая список элементов по требованию. Отдельный элемент ComboBox представлен объектом ComboBoxItem (унаследованным от ListBoxItem).
<Canvas>
<ComboBox Name="cbx" SelectedIndex="0" Width="134">
<ComboBoxItem>First Item</ComboBoxItem>
<ComboBoxItem>Second Item</ComboBoxItem>
<ComboBoxItem>Third Item</ComboBoxItem>
</ComboBox>
<Button Padding="10,3" Canvas.Right="5" Canvas.Bottom="5"
Content="Enter" Click="Button_Click" />
</Canvas>
privatevoid Button_Click(object sender, RoutedEventArgs e)
{
MessageBox.Show(cbx.SelectionBoxItem.ToString(), "Selected");
}

Рис. 16.Элемент управленияComboBox.
Для описания состояния списка служит булево свойство IsDropDownOpen и связанные события DropDownOpened/DropDownClosed. Свойства IsEditableиIsReadOnly могут использоваться, чтобы подсвечивать и отображать элемент списка при вводе текстовой информации, связанной с элементом.Текстовая информация определяется в элементе списка при помощи свойства TextSearch.Text или в самом ComboBox при помощи свойства TextSearch.TextPath.
Элемент управления ListView унаследован от ListBox. Этот список позволяет настроить свой внешнийвид при помощи свойства Viewс типомViewBase. В WPFимеется один наследник абстрактного класса ViewBase –класс GridView. При помощи GridViewэлемент управления ListView отображает данные в виде таблицы с колонками. Каждая колонкатаблицы GridView – это объект класса GridViewColumn, помещённый в коллекцию GridView.Columns. У колонки настраивается ширина, заголовок, отображаемые в колонке значения. GridView поддерживает изменение размеров колонок и их перетаскивание во время работы приложения.
<ListView xmlns:sys="clr-namespace:System;assembly=mscorlib">
<ListView.View>
<GridView>
<GridViewColumn Header="Date" Width="70"/>
<GridViewColumn Header="Day of Week"
DisplayMemberBinding="{Binding DayOfWeek}"/>
<GridViewColumn Header="Year"
DisplayMemberBinding="{Binding Year}"/>
</GridView>
</ListView.View>
<sys:DateTime>1/1/2010</sys:DateTime>
<sys:DateTime>1/2/2010</sys:DateTime>
<sys:DateTime>1/3/2010</sys:DateTime>
</ListView>

Рис. 17.Элемент управленияListView.
Элемент управления TabControl– это простой наборстраницсзакладками:
<TabControl SelectedIndex="2">
<TabItem Header="Tab 1">Content for Tab 1</TabItem>
<TabItem Header="Tab 2">Content for Tab 2</TabItem>
<TabItem Header="Tab 3">Content for Tab 3</TabItem>
</TabControl>

Рис. 18.Элемент управленияTabControl.
По умолчанию закладки в TabControlрасположены вверху. Их положение можно изменить, используя свойство TabStripPlacement. Каждая страница в TabControl– это объект класса TabItem. Класс TabItem –контейнер с заголовком (подобно элементам GroupBox или Expander).
Перейдем к рассмотрению элементов управления из категории меню – Menuи ContextMenu.Класс Menuможет содержать коллекцию любых объектов, но ожидается, что будут использованы объекты MenuItemиSeparator.Separator – простой элемент, представляющий разделитель пунктов меню. MenuItem– это контейнер с заголовком (наследникHeaderedItemsControl). В случае MenuItem заголовок определяет отображаемый пункт меню, а свойство содержимого Items может содержать элементы подменю. MenuItemподдерживает «горячие клавиши». Этот класс также содержит свойства Iconи IsCheckableи определяет события Checked, Unchecked, SubmenuOpened, SubmenuClosed, Click.
<DockPanel LastChildFill="False">
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_New..."/>
<MenuItem Header="_Open..."/>
<Separator/>
<MenuItem Header="Sen_d To">
<MenuItem Header="Mail Recipient"/>
<MenuItem Header="My Documents"/>
</MenuItem>
</MenuItem>
<MenuItem Header="_Edit"></MenuItem>
<MenuItem Header="_View"></MenuItem>
</Menu>
</DockPanel>

Рис. 19.Пример меню.
Класс ContextMenu служит для представления контекстного меню некоторого элемента.ОбъектContextMenu нельзя поместить в дерево элементов, а следует задавать как значение одноимённого свойства, определённого в классе FrameworkElement.
<ListBox>
<ListBox.ContextMenu>
<ContextMenu><!-- пункты меню --></ContextMenu>
</ListBox.ContextMenu>
</ListBox>
ContextMenu – этоконтейнеробъектовMenuItem.В классе ContextMenu определено несколько свойств для настройки места появления контекстного меню относительно элемента, с которым оно связано.
Рассмотрим списковые элементы без категории. Элемент управления TreeView отображает иерархию данных в виде дерева с узлами, которые могут быть свернуты и раскрыты. Каждый элемент в TreeView является объектом TreeViewItem. Класс TreeViewItem напоминает MenuItem – это тоже контейнер с заголовком Header и коллекцией дочерних элементов Items. TreeViewItemсодержитбулевысвойстваIsExpandedиIsSelected, атакжесобытияExpanded, Collapsed, Selected, Unselected.
<TreeView>
<TreeViewItem Header="Desktop">
<TreeViewItem Header="Computer" />
<TreeViewItem Header="Recycle Bin" />
<TreeViewItem Header="Control Panel">
<TreeViewItem Header="Programs" />
<TreeViewItem Header="Security" />
<TreeViewItem Header="User Accounts" />
</TreeViewItem>
<TreeViewItem Header="Network" />
</TreeViewItem>
</TreeView>

Рис. 20.ПростойTreeView.
Элемент управления ToolBarпредставляет панель инструментов. Он просто группирует кнопки, разделители (Separator) и другие дочерние элементы на одной панели. Хотя ToolBar можно разместить в произвольном месте, обычно один или несколько таких объектов размещают внутри элемента ToolBarTray, что позволяет перетаскивать и переупорядочить панели инструментов.Элемент управления SatusBar группирует элементы подобно ToolBarили Menu.Обычно элементSatusBarразмещается в нижней части окна.
Прочие элементы управленияРассмотрим оставшиеся стандартные элементы управления, разбив их на следующие подгруппы:
Текстовые элементы.
Элементы для представления диапазона.
Элементы для работы с датами.
В WPFтекстовыми элементами являются TextBlock, TextBox, PasswordBox, RichTextBox. Элемент TextBlockпредназначен для отображения небольшой порции текста. Для текста доступны настройки шрифта, выравнивания, переноса (wrapping), а сам текст может включать теги XAMLXPS.
<TextBlock TextWrapping="Wrap" TextAlignment="Center"
FontFamily="Consolas"FontSize="16">
<Bold>TextBlock</Bold> is designed to be
<Italic>lightweight</Italic>,and is geared specifically at
integrating small portionsof flow content into a UI.
</TextBlock>

Рис. 21.ДемонстрацияTextBlock.
Элемент управления TextBox служит для отображения и ввода текста, заданного в строковом свойстве Text. TextBoxопределяет события TextChanged и SelectionChanged, а также содержит несколько свойств и методов для работы с частью введённого текста. Для управления переносом текста следует использовать свойство TextWrapping. Чтобы пользователь мог ввести несколько строк текста, нажимая ENTER, установите свойство AcceptsReturn в true.
Элемент RichTextBox – «продвинутая» версия TextBox. Многие свойства у этих элементов общие, так как они унаследованы от одного базового класса TextBoxBase.Содержимое RichTextBox сохраняется в свойстве Document типа FlowDocument, который создан для поддержки XPS.
Элемент управления PasswordBox предназначен для ввода паролей. Можно сказать, что это упрощённая версия TextBox–не поддерживаются вырезание и копирование текста, не генерируются событияTextChanged и SelectionChanged. В PasswordBoxвведённый пароль сохраняетсяв свойствеPassword, а при изменении текста генерируется событиеPasswordChanged. Символ, который отображается вместо букв пароля, настраивается при помощи свойства PasswordChar.
Элементы для представления диапазонаProgressBar и Sliderхранят и отображают в некой форме числовое значение, попадающее в заданный диапазон. Оба элемента унаследованы от класса RangeBase, имеющего свойства Value, Minimum,Maximum (все –типа double)и событие ValueChanged.
ProgressBar обычно используют для визуализации процесса выполнения длительной операции. Класс ProgressBar добавляет к RangeBase два свойства:
IsIndeterminate – если установить это булево свойство в true, ProgressBarбудет показыватьнепрерывную бегущую полоску.
Orientation – размещениеProgressBar (HorizontalилиVertical).
Элемент управления Slider – это слайдер (ползунок) с возможностью ручной установки значения из диапазона. У слайдера имеется свойство Orientation и несколько свойств, управляющих метками (например, TickPlacement). Кроме этого, слайдер позволяет задать выделенный диапазон при помощи свойств IsSelectionRangeEnabled, SelectionStartи SelectionEnd.
<StackPanel>
<ProgressBarValue="80"Height="20" Margin="5"/>
<Slider Maximum="30" Value="25"
TickPlacement="BottomRight" TickFrequency="2"
IsSelectionRangeEnabled="True"
SelectionStart="10" SelectionEnd="20"/>
</StackPanel>

Рис. 22.Элементы управленияProgressBar и Slider.
Элементами для работы с датами являются Calendarи DatePicker.Calendar отображает небольшой календарь с возможностью клавиатурной навигации и выбора. Этот класс имеет несколько полезных свойств:
DisplayMode – режим отображения календаря (Year, Month, Decade);
SelectionMode – режимвыборадат (SingleDate, SingleRange, MultipleRange, None);
BlackoutDates– коллекция дат, которые не могут быть выбраны;
DisplayDate– текущая отображаемая дата;
DisplayDateStart и DisplayDateEnd задают возможный диапазон дат;
IsTodayHighlighted– подсветка текущей даты;
SelectedDate и SelectedDates– выбранная дата или коллекция дата.
<Calendar DisplayMode="Month" DisplayDate="1/1/2010"
DisplayDateEnd="12/31/2012">
<Calendar.BlackoutDates>
<CalendarDateRange Start="1/7/2010" End="1/14/2010" />
<CalendarDateRange Start="1/30/2010" End="2/9/2010" />
</Calendar.BlackoutDates>
</Calendar>
Элемент DatePickerпозволяет задать дату, набирая её с клавиатуры или применяя выпадающий элемент Calendar. Большинство свойств DatePicker служит для настройки этого внутреннего календаря. Свойство Textсодержит введённый в DatePicker текст. Если этот текст нельзя конвертировать в дату, генерируется событие DateValidationError (что по умолчанию приводит к исключительной ситуации).
<DatePicker SelectedDateFormat="Long" SelectedDate="02/10/10"
DisplayDateStart="1/01/10" DisplayDateEnd="12/31/10"
FirstDayOfWeek="Monday" />

Рис. 23.Элементы управленияCalendarиDatePicker.
8. ФигурыФигуры – это элементы WPF, представляющие простые линии, эллипсы, прямоугольники и многоугольники. Все фигуры наследуются от абстрактного класса System.Windows.Shapes.Shape. Этот класс определяет небольшой набор важных свойств, перечисленных в табл. 6.
Таблица 6
Свойства класса Shape
Имя Описание
Fill Кисть (объект Brush), рисующая поверхность фигуры
Stroke Кисть, рисующаяграницу фигуры
StrokeThickness Шириналинии в единицахWPF. При рисовании линии её ширина разбивается поровну на каждую сторону
StrokeStartLineCap,StrokeEndLineCap Контур начала и конца линии. Эти свойства имеют эффект для фигур Line, Polyline и (иногда) Path
StrokeDashArray, StrokeDashOffset, StrokeDashCap Свойства позволяют создавать пунктир. Можно управлять размером и частотой пунктира, а также контуром, ограничивающим начало и конец каждого фрагмента пунктира
StrokeLineJoin, StrokeMiterLimit Контур углов фигуры (свойства затрагивают вершины, где стыкуются разные линии)
Stretch Способ заполнения фигурой доступного пространства
DefiningGeometry Представляет геометрию фигуры (объект Geometry). Объект Geometry описывает координаты и размер фигуры без учёта таких вещей из UIElement, как поддержка событий клавиатуры и мыши
GeometryTransform Позволяет применить к фигуре трансформацию (объект Transform)
RenderedGeometry Геометрия, описывающая подготовленную к отображению фигуру
Перейдём к рассмотрению конкретных фигур. Rectangle и Ellipse представляют прямоугольник и эллипс.Размеры этих фигур определяются свойствами Height и Width, а свойстваFill и Stroke делают фигуру видимой. Класс Rectangleимеет свойства RadiusX и RadiusYдля создания закруглённых углов (RadiusX и RadiusYможно воспринимать как полуоси эллипса, используемого для размещения в углах прямоугольника).
<Rectangle Canvas.Top="10" Canvas.Left="10" Height="80" Width="120"
Fill="Azure" Stroke="Coral" RadiusX="30" RadiusY="20" />
<Ellipse Canvas.Bottom="10" Canvas.Right="10" Height="60" Width="80"
Fill="Aqua" Stroke="Olive" StrokeThickness="6" />
<Rectangle Canvas.Top="20" Canvas.Left="150" Height="80" Width="220"
Stroke="Green" StrokeThickness="14"
StrokeLineJoin="Bevel" StrokeMiterLimit="10" />

Рис. 24.Прямоугольники и эллипс.
Фигура Line– это отрезок, соединяющий две точки. Начальная и конечная точки устанавливаются свойствами X1 и Y1 (для первой точки) и X2 и Y2 (для второй точки). Координаты отрезка отсчитываются относительно верхнего левого угла контейнера, в котором он содержится. Если отрезок рисуется в Canvas, установка свойств позиции задаст смещение координатной системы рисования. Чтобы сделать отрезок видимым, нужно задать свойство Stroke:
<LineStroke="Blue"X1="0"Y1="0"X2="10"Y2="100" />
Класс Polyline позволяет рисовать последовательность связанных отрезков. Для этого необходимо задать набор точек в свойстве-коллекции Points. В XAML для Pointsможно использовать лаконичный строчный синтаксис, просто перечислив точки координат через пробел или запятую. Класс Polygonнапоминает Polyline. При рисовании этот элемент добавляет финальный отрезок, соединяющий начальную и конечную точки коллекции Points.
<Polygon Stroke="Blue" StrokeThickness="5"
Points="90,80 110,120 130,180 150,110 170,190 190,100 210,240" />
Рисуя фигуры Line и Polyline, можно указать форму начальной и конечной точки линии, используя свойства StartLineCap и EndLineCap. Изначально эти свойства установлены в Flat, что означает немедленное завершение линии в её конечных координатах. К другим возможным вариантам относятся Round (линия мягко закругляется). Triangle (сводит обе стороны линии в точку) и Square (завершает линию чёткой гранью). Все значения, кроме Flat, добавляют линии длину–половину толщины линиидополнительно.
<Line X1="30" Y1="30" X2="200" Y2="30" Stroke="Brown"StrokeThickness="20"
StrokeStartLineCap="Triangle"StrokeEndLineCap="Round" />
ВсефигурыпозволяютизменятьвидиформусвоихугловчерезсвойствоStrokeLineJoin. Значение по умолчанию –Miter– использует чёткие грани, Bevel обрезает угол в точке сопряжения, аRound– плавно закругляет его.
<Polyline Stroke="Blue" StrokeThickness="5" StrokeLineJoin="Round"
Points="60,140 80,90 100,150 120,90 140,140"/>
Линии (включая границы фигур) могут быть изображены пунктиром. При этом в свойстве-массивеStrokeDashArrayуказываютсядлины сплошных и прерванных сегментов (пробелов). Эти значения интерпретируются относительно толщины линии.
<RectangleCanvas.Top="20"Canvas.Left="230"Height="120"Width="40"
StrokeThickness="4"StrokeDashArray="2 1.5"Stroke="Navy"/>

Рис. 25.Концы линий, форма углов и штриховка.
Фигура Pathпредназначена для отображения произвольной геометрии. Этот класс имеет свойство Dataс типом Geometry. При задании Dataиспользуется один из наследников класса Geometry:
RectangleGeometry–прямоугольник;геометрический эквивалент фигуры Rectangle.
EllipseGeometry– эллипс;геометрический эквивалент Ellipse.
LineGeometry–прямая линия; геометрический эквивалент Line.
GeometryGroup –связывает набор объектов Geometryв единый путь.
CombinedGeometry– объединяет две геометрии в единую фигуру, при этом можно указать способ комбинирования составляющих.
PathGeometry–сложная фигура, состоящая из дуг, кривых и линий.
StreamGeometry– неизменяемый облегченный эквивалент PathGeometry.
Классы LineGeometry, RectangleGeometry и EllipseGeometry отображаются непосредственно на фигуры Line,Rectangle и Ellipse.Ниже для сравнения приведены эквивалентные фрагменты разметок:
<RectangleFill="Yellow"Stroke="Blue"Width="100"Height="50" />
<!--эквивалентно-->
<Path Fill="Yellow" Stroke="Blue">
<Path.Data>
<RectangleGeometry Rect="0,0 100,50" />
</Path.Data>
</Path>
<Line Stroke="Blue" X1="0" Y1="0" X2="10" Y2="100" />
<!--эквивалентно-->
<Path Fill="Yellow" Stroke="Blue">
<Path.Data>
<LineGeometry StartPoint="0,0" EndPoint=" 10,100" />
</Path.Data>
</Path>
<Ellipse Fill="Yellow" Stroke="Blue" Width="100" Height="50" />
<!--эквивалентно-->
<Path Fill="Yellow" Stroke="Blue">
<Path.Data>
<EllipseGeometry RadiusX="50" RadiusY="25" Center="50,25" />
</Path.Data>
</Path>
Класс GeometryGroup предоставляет возможность для объединения нескольких геометрий в единое целое. В следующем примере при помощиGeometryGroup создаётся фигура в виде квадрата с отверстием:
<Canvas>
<TextBlock Canvas.Top="50" Canvas.Left="20" FontSize="25"
FontWeight="Bold" Text="Hello There" />
<Path Fill="Yellow" Stroke="Blue" Margin="5"
Canvas.Top="10" Canvas.Left="10">
<Path.Data>
<GeometryGroup>
<RectangleGeometry Rect="0,0 100,100" />
<EllipseGeometry Center="50,50"
RadiusX="35" RadiusY="25" />
</GeometryGroup>
</Path.Data>
</Path>
</Canvas>

Рис. 26.Демонстрация GeometryGroup.
GeometryGroup отлично работает при создании фигур посредством рисования одной и последующего «вычитания» другой изнутри первой.Для сложных случаев совмещения фигур предназначен класс CombinedGeometry. Он принимает две геометрии в свойствах Geometry1 и Geometry2и комбинирует их способом, указанным в свойстве GeometryCombineMode(имеющем тип одноимённого перечисления):
Union– объединение геометрий;
Intersect– пересечение геометрий;
Exclude– исключение из первой геометрии второй геометрии;
Xor– объединение геометрий, из которого исключено их пересечение.
Ниже приведён фрагмент разметки, демонстрирующий объединение геометрий, и рисунок, показывающий все четыре способа комбинирования.
<Path Fill="Gold" Stroke="Blue" Margin="5">
<Path.Data>
<CombinedGeometry GeometryCombineMode="Union">
<CombinedGeometry.Geometry1>
<RectangleGeometry Rect="0,0 100, 100" />
</CombinedGeometry.Geometry1>
<CombinedGeometry.Geometry2>
<EllipseGeometry Center="85,50"
RadiusX="65" RadiusY="35" />
</CombinedGeometry.Geometry2>
</CombinedGeometry>
</Path.Data>
</Path>

Рис. 27.Четыре способа комбинирования геометрий.
Класс PathGeometryописывает геометрию при помощи объектов PathFigure, хранящихся в коллекции PathGeometry.Figures.PathFigure– это непрерывный набор связанных отрезков и кривых линий. У класса PathFigureчетыре ключевых свойства:
StartPoint– точка (объект Point) начала первой линии фигуры.
Segments– коллекция объектов PathSegment, используемых для рисования фигуры.
IsClosed–если установлено в true, то добавляется отрезок, соединяющий начальную и конечную точкиPathFigure (если они не совпадают).
IsFilled–если установлено в true, то область внутри фигуры заполняется кистью Path.Fill.
Есть несколько типов сегментов, унаследованных от класса PathSegment:
LineSegment–отрезок;
ArcSegment–эллиптическая дуга;
BezierSegment–кривая Безье;
QuadraticBezierSegment–упрощеннаяквадратичнаякривая Безье, имеющая одну контрольную точку вместо двух;
PolyLineSegment–серияотрезков;
PolyBezierSegment–серия кривых Безье.
PolyQuadraticBezierSegment–серия упрощенных кривых Безье.
Использование объектов PathGeometry, PathFigure, PathSegment при описании сложной геометрии может быть чрезвычайно многословно. В WPF имеется краткий альтернативный синтаксис, которые позволяет представить детализированные фигуры в меньшем объёме кода разметки. Этот синтаксис называют геометрическим мини-языком.
Выражениягеометрического мини-языка – это строки, содержащие серии команд. Каждая команда– это одна буква, за которой необязательно следуют несколько параметров (например, координаты). Команда отделяется пробелом. В табл. 7 перечислены все команды геометрического мини-языка.
Таблица 7
Команды геометрического мини-языка
Команда,
параметры Описание
F значение Устанавливает свойство Geometry.FillRule – правило «заливки» контура (0 для EvenOdd или 1 для NonZero). Эта команда должна стоять в начале строки (если она вообще будет применяться)
M x,y Устанавливает начальную точку PathFigure. Любое выражение геометрического языка начинается либо с команды M, либо с пары команд Fи M.Команда Mвнутри выражения служит для перемещения начала координатной системы
Lx,y Создаёт LineSegment до указанной точки
H x Создаёт горизонтальный отрезок до указанного значенияx
V y Создаёт вертикальный отрезок до указанного значенияy
A radiusX,radiusY
degreesisLargeArc
isClockwisex,y Создаёт ArcSegment до заданной точки. Указываются радиусы эллипса, описывающего дугу, угол дуги в градусах и булевские флагинастройки дуги
Cx1,y1x2,y2x,y СоздаётBezierSegment до указанной точки, используя контрольные точки x1,y1 и x2,y2
Q x1,y1x,y СоздаётQuadraticBezierSegment до указанной точки, с одной контрольной точкой x1,y1
Sx1,y1x,y СоздаётгладкийBezierSegment до указанной точки, с одной контрольной точкой x1,y1
Z Завершает текущую PathFigure и устанавливает IsClosed в true
Следующая разметка определяет фигуру в виде треугольника:
<Path Stroke="Blue">
<Path.Data>
<PathGeometry>
<PathFigure IsClosed="True" StartPoint="10,100">
<LineSegment Point="100,100" />
<LineSegment Point="100,50" />
</PathFigure>
</PathGeometry>
</Path.Data>
</Path>
Чтобы нарисовать тот же треугольник при помощи геометрического мини-языка, нужна такая разметка:
<Path Stroke="Blue" Data="M 10,100 L 100,100 L 100,50 Z" />
9. Цвет,кисти, прозрачностьПредставление цвета в WPFWPFпозволяет работать с двумя цветовыми моделями:
RGB – распространённая цветовая модель, в которой каждый компонент цвета (красный, зелёный, синий) представлен одним байтом. Дополнительно может использоваться альфа-канал (кодируемый одним байтом), чтобы задать прозрачность цвета (0 – полностью прозрачный, 255 – полностью непрозрачный).
scRGB–в этой модели каждый компонент цвета (альфа-канал, красный, зелёный, синий) представлен с помощью 16- или 32-битных чисел с плавающей точкой в диапазоне от 0 до 1.
Структура System.Windows.Media.Color хранит информацию о цвете. Свойства структуры позволяют прочитать или задать отдельную цветовую компоненту в любой из двух цветовых моделей, а статические методы – создать цвет на основе компонент или произвести простейшие операции с цветом:
Color c1 = Color.FromRgb(10, 20, 30);
Color c2 = Color.FromArgb(250, 10, 20, 32);
Color c3 = Color.FromScRgb(0.4f, 0.5f, 0.7f, 0.2f);
Color c4 = (c1 + c2)*2;
byte red = c4.R;
bool flag = Color.AreClose(c1, c2);
Класс System.Windows.Media.Colors содержит набор именованных цветов в виде статических свойств для чтения. Класс System.Windows.SystemColors предоставляет аналогичный набор для стандартных цветов системы:
Color c1 = Colors.IndianRed;
Color c2 = SystemColors.ControlColor;
При установке цвета в разметке XAMLможно использовать строки следующего формата:
ИмяЦвета – одно из имён свойств в классе Colors;
#rgbили #rrgggbb– аналог вызова Color.FromRgb(0xrr, 0xgg, 0xbb);
#argb или #aarrgggbb – аналог Color.FromArgb(0xaa, 0xrr, 0xgg, 0xbb);
sc# a r g b – аналогColor.FromScRgb(a, r, g, b) (числа∈0,1).
<Button Background="Red" />
<Button Background="#64A" />
<Button Background="#FF00674A" />
<ButtonBackground="sc# 0.1 0.1 0.5 0.3" />
Ради справедливости отметим, что в приведённом выше примере на самом деле используется не цвет, а соответствующая кистьSolidColorBrush:
<!-- эквивалент<Button Background="Red" /> -->
<Button>
<Button.Background>
<SolidColorBrush Color="Red" />
</Button.Background>
</Button>
Лучшие кистиКисть – это объект, используемый для заполнения фона, переднего плана, границы, линии. Любая кисть является потомком абстрактного класса System.Windows.Media.Brush.Имеется несколько стандартных классов кистей:
SolidColorBrush – закрашивает область сплошным цветом;
LinearGradientBrush– рисует область, используя линейное градиентное заполнение, представляющее собой плавный переход от одного цвета к другому (и, необязательно, к следующему, потом ещё к одному и т.д.);
RadialGradientBrush– закрашивает область, используя радиальное градиентное заполнение.
ImageBrush– рисует область, используя изображение, которое может растягиваться, масштабироваться или многократно повторяться.
DrawingBrush– рисует область, используя объект Drawing (этот объект может включать заданные фигуры и битовые карты).
VisualBrush– заполняет область, используя объект Visual.
SolidColorBrush –самая простая из кистей. Во всех предыдущих примерах разметки использовалась именно она. Свойство SolidColorBrush.Colorопределяет цвет сплошной заливки. Анализатор XAMLспособен автоматически создать SolidColorBrush на основе строки с представлением цвета. Также отметим, что в классе SystemColors задан набор статических кистей SolidColorBrush, соответствующих системным цветам.
<Button Background="{x:Static SystemColors.ControlBrush}" />
Кисть LinearGradientBrush создаёт заполнение, которое представляет собой переход от одного цвета к другому.Ниже приведён простейший пример градиента, который закрашивает прямоугольник по диагонали от синего (в левом верхнем углу) к белому (в правом нижнем углу) цвету:
<Rectangle Width="150" Height="100">
<Rectangle.Fill>
<LinearGradientBrush>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
ГрадиентвLinearGradientBrushстроитсяпоследующимправилам. Вокруг заполняемой области очерчивается виртуальный прямоугольник, у которого левый верхний угол имеет координаты (0, 0), а правый нижний – (1, 1). В этих координатах при помощи свойств StartPoint и EndPointзадаётсявектор градиента(по умолчанию StartPoint=0,0 и EndPoint=1,1). Коллекция GradientStopsсодержит опорные точки градиента– объекты GradientStop с указанием на цвет и смещение относительно вектора градиента (0 – начало вектора, 1 – конец вектора):
<LinearGradientBrush StartPoint="0.5,0" EndPoint="1,1">
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Azure" Offset="0.4" />
<GradientStop Color="White" Offset="1" />
</LinearGradientBrush>
Свойство LinearGradientBrush.SpreadMethod управляет тем, как будет распространяться градиент за пределы вектора градиента. По умолчанию оно имеет значение Pad–области вне градиента заполняются соответствующим сплошным цветом. Допустимы также значения Reflect (для обращения градиента) и Repeat (для дублирования той же цветовой последовательности).

Рис. 28.Демонстрация различных значений SpreadMethod.
Кисть RadialGradientBrush работает подобно LinearGradientBrush, но использует градиент, который исходит из начальной точки в радиальном направлении. Координаты начальной точки задаёт свойство GradientOrigin, которое по умолчанию равно (0.5, 0.5). Градиент распространяется до границы круга градиента, который описывается тремя свойствами: Center,RadiusX и RadiusY.В классе RadialGradientBrush имеются свойства MappingMode и SpreadMethod.
Ниже показан пример использования RadialGradientBrush для создания иллюзии глубины фигуры:
<Ellipse Margin="5" Height="200" Width="200"
Stroke="Black" StrokeThickness="1">
<Ellipse.Fill>
<RadialGradientBrush RadiusX="1" RadiusY="1"
GradientOrigin="0.7,0.3">
<GradientStop Color="White" Offset="0" />
<GradientStop Color="Blue" Offset="1" />
</RadialGradientBrush>
</Ellipse.Fill>
</Ellipse>

Рис. 29.Использование RadialGradientBrush.
КистьImageBrushпозволяетзаполнитьобластьизображением, которое идентифицируется свойством ImageSource. Изображение может быть либо битовым (формата BMP, PNG, GIF, JPEG, ICON), либо векторным. В первом случае картинка идентифицируется URI, который обычно указывает на двоичный ресурс сборки. Во втором случае используется объект DrawingImage.
Для настройки ImageBrush можно использовать следующие свойства:
Stretch – правило растяжения картинки, если она не совпадает с заполняемой областью: None– не растягивать, Fill –заполнить, исказив пропорции,Uniform–сохранить пропорции, заполнить сколько получиться, UniformToFill –сохранить пропорции, заполнить всё, обрезав лишнее;
AlignmentX и AlignmentY–правила выравнивания картинки, если она меньше заполняемой области;
Viewbox– фрагмент, который вырезается из картинки для использования в кисти;
ViewboxUnits– способ определения координат Viewbox (Absolute– абсолютные, RelativeToBoundBox – относительные при помощи виртуального прямоугольника);
Viewport –фрагмент закрашиваемой области, на который отображается картинка кисти.Свойство применяется, когда картинкой нужно «замостить»большую область.
ViewportUnits– способ определения координат Viewport;
TileMode– способ заполнения картинкой кисти большой области:None– без заполнения, Tile– простое заполнение, FlipX, FlipY, FlipXY– заполнения, с отражением по указанной оси.
В следующем примере кистьImageBrush используется для заполнения фонаGrid. Из изображения информационной иконки вырезается четверть, которая повторяется на фоне двадцать раз (четыре строки по пять фрагментов):
<Grid>
<Grid.Background>
<ImageBrush ImageSource="info.png" Viewbox="0,0 0.5,0.5"
Viewport="0,0 0.2,0.25" TileMode="Tile" />
</Grid.Background>
</Grid>

Рис. 30.Кисть ImageBrush.
Кисть DrawingBrushиспользует для заполнения области объект Drawing, помещённый в одноимённое свойство кисти. Класс Drawing представляет двухмерные рисунки. Подробно работа с Drawing будет разобрана в одном из следующих параграфов. Отметим, что кисти ImageBrush, DrawingBrush и рассматриваемая далее VisualBrush унаследованы от общего предка – класса TileBrush.Этот класс определяет свойства, связанные с заполнением области картинкой (Viewbox, Viewport, TileMode).
VisualBrush– разновидность кисти, позволяющая брать визуальное содержимое элемента и использовать его для заполнения любой поверхности. Например, с помощью VisualBrush можно скопировать внешний вид кнопки. Однако такая «кнопка» не будет реагировать на нажатия или получать фокус– это просто копия внешнего вида элемента.
Интересно, что VisualBrushне просто копирует визуальное представление, а отслеживает изменения в копируемом элементе. В следующем примере поверхность окна меняется, когда пользователь редактирует текст в поле ввода:
<Canvas>
<Canvas.Background>
<VisualBrush Visual="{Binding ElementName=txt}"
TileMode="FlipXY" Viewport="0,0 0.4,0.5" />
</Canvas.Background>
<TextBox Name="txt" FontSize="20" Width="180"
Canvas.Left="20" Canvas.Top="20" />
</Canvas>

Рис. 31.Кисть VisualBrush.
ПрозрачностьВ WPF поддерживается истинная прозрачность. Каждый элемент и кисть содержит свойство Opacity, определяющее степень прозрачности и принимающее вещественные значения из диапазона 0, 1, Например, Opacity=0.9создаёт эффект 90% видимости и 10% прозрачности. Также можно использовать цвет (и соответствующую сплошную кисть) с альфа-каналом, меньшим максимума.
Все элементы содержат свойство OpacityMask, которое принимает любую кисть. Альфа-канал кисти определяет степень прозрачности (другие цветовые компоненты значения не имеют). Применение OpacityMask с кистями, содержащими градиентный переход от сплошного к прозрачному цвету создаёт эффект постепенного «исчезновения» поверхности. Если поместить в OpacityMaskкисть DrawingBrush, можно создать прозрачную область заданной формы.
В следующем примере OpacityMask используется в сочетании с VisualBrush для создания популярного эффекта отражения. По мере набора текстаVisualBrush рисует ниже отражение этого текста. VisualBrushзакрашивает прямоугольник, использующий свойство OpacityMask для постепенного затухания отражения.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="30" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBox Name="txt" FontSize="20" FontWeight="Bold" />
<Rectangle Grid.Row="1" RenderTransformOrigin="1,0.5">
<Rectangle.Fill>
<VisualBrush Visual="{Binding ElementName=txt}" />
</Rectangle.Fill>
<Rectangle.OpacityMask>
<LinearGradientBrush StartPoint="0,0" EndPoint="0,1">
<GradientStop Offset="0.3" Color="Transparent" />
<GradientStop Offset="1" Color="#44000000" />
</LinearGradientBrush>
</Rectangle.OpacityMask>
<Rectangle.RenderTransform>
<ScaleTransform ScaleY="-1" />
</Rectangle.RenderTransform>
</Rectangle>
</Grid>

Рис. 32.Эффект отражения.
10. трансформации и эффектыТрансформация – это заданное изменение координатной системы, в которой отображается элемент.Описание таких трансформаций на плоскости, как масштабирование, отражение и поворот, можно выполнить в терминах числовых матриц размером2×2. Чтобы представить в матричной форме операцию сдвига координатной системы, используют однородные координаты.
Однородными координатами точки(x,y)является тройка вида (x,y,1). Если дана тройкачисел(a,b,c), соответствующая точка на плоскости находится после применения нормировки –деления на c: (a/c,b/c,1). Тройки вида (a,b,0) описывают в однородных координатах бесконечно удалённую точку.
В терминах однородных координат основные трансформации можно выразить следующим образом:
Масштабирование (включая отражения): (x,y,1)sx000sy0001.
Поворот на угол φ: (x,y,1)cosφsinφ0-sinφcosφ0001.
Сдвиг: (x,y,1)100010dxdy1.
Комбинация трансформаций выполняется как умножение матриц.
В WPFтрансформации представлены классами, унаследованными от абстрактного класса System.Windows.Media.Transform. Набор предопределённых трансформаций перечислен в табл. 8.
Таблица 8
Классы трансформаций
Имя класса Описание Важныесвойства
TranslateTransform Смещает координатную систему на указанную величину X, Y
RotateTransform Поворачивает координатную систему вокруг заданной точки Angle,CenterX,CenterY
ScaleTransform Масштабирует координатную систему. Можно применять разную степень масштабирования по измерениям X и Y ScaleX,ScaleY,CenterX,CenterY
SkewTransform Деформирует координатную систему, наклоняя еёоси на заданное число градусов AngleX,AngleY,CenterX,CenterY
MatrixTransform Выполняет трансформацию, используя указанную матрицу вида a11a120a21a220dxdy1Matrix
TransformGroup Комбинирует несколько трансформаций. Порядок трансформаций в группе имеет значение Children
Укажем некоторые возможности задания трансформаций в WPF:
КлассUIElementопределяетсвойстваRenderTransformиRenderTransformOrigin. RenderTransform– это трансформация, выполняемая после процесса компоновки непосредственно перед отображением элемента. RenderTransformOrigin задаёт стартовую (неподвижную) точку трансформации. По умолчанию это точка имеет координаты (0,0) (координаты точки относительные, в терминах виртуального ограничивающего прямоугольника).
Класс FrameworkElementсодержит свойство LayoutTransform для трансформации, применяемой до процесса компоновки.
Класс Brushимеет свойства RelativeTransform и Transform,позволяющие выполнить трансформацию кисти до и после её применения.
Следующий пример демонстрирует использованиетрансформаций.
<StackPanel Orientation="Horizontal">
<Button Height="30" Width="60" Content="Rotate">
<Button.LayoutTransform>
<RotateTransform Angle="-45" CenterX="30" CenterY="15"/>
</Button.LayoutTransform>
</Button>
<Button Height="30" Width="60" Content="Skew">
<Button.RenderTransform>
<SkewTransform AngleX="30" AngleY="0" />
</Button.RenderTransform>
</Button>
<Button Height="30" Width="60" Content="Matrix">
<Button.LayoutTransform>
<MatrixTransform Matrix="1,0.5,1,-1,20,10" />
</Button.LayoutTransform>
</Button>
</StackPanel>

Рис. 33. Примеры трансформаций.
Кроме трансформаций, WPFподдерживает применение к элементамэффектов, таких как размытие, отбрасывание тени, сияние. Эффекты в WPFделятся на две категории – битовые эффекты и эффекты пиксельных шейдеров.
Битовые эффекты представлены классами, унаследованными от класса System.Windows.Media.Effects.BitmapEffect. Все битовые эффекты обрабатываются программно, без использования ресурсов видеокарты. Набор предопределённыхбитовых эффектов перечислен в табл. 9.
Таблица 9
Классы битовых эффектов
Имя класса Описание Важныесвойства
BlurBitmapEffect Размывает содержимое элемента Radius, KernelType
BevelBitmapEffect Добавляет выпуклую рамку вокруг содержимого BevelWidth, Relief, EdgeProfile, LightAngle, Smoothness
EmbossBitmapEffect Создает эффект «тиснения», выделяя границы и линии LightAngle, Relief
OuterGlowBitmapEffect Добавляет цветное сияние вокруг содержимого GlowColor, GlowSize, Noise, Opacity
DropShadowBitmapEffect Добавляет прямоугольную тень за элементом Color, Direction, Noise, Opacity, ShadowDepth, Softness
BitmapEffectGroup Применяет комбинацию битовых эффектов. Порядок эффектов имеет значение, поскольку каждый применяется поверх существующих Children
Эффекты пиксельных шейдеров используют одноимённую технологию графических процессоров. Они представлены следующими классами:
BlurEffect – эффект размытия, подобный BlurBitmapEffect;
DropShadowEffect – эффект тени, подобный DropShadowBitmapEffect;
ShaderEffect – любой эффект пиксельных шейдеров, описанный при помощи объекта PixelShader.
Для применения эффектов пиксельных шейдеров класс Visual определяет свойство VisualEffect, а класс UIElement – свойство Effect. Следующая разметка демонстрирует эффектыBlurEffect и DropShadowEffect.
<StackPanel Orientation="Horizontal">
<Button Height="40" Width="80" Content="Blur" Margin="10">
<Button.Effect>
<BlurEffect Radius="3" />
</Button.Effect>
</Button>
<Button Height="40" Width="80" Content="Shadow" Margin="10">
<Button.Effect>
<DropShadowEffect ShadowDepth="5" Direction="300"
Color="Blue" />
</Button.Effect>
</Button>
</StackPanel>

Рис. 34. Применение эффектов.
11. Классы drawing иvisualАбстрактный класс System.Windows.Media.Drawing представляет двухмерные рисунки– другими словами, содержит всю информацию, которая нужна для отображения части векторного или битового изображения.
Набор стандартных наследников Drawing перечислен в табл. 10.
Таблица 10
Классы Drawing
Имя класса Описание Важныесвойства
GeometryDrawing Обёртывает геометрию заполняющей кистью и пером, очерчивающим контур Geometry, Brush, Pen
ImageDrawing Обёртывает графический образ (обычно битовую карту из файла) прямоугольником, определяющим его границы ImageSource, Rect
VideoDrawing Комбинирует MediaPlayer, используемый для воспроизведения видео, с прямоугольником, задающим его границы Player, Rect
GlyphRunDrawing Обёртывает низкоуровневый текстовый объект, известный как GlyphRun, с кистью, рисующей его GlyphRun, ForegroundBrush
DrawingGroup Комбинирует коллекцию объектов Drawing любого типа. DrawingGroup позволяет создавать составные рисунки и применять эффекты ко всей коллекции сразу BitmapEffect, BitmapEffectInput, Children, ClipGeometry, GuidelineSet, Opacity, OpacityMask, Transform
Классы-наследники Drawing не являются элементами, а, значит, не могут быть помещены в пользовательский интерфейс. Для отображения рисунка нужно использовать один из трех классов, перечисленных ниже:
DrawingImage – этот класс унаследован от ImageSource и позволяет разместить рисунок внутри элемента Image;
DrawingBrush – унаследован от Brush и позволяет обернуть рисунок кистью, которую можно использовать для заполнения любой поверхности;
DrawingVisual– унаследован от Visual и позволяет поместить рисунок в низкоуровневый визуальный объект.
Следующий фрагмент разметки демонстрирует создание объекта GeometryDrawing и размещение его в элементе Image, который, в свою очередь, помещён на кнопку.
<Button Height="100" Width="210">
<Image>
<Image.Source>
<DrawingImage>
<DrawingImage.Drawing>
<GeometryDrawing>
<GeometryDrawing.Geometry>
<GeometryGroup>
<EllipseGeometry Center="50,50" RadiusX="45" RadiusY="20"/>
<EllipseGeometry Center="50,50" RadiusX="20" RadiusY="20" />
</GeometryGroup>
</GeometryDrawing.Geometry>
<GeometryDrawing.Brush>
<LinearGradientBrush>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="#CCF" Offset="1" />
</LinearGradientBrush>
</GeometryDrawing.Brush>
<GeometryDrawing.Pen>
<Pen Thickness="4" Brush="Black" />
</GeometryDrawing.Pen>
</GeometryDrawing>
</DrawingImage.Drawing>
</DrawingImage>
</Image.Source>
</Image>
</Button>

Рис. 35. Кнопка с изображением, содержащим GeometryDrawing.
В приложениях интенсивной графики, работающих с большим числом графических примитивов, использование фигур, геометрий и рисунков не оправдано с точки зрения производительности. В таких ситуациях следует применять низкоуровневую модельвизуального слоя (visual layer). Базовая идея в том, чтобы определить графический элемент как объект Visual, который чрезвычайно облегчён и требует меньших накладных расходов, чем Geometry или Path.
Класс Visualявляется абстрактным, поэтому используются его классы-наследники. К ним относится UIElement (корень элементной модели WPF), Viewport3DVisual (отображениетрёхмерныхмоделей) и ContainerVisual (базовый контейнер, содержащийдругие объекты Visual). Наиболее полезный класс – это DrawingVisual(наследникContainerVisual), добавляющий поддержку, необходимую для рисования графического содержимого.
Чтобы нарисовать содержимое в DrawingVisual, вызывается его экземплярный метод RenderOpen(). Этот метод возвращает объектDrawingContext.Класс DrawingContext состоит из методов (перечисленных в табл. 11), которые добавляют графические детали к создаваемому визуальному объекту. Завершив рисование, следует вызвать у контекста экземплярный метод Close().Ниже показан пример создания элемента, содержащего чёрный треугольник:
DrawingVisualvisual = newDrawingVisual();
using (DrawingContext dc = visual.RenderOpen())
{
Pen drawingPen = newPen(Brushes.Black, 3);
dc.DrawLine(drawingPen, newPoint(0, 50), newPoint(50, 0));
dc.DrawLine(drawingPen, newPoint(50, 0), newPoint(100, 50));
dc.DrawLine(drawingPen, newPoint(0, 50), newPoint(100, 50));
}
Фактически, вызов методовDrawingContext не выполняет рисования, а определяет внешний вид визуального элемента. После вызоваClose() готовый рисунок доступен только для чтения через свойство DrawingVisual.Drawing.
Таблица 11
Методы класса DrawingContext
Имя метода Описание
DrawLine(),
DrawRectangle(),
DrawRoundedRectangle(),
DrawEllipse() Рисует указанную фигуру в указанной точке, с указанным заполнением и контуром
DrawGeometry(),
DrawDrawing() Рисует объекты Geometry и Drawing
DrawText() Рисует текст в указанном месте. Детали оформления текста передаются методу в объекте FormattedText
DrawImage() Рисует битовое изображение в указанной области
DrawVideo() Рисует видео-содержимое (помещённое в объект MediaPlayer)
Pop() Отменяет действие последнего вызова метода PushXXX()
PushClip() Ограничивает рисование определённой областью. Содержимое, выходящее за её пределы, не рисуется
PushEffect() Применяет BitmapEffect к последующим операциям рисования
PushOpacity(),
PushOpacityMask() Применяет новые установки прозрачности или маску прозрачности, чтобы сделать последующие операции рисования частично прозрачными
PushTransform() Устанавливает объект Transform, который будет применён к последующим операциям рисования
Чтобы отобразить визуальный объект, понадобится помощь полноценного элемента WPF, который добавит его в визуальное дерево. Ниже описаны шаги, необходимые для размещения визуальных объектов в элементе.
Вызов методов AddVisualChild() и AddLogicalChild() элемента для регистрации визуального объекта. Эти действия не являются необходимыми для того, чтобы визуальные элементы отобразились. Они нужны, чтобы гарантировать корректное отслеживание элементов в визуальном и логическом дереве, а также для взаимодействия с другими средствами WPF, такими как проверка попадания.
Переопределение свойства VisualChildrenCount и возврат количества добавленных визуальных объектов.
Переопределение метода GetVisualChild() и добавление кода, необходимого для возврата вашего визуального элемента по номеру индекса.
ПереопределениеVisualChildrenCount и GetVisualChild()выполняет графическое замещение элемента. Например, если вы переопределите эти два метода в пользовательском окне, то не увидите остального содержимого этого окна. Вместо этого вы увидите только добавленные визуальные объекты.По этой причине принято создавать выделенный пользовательский класс, который служит оболочкой для визуальных объектов, которые нужно отобразить.
12. РЕСУРСЫПлатформа .NETподдерживает инфраструктуру для работы с ресурсами – информационными фрагментами приложения, представляющими изображения, таблицы строк или иные данные. WPFрасширяет базовые возможности .NET, предоставляя поддержку двух видов ресурсов – двоичных и логических.
Двоичные ресурсыДвоичный ресурс в WPF – это традиционный ресурс с точки зрения платформы .NET. Обычно на этапе разработки двоичный ресурс представлен в виде файла в составе проекта. Такой файл может быть внедрён в сборку .NETили существовать в виде отдельного компонента, логически связанного со сборкой. Это поведение настраивается в VisualStudioв окне свойств файла. Установите BuildActionвзначение Resource для внедрения ресурса, или в Content для закрепления ассоциации между отдельным файлом ресурса и сборкой.
Для доступа к двоичным ресурсам WPFобычно использует универсальный идентификатор ресурса в формате упакованного URI (этот синтаксис закреплён в стандарте XPS).Упакованный URIимеет вид pack://контейнер/путь. Ниже представлены некоторые типичные примеры упакованныхURI.
pack://application:,,,/img.png – ресурс img.png (изображение), внедрённый в текущую сборку, или файл img.png, ассоциированный со сборкой при компиляции.
pack://application:,,,/sub/img.png – аналогично предыдущему, но в проекте Visual Studio файл img.png располагался в подкаталогеsub.
pack://application:,,,/Assembly_2;component/img.png – ресурсimg.png, внедрённыйвсборкуAssembly_2.dll.
sub/img.png – относительный упакованный URI для ссылки на ресурс, связанный с текущей сборкой.
Для работы с упакованнымиURIимеется класс System.Uri. Вот пример связывания элемента Imageс изображением, представленным в виде ресурса:
// этотобъектописываетабсолютныйURI
var absoluteUri = newUri("pack://application:,,,/images/img.png");
// этотобъектописываетотносительныйURI
var relativeUri = newUri("images/img.png", UriKind.Relative);
// создадимэлементImage
var picture = newImage();
// свяжемImageсbitmap-изображением, используяабсолютныйURI
picture.Source = newBitmapImage(absoluteUri);
В разметке XAML ссылки на ресурсы обычно задаются простыми строками, так как имеется конвертер типов «строка–объект URI»:
<Image x:Name="img" Source="images/img.png" />
Логические ресурсыЛогические (объектные) ресурсы – это произвольные объекты, ассоциированные с элементом WPF. Классы FrameworkElement и FrameworkContentElement определяют словарь Resources с типом System.Windows.ResourceDictionary. Этот словарь хранит коллекцию логических ресурсов элемента. С коллекцией Resources можно работать как в коде, так и в разметке XAML.В последнем случае объект, помещаемый в ресурс, должен обладать конструктором без параметров. Кроме этого, для определения ключа словаря в разметке требуется использовать атрибут x:Key из пространства имён анализатора XAML:
<!-- Определение ресурсов для кнопки в XAML-->
<Button x:Name="btn" Content="OK">
<Button.Resources>
<SolidColorBrush x:Key="background">
Yellow
</SolidColorBrush>
<SolidColorBrush x:Key="borderBrush">
Red
</SolidColorBrush>
</Button.Resources>
</Button>
// эквивалентное определение ресурсов для кнопки в коде
btn.Resources.Add("background", newSolidColorBrush(Colors.Yellow));
btn.Resources.Add("borderBrush", newSolidColorBrush(Colors.Red));
Чтобы сослаться на ресурс в разметке XAML, необходимо использовать расширения разметки {StaticResource} или {DynamicResource}.При этом указывается ключ ресурса. Анализатор XAML выполняет поиск ресурса по ключу, просматривая коллекцию ресурсов элемента, затем ресурсы родительского элемента, и так далее. Вот почему ресурсы обычно объявляются на уровне родительского окна или объекта Application.
<Window xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Window.Resources>
<SolidColorBrush x:Key="borderBrush">Red</SolidColorBrush>
</Window.Resources>
<Button Content="OK" BorderBrush="{StaticResource borderBrush}"/>
</Window>
При статической ссылке изменения ресурса после применения не влияют на целевой объект. При динамическом использовании любая модификация ресурса (даже после применения!) отразится на целевом объекте. Ниже показан пример статического и динамического применения ресурсов в коде:
// статическое применение ресурса с поиском по дереву элементов
button.Background = (Brush)button.FindResource("background");
// статическое применение ресурса из заданной коллекции ресурсов
button.Background = (Brush)window.Resources["background"];
// динамическоеприменениересурса
button.SetResourceReference(Button.BackgroundProperty, "background");
Определение логических ресурсов часто выносится в отдельныйXAML-файл, который в качестве корневого элемента содержит <ResourceDictionary>:
<ResourceDictionary xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<Image x:Key="logo" Source="logo.jpg"/>
</ResourceDictionary>
ДлятогочтобыобъединитьресурсывфайлахсколлекциейResources, следуетиспользоватьсвойствоMergedDictionariesклассаResourceDictionary:
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="file1.xaml"/>
<ResourceDictionary Source="file2.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
13. ПРИВЯЗКА данныхБазовые концепции привязки данныхПривязка данных (databinding) – это отношение, которое используется для извлечения информации из объекта-источника и установки свойства целевого объекта. Механизм привязки данных WPFработает только для свойств зависимостей целевого объекта. Сам целевой объект обычно является элементом управления. Объектом-источником может быть произвольный объект .NET. Привязка данных способна (при определённых условиях) автоматически обновлять целевое свойство при изменении объекта-источника.
Очевидно, что при определении привязки данных нужно указать объект-источник и правило извлечения информации из него. Кроме этого, можно выполнить настройку следующих параметров:
Направление привязки. Привязка может быть однонаправленной (целевое свойство меняется при изменении свойства-источника), двунаправленной (модификации в источнике и целевом объекте влияют друг на друга) и от цели к источнику (исходное свойство обновляется при изменении целевого свойства).
Условие обновленияисточника. Если привязка двунаправленная или от цели к источнику, можно настроить момент обновления источника.
Конвертеры значений. Привязка может выполнять автоматическое преобразование данных при их перемещении от источника к целевому объекту и наоборот.
Проверка данных. Привязка позволяет определять правила валидации и поведение, осуществляемое при нарушении этих правил.
Привязка данных задаётся объектом классаSystem.Windows.Data.Binding. В табл. 12приведено описание основных свойств этого класса.
Таблица 12
Свойства класса Binding
Имя свойства Описание
Converter Объект-конвертер, используемый для преобразования данных
ConverterCulture Культура, передаваемая в конвертер
ConverterParameter Произвольный параметр, передаваемый в конвертер
ElementName Имя элемента WPF, который будет объектом-источником. Этот элемент и целевой объект должны принадлежать одному логическому дереву. Свойство нельзя задать совместно со свойствами RelativeSource или Source
FallbackValue Значение, которое будет использоваться для целевого свойства, если операция привязки закончилась неудачей
Mode Направление привязки (перечислениеBindingMode):
OneWay– целевое свойство обновляется при изменении свойства-источника;
TwoWay– целевое свойство обновляется при изменении свойства-источника, и свойство-источник обновляется при изменении целевого свойства;
OneTime– целевое свойство устанавливается изначально на основе значения свойства-источника, и с этого момента изменения в источнике игнорируются;
OneWayToSource– исходное свойство обновляется при изменении целевого свойства;
Default– используется направление привязки, заданное в метаданных свойства зависимостей
IsAsync Еслисвойство установлено в true, извлечение данных из источника будет происходить асинхронно
NotifyOnSourceUpdated Булево значение: указывает на необходимость генерации события SourceUpdated при передаче информации от объекта-источника к целевому объекту
NotifyOnTargetUpdated Булево значение: указывает на необходимость генерации события TargetUpdated при передаче информации от целевого объекта к объекту-источнику
NotifyOnValidationError Булево значение: указывает на необходимость генерации присоединённого события Errorу целевого объекта при ошибках проверки данных
Path Путь к информации в объекте-источнике
RelativeSource Путь к объекту-источнику, задаваемый относительно целевого объекта.Свойство нельзя задать совместно со свойствами ElementName или Source
Source Объект-источник. Свойство нельзя задать совместно со свойствами ElementName или RelativeSource
StringFormat Форматированиедля извлекаемых данных строкового типа
TargetNullValue Значение, которое будет использоваться для целевого свойства, если из источника извлекается null
ValidatesOnDataErrors Булево значение: указывает на использование объектом-источником интерфейса IDataErrorInfo
ValidatesOnExceptions Булево значение: указывает на необходимость рассматривать исключения как ошибки валидации
ValidationRules Коллекция объектов, определяющих правила валидации
UpdateSourceExceptionFilter Метод обработки исключений, генерируемых при валидации
UpdateSourceTrigger Задаёт момент обновления источника при изменениях в целевом свойстве (перечислениеUpdateSourceTrigger):
PropertyChanged–немедленно при изменении в целевом свойстве;
LostFocus–при потере целевым элементом управления фокуса ввода;
Explicit– при вызове метода UpdateSource();
Default– по значению, заданному в метаданных свойства зависимостей при помощи DefaultUpdateSourceTrigger
XPath Выражение XPath. Может использоваться, если источник привязки возвращает XML
В свойство привязки Path записывается строка по следующим правилам:
В простейшем случае значением свойства Path является имя свойства объекта-источника, используемого в привязке:Path=PropertyName.
Подсвойства свойства можно указывать с помощью синтаксиса, подобного используемому в C#: Path=ShoppingCart.Order.
Для создания привязки к присоединяемому свойству поместите это присоединяемое свойство в скобки:Path=(DockPanel.Dock). Такой же синтаксис можно использовать и для простых свойств, если известен тип объекта-источника: Path=(TextBox.Text).
Элементы индексатора можно использовать с помощью квадратных скобок: Path=ShoppingCart[0].
Если источник является представлением коллекции, Path=/ задаёт привязку к текущему элементу в коллекции. Можно указать подсвойство текущего элемента: Path=/SubProp.
Для привязки к текущему источнику можно использовать путь в виде точки, т. е.Text="{Binding}" эквивалентно Text="{Binding Path=.}".
Практическое использование привязки данныхПостроим примерразметки XAML, в котором привязка соединяет два свойства разных элементов управления.Для привязки данных определено расширения разметки {Binding}, что позволяет настроить привязку декларативно.Сделаем так, чтобы при перемещении ползунка слайдера автоматически менялся размер строки текста:
<StackPanel>
<Slider Name="slider" Margin="3" Minimum="1" Maximum="40" />
<TextBlock Margin="10" Text="Sample Text" Name="lblText"
FontSize="{Binding ElementName=slider, Path=Value}"/>
</StackPanel>

Рис. 36. Привязка для элементов управления.
Привязку можно выполнить не только декларативно, но и программно (а в некоторых ситуациях это единственный вариант). Нижеприведёнкодпрограммнойпривязки, эквивалентной примеру разметки XAML:
Binding binding = newBinding();
binding.Source = slider;
binding.Path = newPropertyPath("Value");
lblText.SetBinding(TextBlock.FontSizeProperty, binding);
Заданную привязку можно удалить в коде, используя два статических метода класса BindingOperations. Метод ClearBinding() принимает ссылку на свойство зависимостей, которое имеет привязку, подлежащую удалению, аClearAllBindings() удаляет все привязки элемента:
BindingOperations.ClearAllBindings(lblText);
Следующий пример XAMLдемонстрирует двунаправленную привязку: при перемещении ползунка слайдера меняется текст в TextBox, акорректное изменение текста (допустимы вещественные числа) ведёт к перемещению ползунка. Обратите внимание на установкуUpdateSourceTrigger=PropertyChanged.Это обеспечивает мгновенные изменения позиции ползунка прямо при вводе текста.
<StackPanel>
<Slider Name="slider" Minimum="1" Maximum="40" Value="10" />
<TextBox Name="txtSize"
Text="{Binding ElementName=slider, Path=Value,
Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"/>
</StackPanel>
ЕслиUpdateSourceTriggerустановитьвUpdateSourceTrigger.Explicit, требуетсянаписатькод, вызывающийдляобновленияметодUpdateSource()уобъектаBindingExpression, связанногосэлементом:
// получить выражение привязки для текстового поля
BindingExpression binding =
txtSize.GetBindingExpression(TextBox.TextProperty);
// обновитьсвязанныйисточник (Slider)
binding.UpdateSource();
Рассмотрим сценарии, в которых источником привязки является не элемент WPF, а обычный объект. В этом случае вместо ElementName используется одно из описанных ниже свойств:
Source– свойство привязки, указывающее на объект-источник.
RelativeSource– свойство привязки, которое указывает на источник относительно текущего элемента. Это подход, часто применяемый при разработке шаблонов элементов управления и шаблонов данных.
DataContext– наследуемое свойство FrameworkElement.Если источник не указан через Source или RelativeSource, WPF выполняет поиск по дереву элементов, начиная с текущего элемента. В качестве источника данных будет использоваться первый DataContext, не равный null.
Следующий пример разметки XAMLдемонстрирует использование свойства Sourceдля связывания с объектом класса Person. Так как объект-источник должен существовать на момент выполнения привязки, этот объект помещается в словарь ресурсов окна.
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication">
<Window.Resources>
<local:Person x:Key="person" Name="Mr. Smith" Age="27.3" />
</Window.Resources>
<StackPanel>
<TextBox Text="{Binding Source={StaticResource person},
Path=Name}"/>
</StackPanel>
</Window>
publicclassPerson
{
publicstring Name { get; set; }
publicdouble Age { get; set;}
publicstring Address { get; set; }
}
Модификация примера использует для размещения объекта привязки свойство DataContext у контейнера StackPanel:
<!--пропущена настройка окна и объявление ресурса-->
<StackPanel DataContext="{StaticResource person}">
<TextBox Text="{BindingName}"/>
</StackPanel>
Свойство привязки RelativeSource позволяет указать объект-источник на основе его отношения к целевому объекту. Например, можно использовать RelativeSource для привязки элемента к самому себе или к родительскому элементу, находящемуся на неизвестное число шагов вверх по дереву элементов.Для установки свойства Binding.RelativeSourceиспользуется объект класса RelativeSource. При этом можно использовать как специальное расширение разметки, так и синтаксис, основанный на элементах-свойствах.
<!--используем элемент-свойствоXAML-->
<TextBlock>
<TextBlock.Text>
<Binding Path="Title">
<Binding.RelativeSource>
<RelativeSource Mode="FindAncestor"
AncestorType="{x:Type Window}" />
</Binding.RelativeSource>
</Binding>
</TextBlock.Text>
</TextBlock>
<!--используемрасширениеразметки -->
<TextBlock Text="{Binding Path=Title,
RelativeSource={RelativeSource FindAncestor,
AncestorType={x:Type Window}}}" />
В табл. 13 перечислены четыре возможных режима для RelativeSource.
Таблица 13
Значения перечисления RelativeSourceMode
Имя Описание
Self Выражение привязывает к другому свойству того же элемента
FindAncestor Выражение привязывает к родительскому элементу. WPF будет производить поиск вверх по дереву элементов, пока не найдёт нужного родителя. Чтобы специфицировать родителя, необходимо также установить свойство AncestorType для указания типа родительского элемента. Можно использовать свойство AncestorLevel, чтобы пропустить определённое число вхождений указанного элемента
PreviousData Выражение привязывает к предыдущему элементу данных в списке
TemplatedParent Выражение привязывает к элементу, к которому применён шаблон. Этот режим работает, только если привязка расположена внутри шаблона элемента управления или шаблона данных
Если при привязке к объекту .NETнеобходимо, чтобы привязка отслеживала изменения в объекте, можно поступить одним из следующих способов:
Сделать отслеживаемые свойства объекта-источника свойствами зависимостей, а сам объект наследовать от DependencyObject.
Инициировать при изменении свойства события ИмяСвойстваChanged.
Реализовать в объекте-источникеинтерфейс INotifyPropertyChanged, содержащий событиеPropertyChanged. Это событие нужно инициировать всякий раз, когда свойство изменяется, передавая имя свойства.
Ниже приведена реализация класса Person, использующая третий подход:
publicclassPerson : INotifyPropertyChanged
{
publiceventPropertyChangedEventHandler PropertyChanged;
protectedvirtualvoid OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this,
newPropertyChangedEventArgs(propertyName));
}
privatestring _name;
publicstring Name
{
get {return _name}
set{
if (value != _name)
{
_name = value;
OnPropertyChanged("Name");
}
}
} // остальные свойства надо реализовать аналогично
}
Конвертеры значенийКонвертер значений отвечает за преобразование исходных данных непосредственно перед их отображением в целевом элементе и (в случае двунаправленной привязки) преобразования нового целевого значения непосредственно перед его применением к источнику.
Для создания конвертера значений требуется выполнить четыре шага.
Создать класс, реализующий интерфейсIValueConverter.
Добавить атрибут [ValueConversion] в объявление класса и специфицировать исходный и целевой типы данных (это необязательный шаг).
Реализовать метод Convert(), преобразующий данные из исходного формата в отображаемый формат.
Реализовать метод ConvertBack(), выполняющий обратное преобразование значения из отображаемого формата в его «родной» формат.
Приведём пример очень простого конвертера значений, преобразующего булево значение в кисть WPF:
[ValueConversion(typeof(bool), typeof(Brush))]
publicclassBoolToColorConverter : IValueConverter
{
publicobject Convert(object value, Type targetType,
object parameter, CultureInfo culture)
{
return (bool)value ? Brushes.Blue :Brushes.Green;
}
publicobject ConvertBack(object value, Type targetType,
object parameter,
CultureInfo culture)
{
returnnull; // ожидаем, чтопривязкаоднонаправленная
}
}
Чтобы ввести в действие конвертер значений, нужно использовать свойство привязки Converter и, при необходимости, свойства ConverterParameter и ConverterCulture. Как несложно понять, значения последних двух свойств передаются методам конвертирования в качестве аргументов. Объект, соответствующий конвертеру, обычно размещают в ресурсах элемента или окна:
<Window x:Class="WpfApplication.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Converters="clr-namespace:WpfApplication">
<Window.Resources>
<Converters:BoolToColorConverter x:Key="BoolToColor"/>
</Window.Resources>
<TextBlockForeground="{Binding,
Converter={StaticResourceBoolToColor}}"/>
</Window>
С помощью конвертеров значений можно реализовать оценку нескольких свойств-источников и использование их для создания единственного конвертированного значения. Для этого необходимо:
Использовать привязку MultiBinding вместо обычного объекта Binding;
Создать конвертер, реализующий интерфейс IMultiValueConverter.
MultiBinding группирует последовательность объектов Binding. Ниже приведен пример, где MultiBinding использует два свойства в объекте данных:
<TextBox>
<TextBox.Text>
<MultiBindingConverter="{StaticResourceValueInStockConverter}">
<Binding Path="UnitCost"/>
<Binding Path="UnitsInStock"/>
</MultiBinding>
</TextBox.Text>
</TextBox>
ИнтерфейсIMultiValueConverterопределяетметодыConvert()иConvertBack(), аналогичноинтерфейсуIValueConverter. Отличие в том, что им передаётся массив значений и типов значений вместо единственного значения. Эти значения помещаются в том же порядке, как они определены в разметке.
Проверка данныхДля привязки данных, которая передаёт информацию в объект-источник (режимы TwoWay и OneWayToSource), можно реализовать проверку данных. Если данные не проходят проверку, то источник не обновляется, а пользователь получает визуальный сигнал об ошибке.
Первый способ организации проверки заключается в создании и применении проверочных правил. Каждое проверочное правило – это наследник класса ValidationRuleс переопределённым методомValidate(). Этот метод получает проверяемое значение и информацию о культуре, а возвращает объект ValidationResult с результатом проверки. Ниже приведён пример правила, ограничивающего десятичные значения некоторым диапазоном:
publicclassPositivePriceRule : ValidationRule
{
privatedecimal min = 0;
privatedecimal max = Decimal.MaxValue;
publicdecimal Min
{
get { return min; }
set { min = value; }
}
publicdecimal Max
{
get { return max; }
set { max = value; }
}
publicoverrideValidationResult Validate(object value,
CultureInfo culture)
{
decimal price = 0;
try
{
price = Decimal.Parse((string)value, NumberStyles.Any,
culture);
}
catch
{
returnnewValidationResult(false, "Недопустимыесимволы");
}
if ((price < Min) || (price > Max))
returnnewValidationResult(false, "Внедиапазона");
else
returnnewValidationResult(true, null);
}
}
Проверочные правила помещают в коллекцию ValidationRules, имеющуюся у каждой привязки:
<TextBox>
<TextBox.Text>
<Binding Path="UnitCost" Mode="TwoWay">
<Binding.ValidationRules>
<local:PositivePriceRule Max="999.99"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Второй способ организации проверки основан на генерировании исключительной ситуации в методе установки свойства объекта-источника:
// фрагмент класса: определение свойства UnitCost
publicdecimal UnitCost
{
get { return unitCost; }
set
{
if (value<0)
{
thrownewArgumentException("UnitCost < 0");
}
else
{
unitCost = value;
}
}
}
Исключительные ситуации, возникающие при передаче информации в объект, отлавливаются при помощи специального встроенного проверочного правила ExceptionValidationRule,помещённого в ValidationRules.
<Binding.ValidationRules>
<ExceptionValidationRule />
</Binding.ValidationRules>
В качестве альтернативы использованиюExceptionValidationRule можно просто установить свойство привязкиValidatesOnExceptions в значение true.
Третий способ организации проверки данных основан на реализации объектом-источником интерфейсаSystem.ComponentModel.IDataErrorInfo. Интерфейс IDataErrorInfoсодержит два элемента: строковое свойствоError и строковый индексатор с ключом-строкой. Свойство Error– это общее описание ошибок объекта. Индексатор принимает имя свойства и возвращает соответствующую детальную информацию об ошибке. Ключевая идея в том, что вся логика обработки ошибок централизована в индексаторе.
Ниже приведён фрагмент класса, реализующего IDataErrorInfo. В примере проверяется на наличие ошибок только одно свойство.
publicclassProduct : IDataErrorInfo
{
publicstring ModelNumber { get; set; }
// обработкаошибокпроисходитздесь
publicstringthis[string propertyName]
{
get
{
if (propertyName == "ModelNumber"&&
ModelNumber.Any(c => !Char.IsLetterOrDigit(c)))
return"В ModelNumber допустимы буквы и цифры";
returnnull;
}
}
// WPF не использует это свойство
publicstring Error
{
get { returnnull; }
}
}
Чтобы заставить WPF использовать интерфейс IDataErrorInfo и применять его для проверки ошибок при модификации свойства, нужно добавить встроенное правило DataErrorValidationRuleв коллекцию ValidationRules:
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
В качестве альтернативы использованиюDataErrorValidationRule можно установить свойство привязкиValidatesOnDataErrors в значение true.
При нарушении любого из проверочных правил, инфраструктура WPFвыполняет следующие шаги:
В целевом элементеприсоединённое свойствоValidation.HasError устанавливается в true.
Создаётся объект ValidationError с информацией об ошибке и добавляется в присоединённую коллекциюValidation.Errors.
Если свойство привязки NotifyOnValidationError установлено в true, WPF инициирует в целевом элементе присоединённое событие Validation.Error.
Визуальное представление целевого элемента управления также изменяется при возникновении ошибки. Шаблон элемента заменяется шаблоном, определённым в свойстве Validation.ErrorTemplate. Например, в текстовом поле новый шаблон окрашивает контур рамки в красный цвет.
14. СТИЛИИ триггерыСтиль– это коллекция значений свойств, которые могут быть применены к элементу. В WPFстили играют ту же роль, которую CSS играет в HTML-разметке. Подобно CSS, стили WPF позволяют определять общий набор характеристик форматирования и применять их по всему приложению для обеспечения согласованности. Стили могут работать автоматически, предназначаться для элементов конкретного типа и каскадироваться через дерево элементов.
Рассмотрение стилей начнём с конкретного примера, в котором определяется и применяется стиль для кнопок.
<StackPanel Orientation="Horizontal" Margin="10">
<StackPanel.Resources>
<Style x:Key="buttonStyle">
<Setter Property="Button.FontSize" Value="22"/>
<Setter Property="Button.Background" Value="Purple"/>
<Setter Property="Button.Foreground" Value="White"/>
<Setter Property="Button.Height" Value="50"/>
<Setter Property="Button.Width" Value="50"/>
<Setter Property="Button.RenderTransformOrigin" Value=".5,.5"/>
<Setter Property="Button.RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
</Style>
</StackPanel.Resources>
<Button Style="{StaticResource buttonStyle}">1</Button>
<Button Style="{StaticResource buttonStyle}">2</Button>
<Button Style="{StaticResource buttonStyle}">3</Button>
</StackPanel>

Рис. 37.Кнопки, к которым применён стиль.
Любой стиль – это объект класса System.Windows.Style. Основным свойством стиля является коллекция Setters, в которой каждый элемент задаётзначение для некоторого свойства зависимостей (стили не работают с обычными свойствами – только со свойствами зависимостей). В разметке стилядочерние элементыс именем<Setter> автоматически помещаются в коллекцию Setters.
Чтобы задать значение для свойства зависимостей, нужно указать имя этого свойства. В нашем примере имя включает префикс –класс элемента. Если требуется применить один стиль к визуальным элементам разных типов, для префиксаиспользуется имя общего типа-предка:
<StackPanel Orientation="Horizontal" Margin="10">
<StackPanel.Resources>
<Style x:Key="controlStyle">
<Setter Property="Control.FontSize" Value="22"/>
<Setter Property="Control.Background" Value="Purple"/>
<Setter Property="Control.Foreground" Value="White"/>
<Setter Property="Control.Height" Value="50"/>
<Setter Property="Control.Width" Value="50"/>
</Style>
</StackPanel.Resources>
<Button Style="{StaticResource controlStyle}" Content="1"/>
<TextBox Style="{StaticResource controlStyle}" Text="Hi"/>
<Expander Style="{StaticResource controlStyle}" Content="3"/>
</StackPanel>
Свойство стиля TargetType позволяет указать конкретный тип, к которому применяется стиль. В этом случае префикс в установщиках свойств использовать необязательно:
<Stylex:Key="baseStyle"TargetType="{x:TypeButton}">
<Setter Property="FontSize" Value="22"/>
<Setter Property="Background" Value="Purple"/>
<Setter Property="Foreground" Value="White"/>
</Style>
Определяя стиль, можно построить его на основе стиля-предка. Для этого следует воспользоваться свойством стиля BasedOn. Стили также могут содержатьлокальные логические ресурсы (коллекция Resources):
<Stylex:Key="baseStyleWH"TargetType="{x:TypeButton}"
BasedOn="{StaticResource baseStyle}">
<Style.Resources>
<sys:Double x:Key="size">50</sys:Double>
</Style.Resources>
<Setter Property="Height" Value="{StaticResource size}"/>
<Setter Property="Width" Value="{StaticResource size}"/>
</Style>
Кроме установки свойств зависимостей, стиль позволяет задать обработчики событий. Дляэтогоприменяетсяэлемент<EventSetter>:
<Style x:Key="buttonStyle" TargetType="{x:Type Button}">
<Setter Property="FontSize" Value="22"/>
<EventSetter Event="MouseEnter" Handler="button_MouseEnter"/>
</Style>
Как показывает первый пример параграфа, для применения стилей в элементе управления следует установить свойство Style, которое определено в классе FrameworkElement. Хотя стиль можно определить в самом элементе управления, обычно для этого используются ресурсы окна или приложения. Если при описании стиля в ресурсе задать TargetType, но не указывать ключ, стиль будет автоматически применяться ко всем элементам указанного типа.
<StackPanel Orientation="Horizontal" Margin="10">
<StackPanel.Resources>
<Style TargetType="{x:Type Button}">
<Setter Property="FontSize" Value="22"/>
<Setter Property="Background" Value="Purple"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="50"/>
</Style>
</StackPanel.Resources>
<!-- эта кнопка будет иметь указанный выше стиль -->
<Button Content="1"/>
<!-- у этой кнопки такой же стиль, но задана своя ширина -->
<Button Width="100" Content="2"/>
<!-- убрали стиль у кнопки, присвоивStyle значение null -->
<Button Style="{x:Null}" Content="3"/>
</StackPanel>
При описании стилей часто применяются триггеры. Триггер – это способ описания реакции на изменение свойства зависимостей, альтернативный применению обработчиков событий. Триггеры имеют два достоинства. Во-первых, их легко задать декларативно. Во-вторых, для триггера указывается только условие старта, условие завершения действия триггера указывать не надо.
Каждый триггер является экземпляром класса, который наследуется от System.Windows.TriggerBase. В WPFдоступно пять видов триггеров:
Триггер свойства – класс Trigger. Это триггер самого простого типа. Он следит за появлением изменений в свойстве зависимостей.
Триггер события – класс EventTrigger. Триггер следит за наступлением указанного события.
Триггер данных – класс DataTrigger.Этот триггер работает со связыванием данных. Он похож на триггер свойства, но следит за появлением изменений не в свойстве зависимостей, а в любых связанных данных.
Мультитриггер – класс MultiTrigger. Данный триггер объединяет несколько триггеров свойств. Он стартует, если выполняются все условия объединяемых триггеров.
Мультитриггер данных – класс MultiDataTrigger. Подобен мультитриггеру, но объединяет несколько триггеров данных.
Для декларации и хранения триггеров у классов FrameworkElement, Style, DataTemplateиControlTemplateимеется коллекция Triggers. Правда, FrameworkElementподдерживает только триггеры событий.
Рассмотрим применение триггеров свойств при описании стиля. У триггера свойства настраиваются:
Имя свойства, с которым связан триггер;
Значение свойства, которое задаёт условие старта триггера;
Коллекция Setters, описывающая действие триггера.
Взяв за основу первый пример этого параграфа, модифицируем стиль при помощи триггера так, чтобы поворот кнопки происходил при перемещении над ней указателя мыши:
<Style x:Key="buttonStyle" TargetType="{x:Type Button}">
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Foreground" Value="Black"/>
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
</Trigger>
</Style.Triggers>
<Setter Property="FontSize" Value="22"/>
<Setter Property="Background" Value="Purple"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="Height" Value="50"/>
<Setter Property="Width" Value="50"/>
<Setter Property="RenderTransformOrigin" Value=".5,.5"/>
</Style>
В следующем примере триггер используется для того, чтобы изменить внешний вид TextBoxпри наступлении ошибки проверки. Обратите внимание на использование привязки данных и работу с присоединёнными свойствами.
<Style x:Key="textBoxStyle" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="Background" Value="Red"/>
<Setter Property="ToolTip"
Value="{Binding RelativeSource={RelativeSource Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
В стилях могут применяться не только триггеры свойств, но и триггеры данных, которые позволяют отследить изменения в обычном свойстве любого объекта .NET (а не только в свойствах зависимостей). Вместо свойства Propertyв триггерах данных применяется свойство Binding, содержащее привязку данных. Ниже демонстрируется пример стиля с триггером данных – элемент управления TextBox делается неактивным, если в него ввести «disabled».
<StackPanel>
<StackPanel.Resources>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding
RelativeSource={RelativeSource Self},
Path=Text}"
Value="disabled">
<Setter Property="IsEnabled" Value="False"/>
</DataTrigger>
</Style.Triggers>
<Setter Property="Background"
Value="{Binding RelativeSource={RelativeSource Self},
Path=Text}"/>
</Style>
</StackPanel.Resources>
<TextBox Margin="3"/>
<TextBox Margin="3"/>
<TextBox Margin="3"/>
</StackPanel>

Рис. 38.Демонстрация стиля с триггером данных.
В предыдущих примерах коллекция Triggers содержала только один триггер. В эту коллекцию можно поместить любое количество триггеров– они будут работать независимо друг от друга. Если необходимо создать триггер, стартующий при наступлении совокупности изменений, следует воспользоваться классом MultiTrigger (или MultiDataTrigger– для триггеров данных). Ниже описан триггер стиля, при применении которого кнопка поворачивается, если над ней находится указатель мыши,и она имеет фокус ввода.
<Style.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsMouseOver" Value="True"/>
<Condition Property="IsFocused" Value="True"/>
</MultiTrigger.Conditions>
<Setter Property="RenderTransform">
<Setter.Value>
<RotateTransform Angle="10"/>
</Setter.Value>
</Setter>
</MultiTrigger>
</Style.Triggers>
Некоторые элементы управления позволяют не просто применить стиль, а выбирать один из стилей в зависимости от определённых условий. Для организации выбора стиля служит класс System.Windows.Controls.StyleSelector. Программисту следует создать наследник этого класса и перекрыть виртуальный метод SelectStyle(). Следующий пример демонстрирует селектор стиля, который может использоваться в списке ListView, чтобы организовать визуальное чередование строк:
publicclassListViewItemStyleSelector : StyleSelector
{
publicoverrideStyle SelectStyle(object item,
DependencyObject container)
{
var st = newStyle {TargetType = typeof (ListViewItem)};
var backGroundSetter =
newSetter {Property = Control.BackgroundProperty};
var listView =
ItemsControl.ItemsControlFromItemContainer(container) asListView;
int index = listView.ItemContainerGenerator.
IndexFromContainer(container);
backGroundSetter.Value = index % 2 == 0 ?
Brushes.LightBlue :Brushes.Beige;
st.Setters.Add(backGroundSetter);
return st;
}
}
Применение стилей позволяет изменять внешний вид приложения во время его работы. Для этого необходимо выполнить следующие действия:
1. Объявить все стили, образующие требуемый внешний вид, в отдельномXAML-файле словаря ресурсов (ResourceDictionary).
2. Использовать динамические ссылки для связывания элемента управления и стиля из ресурсов:<Label Style="{DynamicResource HeadingStyle}"/>.
3. При помощи кода, подобного следующему фрагменту, по необходимости загружать и подменять ресурсы приложения:
ResourceDictionary resources = null;
using (FileStream fs = newFileStream("Skin.xaml", FileMode.Open))
{
resources = (ResourceDictionary)XamlReader.Load(fs);
}
Application.Current.Resources = resources;
15. ПРИвязкакколлекциямиШАБЛОНЫДАННЫХРассмотрим следующий пример. Пусть имеется класс, описывающий задание для программиста, и классс набором заданий:
publicclassTask
{
publicstring Name { get; set; }
publicstring Note { get; set; }
publicint Priority { get; set; }
publicTaskType Type { get; set; }
}
publicenumTaskType { Coding, Testing, Support }
publicclassTasks : List<Task>
{
public Tasks()
{
Add(newTask{ Name = "Data loading",
Note = "Need to test work with DB",
Priority = 2, Type = TaskType.Testing });
Add(newTask{ Name = "Log class",
Note = "Finish this work",
Priority = 2, Type = TaskType.Coding });
Add(newTask{ Name = "IoC Usage",
Note = "Find more info",
Priority = 4, Type = TaskType.Coding });
Add(newTask{ Name = "Urgent bug fixing",
Note = "Problem with class C",
Priority = 1, Type = TaskType.Support });
Add(newTask{ Name = "UI development",
Note = "Make markup for Main Window",
Priority = 1, Type = TaskType.Coding });
Add(newTask{ Name = "Help Doc",
Note = "Write technical documentation",
Priority = 3, Type = TaskType.Support });
Add(newTask{ Name = "New project!",
Note = "Plan the meeting",
Priority = 1, Type = TaskType.Support });
}
}
Необходимо создать приложение WPF, отображающее задания. Список заданий будет показан в элементе ListBox, связанном с объектом классаTasks. Класс ItemsControl, являющийся предком всех списковых элементов управления, определяет два свойства, которые будут использоваться при привязке:
ItemsSource– указывает на коллекцию, содержащую все объекты, которые будут показаны в списке. В свойство ItemsSource можно поместить любой объект, реализующий интерфейс IEnumerable или его универсальную версию.
DisplayMemberPath–путь к свойству, которое будет применяться для создания отображаемого текста каждого элемента коллекции.
Детальная информация о выбранном задании будет отображаться в наборе текстовых полей. Для этого применяется привязка к свойству DataContext контейнера, обрамляющего поля.
<Window x:Class="TaskView.MainWindow" Title="List of Tasks"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<StackPanel Margin="5" Orientation="Horizontal">
<ListBox Name="lstTasks" DisplayMemberPath="Name"Width="250"/>
<Grid Margin="5" DataContext="{Binding ElementName=lstTasks,
Path=SelectedItem}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="7" />
<ColumnDefinition Width="200" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="11" />
<RowDefinition Height="Auto" />
<RowDefinition Height="11" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Name:" />
<TextBox Grid.Row="0" Grid.Column="2"
Text="{Binding Name}" />
<TextBlock Grid.Row="2" Grid.Column="0" Text="Note:" />
<TextBox Grid.Row="2" Grid.Column="2"
Text="{Binding Note}" />
<TextBlock Grid.Row="4" Grid.Column="0" Text="Priority:" />
<TextBox Grid.Row="4" Grid.Column="2"
Text="{Binding Priority}" />
</Grid>
</StackPanel>
</Window>
// файлCode Behind
namespace TaskView
{
publicpartialclassMainWindow : Window
{
public MainWindow()
{
InitializeComponent();
lstTasks.ItemsSource = newTasks();
}
}
}

Рис. 39.Привязка списку, форма «главная-подробности».
Поддержка, которую даёт интерфейс IEnumerable, ограничена привязкой только для чтения – изменения, производимые в коллекции после привязки, в списковом элементе управления не отображаются. Чтобы включить отслеживание изменений, нужно использовать коллекцию с интерфейсом INotifyCollectionChanged. WPF включает единственную коллекцию, реализующую этот интерфейс, – это класс ObservableCollection<T>.
// изменённыйклассMainWindow
publicpartialclassMainWindow : Window
{
privatereadonlyObservableCollection<Task> tasks;
public MainWindow()
{
InitializeComponent();
tasks = newObservableCollection<Task>(newTasks());
lstTasks.ItemsSource = tasks;
}
}
Изменим внешний вид списка заданий при помощи шаблона данных. Шаблоны данных (datatemplates) – механизм для настройки отображенияобъектов определённого типа. В WPFшаблон данных – это объект класса System.Windows.DataTemplate. Основное свойство шаблона –VisualTree. Оно содержит визуальный элемент, определяющий внешний вид шаблона. Часто этим визуальным элементом является контейнер компоновки. В разметке XAMLдля задания VisualTree достаточно поместить в DataTemplate дочерний элемент. При формировании VisualTree обычно используется привязка данных для извлечения информации из объекта, для которого применяется шаблон. Сам шаблон данных, как правило, размещают в ресурсах окна или приложения.
С учётом вышесказанного определим шаблон данных в ресурсах окна и используем свойство списка ItemTemplateдля применения шаблона к каждому элементу списка:
<!-- определяем шаблон в ресурсах окна -->
<Window.Resources>
<DataTemplate x:Key="taskTemplate">
<Border Name="border" BorderBrush="Aqua" BorderThickness="1"
CornerRadius="2" Padding="5" Margin="5">
<TextBlock FontSize="14" FontWeight="Bold"
Text="{Binding Name}" />
</Border>
</DataTemplate>
</Window.Resources>
<!-- изменённаянастройка ListBox -->
<ListBox Name="lstTasks" HorizontalContentAlignment="Stretch"
ItemTemplate="{StaticResource taskTemplate}" Width="250"/>

Рис. 40.Список, к которому применён шаблон данных.
У классаDataTemplate имеется свойство DataType, определяющее тип данных, к которому будет применяться шаблон. Если задано это свойство, WPFбудет использовать шаблон в любой ситуации, где до этого выводилась строка с результатом ToString():
<!-- не указываем ключ ресурса, если шаблон в ресурсах -->
<!--подключено xmlns:TaskView="clr-namespace:TaskView" -->
<DataTemplate DataType="{x:Type TaskView:Task}">
<!-- описание шаблона не изменилось -->
</DataTemplate>
<!-- вListBoxнезадаём свойствоItemTemplate -->
<ListBox Name="lstTasks" Width="250" HorizontalContentAlignment="Stretch"/>
В шаблон данных можно поместить триггеры данных. Следующий пример показывает использование триггера для того, чтобы изменить цвет окантовки задач из группы TaskType.Support:
<!-- эта разметка – часть шаблона DataTemplate -->
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Type}" Value="Support">
<Setter TargetName="border" Property="BorderBrush"
Value="Brown" />
</DataTrigger>
</DataTemplate.Triggers>
Обратитевнимание–элемент<Setter>содержитустановкуTargetName. Как ясно из контекста,TargetName используется, чтобы обратиться к именованному дочернему элементу визуального представления шаблона данных. Дочерний элемент должен быть описан до триггера.
В WPFсписковые элементыуправления поддерживают возможность выбора для объекта одного из нескольких шаблонов данных. Предположим, что в примере со списком заданий требуется особым образом отображать задания с приоритетом, равным 1. Определим в ресурсах окна ещё один шаблон данных:
<DataTemplate x:Key="importantTask">
<Border BorderBrush="Red" BorderThickness="2"CornerRadius="2"
Padding="5" Margin="5">
<TextBlock FontSize="18" Foreground="Red" Text="{Binding Name}"/>
</Border>
</DataTemplate>
Выбор шаблона выполняется программно, с помощью создания подкласса для DataTemplateSelector и переопределения метода SelectTemplate():
publicclassTaskTemplateSelector : DataTemplateSelector
{
publicoverrideDataTemplate SelectTemplate(object item,
DependencyObject container)
{
if (item != null&& item isTask)
{
var task = item asTask;
Window window = Application.Current.MainWindow;
return task.Priority == 1?
window.FindResource("importantTask")asDataTemplate :
window.FindResource(newDataTemplateKey(typeof(Task)))
asDataTemplate;
}
returnnull;
}
}
ЗатемможнообъявитьобъектTaskTemplateSelectorвкачествересурсаиназначитьэтотресурссвойствуListBox.ItemTemplateSelector. ОбъектListBoxвызываетметодSelectTemplate()длякаждогоэлементавбазовойколлекции.
<ListBoxName="lstTasks" Width="250"
HorizontalContentAlignment="Stretch"
ItemTemplateSelector="{StaticResource selector}"/>

Рис. 41.Селектор шаблонов в действии.
При работе с иерархическими элементами управления (например, TreeView) вместо шаблона данных на основе DataTemplate следует использовать HiererchicalDataTemplate.У такого шаблона имеется свойство ItemsSource, которое нужно связать с дочерней коллекцией, и свойство ItemTemplate – дочернийшаблон данных (DataTemplate или HiererchicalDataTemplate).
16. представления ДанныхПримеры параграфа используют в качестве основы приложение для просмотра списка заданий с простым шаблоном данныхдля типаTask:
<DataTemplate DataType="{x:Type TaskView:Task}">
<Border BorderBrush="Aqua" BorderThickness="1" CornerRadius="2"
Padding="5" Margin="5">
<TextBlock FontSize="14" FontWeight="Bold" Text="{Binding Name}"/>
</Border>
</DataTemplate>
При выполнении привязки коллекции к списковому элементу управления WPFиспользует специальный объект, созданный на основе коллекции и называемый представлением коллекции (collectionview). Представление позволяет фильтровать, сортировать и группировать данные коллекциии даёт возможность навигации по коллекции.
Представления построены на основе интерфейсаICollectionViewиз пространства имён System.ComponentModel. Этот интерфейс унаследован от IEnumerableи INotifyCollectionChanged.Его элементы отписаны в табл. 14.
Таблица 14
Элементы интерфейса ICollectionView
Имя Описание
CanFilter,
CanGroup,
CanSort Булевы свойства, которые указывают на поддержку представлением фильтрации, группировки и сортировки
Contains() Метод для проверки вхождения элемента в представление
Culture Объект, описывающий региональные стандарты (используется при сортировке представления)
CurrentChanging,
CurrentChanged События, генерируемые при изменении позиции текущего элемента
CurrentItem,
CurrentPosition Текущий элемент представления и его позиция
Filter Функция фильтрации, описанная как Predicate<object>
GroupDescriptions Коллекция объектов GroupDescription, описывающих условия группировки данных
Groups Коллекция групп, имеющихся в представлении
IsCurrentAfterLast,
IsCurrentBeforeFirst Булевы свойства, указывающие на то, что текущий элемент представления вышел за пределы инкапсулируемой коллекции
IsEmpty Свойство равно true, если представление не содержит данных
MoveCurrentTo(),
MoveCurrentToFirst(),
MoveCurrentToLast(),
MoveCurrentToNext(),
MoveCurrentToPosition(),
MoveCurrentToPrevious() Эти методы предназначены для изменения позиции текущего элемента представления
Refresh() Метод для повторного создания представления по коллекции
SortDescriptions Коллекция объектов SortDescription, описывающих критерии сортировки данных представления
SourceCollection Коллекция, инкапсулируемая представлением
Существуетчетырестандартныхкласса, реализующихICollectionView: CollectionView, ListCollectionView, BindingListCollectionView (все три из пространстваимён System.Windows.Data) и класс ItemCollection (пространство имён System.Windows.Controls). Выбор класса для представления диктуется источником данных:
Если источник данных реализует интерфейсIBindingList, создаётся объект BindingListCollectionView. Это происходит, в частности, когда источником является ADO.NET-объект DataTable. Представления BindingListCollectionView не поддерживают фильтрацию через свойство Filter, но определяют специальное строковое свойство CustomFilter.
Если источник данных реализует IList, создаётся объект представления ListCollectionView.
Если источник данных реализует только интерфейсIEnumerable, создаётся простейший объект представления CollectionView. Представления этого типа не поддерживают группировку.
Реализуем при помощи представлений фильтр для списка задач. В случае, когда списковый элемент управления уже связан с коллекцией данных, получить представление можно из свойстваItemsсписка (тип этого свойства –ItemCollection).Добавимкнопку установки фильтра высокоприоритетных заданий и кнопку сброса фильтра. Обработчики кнопок показаны ниже:
// обработчик для кнопки установки фильтра
privatevoidbtnImportant_Click(objectsender, RoutedEventArgse)
{
// получаем представление у элемента управления lstTasks
ICollectionView view = lstTasks.Items;
// устанавливаемфильтр, используяPredicate<object>
view.Filter = newPredicate<object>(t => ((Task)t).Priority == 1);
}
// обработчикдлякнопкисбросафильтра
privatevoid btnReset_Click(object sender, RoutedEventArgs e)
{
ICollectionView view = lstTasks.Items;
// чтобысброситьфильтр, достаточноприсвоитьемуnull
view.Filter = null;
}
Используем представление, чтобы выполнить сортировку заданий по приоритету. Отдельный критерий сортировки описывается при помощи структуры System.ComponentModel.SortDescription. При этом указывается имя поля сортировки и направление сортировки (по возрастанию или по убыванию). Представление хранит все критерии сортировки в коллекции SortDescriptions. Ниже показан код обработчика для кнопки сортировки:
privatevoid btnSort_Click(object sender, RoutedEventArgs e)
{
ICollectionView view = lstTasks.Items;
view.SortDescriptions.Add(
newSortDescription("Priority", ListSortDirection.Ascending));
}
Заметим, что если используется представление типа ListCollectionView, сортировку можно выполнить при помощи свойства CustomSort, которое принимает объект IComparer.
Представления позволяют группировать данные коллекции. Для этого нужно добавить объектыPropertyGroupDescription (пространство имён System.Windows.Data) в коллекцию GroupDescriptions:
privatevoid btnGroup_Click(object sender, RoutedEventArgs e)
{
ICollectionView view = lstTasks.Items;
view.GroupDescriptions.Add(
newPropertyGroupDescription {PropertyName = "Type"});
}
Когда используется группировка, для каждой группы создаётся отдельный объект GroupItem, и все эти объекты добавляются в список. Сделатьгруппы видимыми можно, отформатировав элемент GroupItem. Класс ItemsControlимеет свойство GroupStyle, которое предоставляет коллекцию объектов GroupStyle. Несмотря на имя, класс GroupStyle стилем не является. Он представляет собой удобный пакет, содержащий несколько полезных параметров для конфигурированияGroupItem:
ContainerStyle– стиль, который должен применяться к элементу GroupItem;
ContainerStyleSelector – селекторстилядляGroupItem;
HeaderTemplate–шаблон для отображения содержимогов начале каждой группы;
HeaderTemplateSelector – селектор шаблона HeaderTemplate;
HidesIfEmpty – булево свойство, используемое для сокрытия пустых групп.
Чтобы добавить заголовок для группы, нужно установить свойство GroupStyle.HeaderTemplate. Свойство является обычным шаблоном данных. Если в шаблоне будет использоваться привязка данных, её нужно выполнять относительно предназначенного для группы объекта PropertyGroupDescription:
<ListBox Name="lstTasks" Width="250" HorizontalContentAlignment="Stretch">
<ListBox.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=Name}"
FontSize="14" FontWeight="Bold"
Foreground="White" Background="Green"
Margin="0,5,0,0" Padding="3"/>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListBox.GroupStyle>
</ListBox>

Рис. 42. Список заданий с группировкой.
КлассSystem.Windows.Data.CollectionViewSource – это вспомогательный класс для работы с представлениями данных, который удобно использовать в разметке XAML. Двумя наиболее важными свойствами класса CollectionViewSource являются свойство View, которое упаковывает объект представления, и свойство Source, которое указывает на источник данных. У него также имеются свойства SortDescriptions и GroupDescriptions для сортировки и группировки коллекции, и событие Filter, которое можно использовать для выполнения фильтрации. Статический метод CollectionViewSource.GetDefaultView() позволяет получить представление для указанной коллекции.
17. ШАблоны ЭЛЕМЕНТОВ УПРАВЛЕНИЯБольшинство элементов управления имеет внешний вид и поведение. Рассмотрим кнопку: её внешним видом является область для нажатия, а её поведение – это событие Click, которое вызывается в ответ на нажатие кнопки.WPF эффективно разделяет внешний вид и поведение, благодаря концепции шаблона элемента управления. Шаблон элемента управления полностью определяет визуальную структуру элемента. Шаблон переопределяем – в большинстве случаев это обеспечивает достаточную гибкость и освобождает от необходимости написания пользовательских элементов управления.
Рассмотрим создание пользовательского шаблона для кнопки. Шаблон элемента управления – это экземпляр класса System.Windows.ControlTemplate.Основным свойствомшаблона является свойство VisualTree, которое содержит визуальный элемент, определяющий внешний вид шаблона. Для задания VisualTree в разметке XAMLдостаточно поместить в ControlTemplate дочерний элемент. В элементах управления ссылка на шаблон устанавливается через свойство Template. С учетом вышесказанного первая версия шаблона для кнопки будет описана следующей разметкой:
<Button Content="Sample" Width="100" Height="50" Padding="10">
<Button.Template>
<ControlTemplate>
<Border BorderBrush="Orange" BorderThickness="2"
CornerRadius="2" Background="Aqua"/>
</ControlTemplate>
</Button.Template>
</Button>

Рис. 43. Первая версия шаблона для кнопки.
Самый большой недостаток нашего первого шаблона заключается в том, что он не отображает содержимое кнопки (свойство Content). У шаблона может быть установлено свойство TargetType. Оно содержит тип элемента управления, являющегося целью шаблона.Если установлено свойство TargetType, при описании VisualTree для ссылки на содержимое элемента управления можно использовать объекты ContentPresenter (для элементов управления содержимым) или ItemsPresenter (для списковых элементов управления).
<Button Content="Sample" Width="100" Height="50" Padding="10">
<Button.Template>
<ControlTemplateTargetType="Button">
<Border BorderBrush="Orange" BorderThickness="2"
CornerRadius="2" Background="Aqua">
<ContentPresenter />
</Border>
</ControlTemplate>
</Button.Template>
</Button>

Рис. 44. Шаблон, отображающий содержимое кнопки.
Вторая версия шаблона не учитывает отступ, заданный на кнопке при помощи свойства Padding. Чтобы исправить это, используем привязку данных. В шаблонах допустим особый вид привязки–TemplateBinding. Эта привязка извлекает информацию из свойства элемента управления, являющегося целью шаблона.
<Button Content="Sample" Width="100" Height="50" Padding="10">
<Button.Template>
<ControlTemplate TargetType="Button">
<Border BorderBrush="Orange" BorderThickness="2"
CornerRadius="2" Background="Aqua">
<ContentPresenter Margin="{TemplateBinding Padding}"/>
</Border>
</ControlTemplate>
</Button.Template>
</Button>
Вшаблонахэлементовуправлениячастоиспользуютсятриггеры. Например, для кнопки при помощи триггеров можно реализовать изменение внешнего вида при нажатии или при перемещении указателя мыши.
<ControlTemplate TargetType="Button">
<Border Name="brd" BorderBrush="Orange" BorderThickness="2"
CornerRadius="2" Background="Aqua">
<ContentPresenter Margin="{TemplateBinding Padding}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter TargetName="brd" Property="Background"
Value="CornflowerBlue" />
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="brd" Property="Background" Value="Blue" />
<Setter TargetName="brd" Property="BorderBrush" Value="Khaki"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
Заметим, что, как правило, шаблоны элементов управления описываются в ресурсах окна или приложения. Часто шаблон объявляют в стиле элемента управления. Это позволяет создать эффект «шаблона по умолчанию» (в отличие от стиля, шаблон нельзя указать в ресурсах без ключа).
<Window.Resources>
<Style TargetType="Button">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="Button">
<!--содержимоешаблонанеизменилось-->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
Нашшаблондлякнопкипрост. Однако во многих случаях шаблоны выглядят сложнее. Ниже перечислены некоторые из характеристик более сложных шаблонов:
Они включают элементы управления Button, которые инициируют встроенные команды или обработчики событий.
Они используют именованные элементы, имена у которых обычно начинаются с PART_. При создании шаблона следует проверять наличие всех этих именованных элементов, поскольку класс элемента управления может включать код, который манипулирует непосредственно этими элементами (например, присоединяет обработчики событий).
Они включают вложенные элементы управления, которые могут иметь свои собственные шаблоны.
Довольно часто они предполагают организацию элементов с помощью элемента управления Grid (хотя для более точного выравнивания различных элементов может применяться и элемент управления Canvas).

Приложенные файлы

  • docx 17219378
    Размер файла: 1 MB Загрузок: 0

Добавить комментарий