Полная версия
Разработка пользовательского интерфейса на основе технологии Windows Presentation Foundation
А. В. Абрамян, М. Э. Абрамян
Разработка пользовательского интерфейса на основе системы Windows Presentation Foundation
Предисловие
Книга знакомит читателя с основными приемами разработки пользовательского интерфейса на основе системы Windows Presentation Foundation (WPF), входящей в платформу .NET, начиная с версии 3.0. Данная система была создана с целью снять ряд серьезных ограничений, имевшихся у ее предшественницы – системы разработки интерфейса Windows Forms, изначально входившей в состав платформы .NET. При этом наряду с сохранением концепций, лежащих в основе Windows Forms (в частности, механизма обработки событий), система WPF была дополнена новыми технологиями, позволяющими разрабатывать интерфейсы, имеющие существенно более широкие графические возможности и, что не менее важно в современном мире планшетов и смартфонов, допускающими автоматическую адаптацию к особенностям устройств, на которых запущено приложение.
Новой важной чертой системы WPF для разработчиков приложений стало явное разграничение программного кода и макета приложения, которое было обеспечено включением в состав проекта XML-файлов, позволяющих описывать визуальный интерфейс на особом декларативном языке разметки XAML (eXtensible Application Markup Language). Следует отметить, что в настоящее время подобный подход реализован в большинстве систем разработки интерфейсов.
Разработчики приложений на основе технологии WPF получили в свое распоряжение новые концепции, основанные на свойствах зависимостей, маршрутизируемых событиях и привязке данных. Созданные в рамках WPF иерархии классов и визуальных компонентов, в том числе компонентов с содержимым и группирующих компонентов, обеспечивающих динамическую компоновку своих дочерних элементов, позволили гибко комбинировать интерфейсные элементы, обеспечивая подходящее визуальное представление на экране любого размера.
Принципиально новой по сравнению с Windows Forms стала графическая подсистема, основанная на технологии DirectX и позволяющая реализовывать качественную и быструю двумерную и трехмерную графику и анимацию. Был осуществлен переход на аппаратно-независимую систему единиц измерения (вместо экранных пикселов), обеспечивший одинаковый внешний вид приложения на экране с любым разрешением.
Не последнее место в списке преимуществ технологии WPF занимает удобство разработки WPF-приложений, которое обеспечивается средствами среды программирования Microsoft Visual Studio (версии 2008–2015). Традиционные методы визуальной разработки интерфейса, имевшиеся уже в технологии Windows Forms, были дополнены методами, основанными на непосредственном редактировании xaml-файлов, определяющих макет графического приложения. Подобное редактирование (как и редактирование программного кода) существенно упрощается благодаря контекстным подсказкам, автоматической проверке синтаксиса, средствам автозавершения и другим средствам, встроенным в редакторы кода и xaml-файлов.
Учитывая перечисленные выше особенности системы WPF и принимая во внимание широкую распространенность технологий и языков программирования, основанных на платформе .NET, представляется вполне оправданным применение данной системы в качестве базовой при изучении приемов и методов разработки пользовательского интерфейса в рамках соответствующего университетского курса.
При этом, однако, возникают две проблемы. Первая обусловлена сложностью системы WPF и обширностью ее средств, которые невозможно охватить в рамках одного курса. Вторая проблема связана с недостатком учебной литературы. Если ограничиться изданиями, переведенными на русский язык, то можно отметить лишь [7–9] (при включении в рассмотрение английских книг их общее количество увеличится лишь в 2–3 раза). Из этих изданий на роль учебника может отчасти претендовать только книга Чарльза Петцольда [7], написанная с большим методическим мастерством (что характерно для всех работ этого автора) и содержащая много примеров законченных программ. Две другие книги являются, скорее, справочниками, включающими небольшие фрагменты иллюстративного кода.
Предлагаемая книга представляет собой попытку решения обеих проблем. С одной стороны, она посвящена лишь основным возможностям технологии WPF, которые вполне можно освоить за семестровый курс, а с другой – излагает материал в «практической» форме, упрощающей его усвоение. Изложение построено в виде подробного описания ряда законченных программных проектов, каждый из которых посвящен одному из аспектов технологии WPF. Многочисленные комментарии содержат дополнительные сведения, связанные с изучаемыми возможностями. Во многих случаях приводится сравнение рассматриваемых средств WPF с аналогичными средствами библиотеки Windows Forms. В процессе разработки проектов авторы намеренно допускают типичные ошибки, характерные для начинающих разработчиков интерфейсов, подробно объясняют их причины и приводят способы исправления. Подобный вариант изложения учебного материала «на примерах» ранее с успехом применялся авторами при чтении ими курсов по разработке пользовательских интерфейсов с использованием библиотеки VCL системы программирования Borland Delphi [1, 2] и предшественницы WPF – библиотеки Windows Forms платформы .NET [3].
Учебник состоит из описаний 19 проектов. Его содержимое можно разбить на три части. В первой части рассматриваются базовые возможности библиотеки WPF: создание проекта для WPF-приложения, работа с xaml-файлами, использование группирующих компонентов, управление программой посредством обработчиков событий (проект EVENTS), приемы работы с окнами в WPF-приложении, организация взаимодействия между окнами, особенности диалоговых окон (проект WINDOWS), совместное использование обработчиков событий, события клавиатуры (проект CALC), таймеры в WPF-приложении (проект CLOCK), возможности полей ввода, организация проверки правильности введенных данных (проект TEXTBOXES), события мыши (проект MOUSE), механизм перетаскивания Drag & Drop (проект ZOO), работа с курсорами и иконками, создание ресурсов приложения, совместное использование средств WPF и Windows Forms (проект CURSORS).
Вторая часть содержит описание разработки одного большого проекта TEXTEDIT, разбитое на 5 этапов (версий проекта). В ней основное внимание уделяется особенностям тех стандартных интерфейсных элементов, без которых не обходится практически ни одно приложение: меню и различных видов его команд (версии 1 и 2), контекстных меню (версия 3), панели инструментов (версия 4), статусной панели (версия 5). Кроме того, в версии 1 подробно рассматриваются особенности организации работы с файлами (открытие, сохранение, контроль за сохранением внесенных изменений), а также описываются приемы работы с командами WPF; в версии 2 рассказывается о том, как создавать новые команды WPF; в версии 3 рассматриваются особенности реализации команд редактирования; а в версиях 4 и 5 особое внимание уделяется различным аспектам механизма привязки компонентов и, кроме того, описываются действия по созданию новых свойств зависимости.
Третья часть содержит проекты, связанные с дополнительными возможностями WPF – работа с цветами и кистями и использование компонентов TrackBar (проект COLORS), работа со списками и использование стилей при определении макета приложения (проект LISTBOXES), работа с флажками и наборами флажков (проект CHECKBOXES), работа с иерархическими списками, реализация дерева каталогов и списка файлов, использование компонентов GridSplitter и Image, применение реестра Windows для хранения настроек приложения (проект IMGVIEW), работа с табличными списками и использование заставок и градиентных кистей (проект TRIGFUNC), создание компонентов во время выполнения программы (проект HTOWERS).
Завершает книгу раздел с 48 учебными заданиями, разбитыми на 5 групп. Каждая группа содержит однотипные задания; первая группа включает задания на организацию взаимодействия между окнами приложения, вторая – на синхронизацию компонентов и совместное использование обработчиков событий, третья – на реализацию режима перетаскивания Drag & Drop, четвертая – на создание программ, управляемых таймером, пятая – на использование стандартных диалоговых окон и работу с реестром. Большое количество заданий позволяет легко формировать из них наборы индивидуальных заданий одинакового уровня сложности.
За рамками настоящей книги осталось большинство возможностей библиотеки WPF, связанных с ее графической подсистемой, поскольку детальное изучение этих возможностей более естественно отнести к курсу по компьютерной графике.
Предполагается, что читатель книги уже изучил базовый курс по программированию. Знание основ языка C#, а также языка XML, является желательным, но не обязательным, так как ознакомиться с конструкциями этих языков можно в процессе чтения книги. Тем не менее полезной может оказаться книга [5], освещающая практически все аспекты языка C# и стандартных библиотек платформы .NET, а также книга [4], содержащая описание основных типов стандартной библиотеки (в том числе классов, связанных с обработкой строк и файлов), объектной модели языка C# и технологии LINQ, в том числе интерфейса LINQ to XML.
В качестве среды программирования используется Microsoft Visual Studio 2015, однако все проекты можно реализовать и в более ранних версиях этой среды (начиная с версии 2008). Применение новых возможностей языка C#, появившихся в его версии 6.0 и доступных только в Visual Studio версий 2015 и выше, всегда особо отмечается и сопровождается альтернативными вариантами кода для предыдущих версий.
Соглашения по оформлению фрагментов программного кода и xaml-файлов приводятся в первом проекте EVENTS. В нем же описываются основные действия по созданию и редактированию проекта для WPF-приложения.
1. События: EVENTS
Рис. 1. Окно приложения EVENTS
1.1. Создание проекта для WPF-приложения
Для того чтобы создать проект в среде программирования Visual Studio, выполните команду File | New | Project (Ctrl+Shift+N), в появившемся окне New Project выберите в левой части вариант Visual C#, а в правой части – вариант WPF Application, в поле ввода Name укажите имя проекта (в нашем случае EVENTS), а в поле ввода Location укажите каталог, в котором будет создан каталог проекта. Желательно снять флажок Create directory for solution, чтобы не создавался промежуточный каталог для решения (каталог для решения удобно использовать в ситуации, когда решение содержит несколько проектов; в нашем случае решение всегда будет содержать единственный проект). После указания всех настроек нажмите кнопку «OK».
В результате будет создан каталог EVENTS, содержащий все файлы одноименного проекта, в том числе файл решения EVENTS.sln, файл проекта EVENTS.csproj, а также файлы для двух основных классов проекта, созданных автоматически: класса MainWindow, представляющего главное окно программы, и класса App, обеспечивающего запуск программы, в ходе которого создается и отображается на экране экземпляр главного окна.
Для каждого класса создаются два файла: с расширением xaml, котрый содержит часть определения класса в специальном формате, и с расширением cs (перед которым тоже содержится текст xaml), содержащий часть определения класса на языке C#. Файл с расширением xaml (xaml-файл) имеет формат XML (eXtensible Markup Language – расширяемый язык разметки). Аббревиатура XAML (произносится «зэмл» или «замл») означает, что используется специализированный вариант языка XML: eXtensible Application Markup Language – расширяемый язык разметки для приложений.
Приведем содержимое файлов, связанных с классом App и созданных в Visual Studio 2015.
App.xaml:
App.xaml.cs:
Анализ этих файлов показывает, что класс App наследуется от стандартного класса Application, а также что в cs-файле никакой новой функциональности в класс App не добавляется (обратите внимание на то, что при определении класса App в cs-файле указывается модификатор partial, означающий, что часть определения этого класса содержится в другом файле). Созданный класс (как и другие классы проекта, создаваемые автоматически) связывается с пространством имен EVENTS, совпадающим с именем проекта.
Перед анализом содержимого xaml-файла следует предварительно описать основные правила, по которым формируется любой XML-файл. Подобные файлы состоят из иерархического набора вложенных друг в друга именованных XML-элементов, причем каждый элемент может иметь любое количество XML-атрибутов и дочерних элементов. Элементы оформляются в виде тегов; открывающий тег элемента имеет вид <имя_элемента список_атрибутов>, а закрывающий тег – имя_элемента>. Между этими тегами располагается содержимое элемента, которое может представлять собой обычный текст и/или другие (дочерние) элементы (а также другие XML-узлы, которые мы не будем обсуждать, так как в xaml-файле они не используются). Число уровней вложенности элементов может быть любым. Если элемент не имеет содержимого, то он может представляться в виде одного комбинированного тега вида <имя_элемента список_атрибутов />. Атрибуты в списке определяются следующим образом: имя_атрибута="значение_атрибута"; значение обязательно заключается в кавычки (одинарные или двойные). Все атрибуты одного элемента должны иметь различные имена, в то время как его дочерние элементы могут иметь совпадающие имена. Регистр в именах учитывается; имена как атрибутов, так и элементов могут содержать только буквы, цифры, символы «.» (точка), «-» (дефис) и «_» (подчеркивание) и начинаться либо с буквы, либо с символа подчеркивания. Пробелы в именах не допускаются. Перед именами элементов и атрибутов могут указываться префиксы пространств имен, отделяемые от собственно имени двоеточием (в файле App.xaml имеются два таких атрибута: xmlns:x и xmlns:local). Любой XML-файл должен содержать единственный XML-элемент верхнего уровня, называемый корневым элементом (в файле App.xaml это элемент Application).
В той части определения класса App, которая размещается в xaml-файле, содержится единственная, но очень важная настройка – указание на класс, экземпляр которого будет создан при запуске программы. Это атрибут StartupUri элемента Application, его значение равно MainWindow.xaml. Фактически данный атрибут является свойством класса Application. Как и другие свойства, его можно настроить либо непосредственно в тексте xaml-файла, либо в окне свойств Properties, которое отображает доступные для редактирования свойства текущего объекта из xaml-файла (если в редакторе отображается не xaml-, а cs-файл, то окно Properties является пустым).
При указании или изменении свойств в xaml-файле очень помогает предусмотренная в редакторе xaml-файлов возможность контекстной подсказки при выборе значений свойств. Окно Properties удобно в том отношении, что позволяет просмотреть все доступные свойства текущего объекта. В xaml-файле отображаются только те свойства, значения которых отличаются от значений по умолчанию для данного объекта. Чтобы добавить в xaml-файл новое свойство, достаточно в окне Properties указать для данного свойства значение, отличное от значения по умолчанию.
В процессе компиляции программы все xaml-файлы конвертируются в специальный двоичный формат и затем обрабатываются совместно с cs-файлами проекта.
Класс App обычно не требуется редактировать. По этой причине после создания проекта в редактор не загружаются файлы, связанные с классом App.
Комментарий
На протяжении всей книги мы будем придерживаться следующих соглашений об отступах в текстах xaml- и cs-файлов. В xaml-файле каждый вложенный элемент набирается с отступом в 2 пробела относительно родительского элемента (причина столь небольшого отступа заключается в том, что глубина вложенности элементов в xaml-файлах может быть достаточно большой); если список атрибутов в открывающем теге элемента не умещается в одной строке, то он переносится на следующую строку с отступом в 4 пробела относительно начала открывающего тега. Для cs-файла ситуация обратная: вложенные конструкции набираются с отступом в 4 пробела (как в редакторе кода среды Visual Studio), а при переносе длинного оператора на новую строку используется отступ в 2 пробела.
Приведем файлы, связанные с классом MainWindow; именно эти файлы автоматически загружаются в редактор после создания (или открытия) проекта.
MainWindow.xaml:
MainWindow.xaml.cs:
В дальнейшем при выводе текста xaml-файлов мы не будем указывать атрибуты корневого элемента, предшествующие атрибуту Title, поскольку они генерируются автоматически и не требуют изменения.
Одновременно с отображением xaml-файла для класса окна на экране выводится окно дизайнера – визуального редактора. Следует заметить, что при разработке WPF-приложений визуальный редактор используется не так активно, как при разработке приложений, использующих библиотеку Windows Forms. Это связано с тем, что относительное расположение компонентов в окне WPF-приложения обычно не определяется явным образом, с указанием абсолютных оконных координат, а вычисляется по специальным правилам, связанным с особенностями тех или иных группирующих компонентов (называемых также панелями). Таким образом, окно дизайнера используется преимущественно для того, чтобы быстро определить, как те или иные изменения, внесенные в xaml-файл или сделанные с помощью окна свойств, повлияют на внешний вид окна. С помощью выпадающего списка можно настраивать масштаб для окна дизайнера.
Обратите внимание на то, что по умолчанию в окно приложения уже включен группирующий компонент Grid – наиболее универсальный из группирующих компонентов, позволяющий размещать свои дочерние компоненты в нескольких строках и столбцах. Кроме того, для класса MainWindow определены три свойства: Title, Height и Width. Гораздо большее число свойств приведено в окне Properties (как уже было отмечено выше, их отсутствие в xaml-файле объясняется тем, что данный файл содержит только свойства, значения которых отличаются от значений по умолчанию). При просмотре списка свойств в окне Properties можно использовать либо режим, при котором «родственные» свойства объединяются в группы, либо режим, при котором свойства располагаются в алфавитном порядке. Кроме того, с помощью поля ввода, расположенного над списком свойств, можно выполнять фильтрацию этого списка, отображая только те свойства, в именах которых содержится указанная строка. Например, после ввода в это поле текста Height в списке свойств останутся лишь три свойства: Height, MaxHeight и MinHeight.
В файле MainWindow.xaml.cs содержится частичное определение класса MainWindow, включающее конструктор без параметров, в котором вызывается метод InitializeComponent, обеспечивающий начальную инициализацию всех компонентов окна. Все действия с компонентами можно выполнять только после их начальной инициализации, поэтому пользовательский код добавляется в конструктор после вызова данного метода.
Комментарий
Большинство XML-атрибутов в xaml-файле относятся либо к атрибутам свойств (и определяют соответствующие свойства объектов), либо к атрибутам событий (и позволяют связать события с методами-обработчиками). XML-элементы тоже можно разбить на две категории: это элементы-объекты, имена которых совпадают с типом определяемого объекта, и элементы-свойства, имеющие составные имена вида тип.свойство (элементы-свойства используются в ситуации, когда свойство нельзя определить с помощью единственного атрибута).
Кроме того, для каждого типа компонентов WPF определено особое свойство, которое можно задать в xaml-файле, указав его в виде одного или нескольких дочерних элементов-объектов (примером такого свойства является свойство Content; в частности, в приведенном выше файле MainWindow.xaml свойство Content окна Window равно компоненту Grid). В подобной ситуации имя свойства вообще не указывается. Несколько дочерних элементов-объектов можно указывать, если определяемое свойство является свойством-коллекцией (примером такого свойства является свойство Children, имеющееся у всех группирующих компонентов-панелей, например, компонента Grid или используемого в следующем пункте компонента Canvas).
1.2. Добавление компонентов и настройка их свойств
Разрабатываемое нами приложение отличается от традиционных WPF-приложений тем, что мы хотим произвольным образом перемещать отдельные компоненты в пределах окна. В подобной ситуации вместо группирующего компонента Grid удобнее пользоваться компонентом Canvas. Поэтому нам необходимо изменить «внешний» компонент окна и, кроме того, добавить на новый внешний компонент кнопку Button.
Эти действия можно выполнить двумя способами: с помощью окна дизайнера, удалив в нем лишние компоненты и добавив новые путем их перетаскивания с панели компонентов Toolbox, и с помощью непосредственного редактирования xaml-файла.
Опишем первый способ.
Вначале необходимо выделить в окне дизайнера компонент Grid, щелкнув на нем мышью. То, что выделен именно компонент Grid, можно проверить по тексту xaml-файла (в котором также будет выделен элемент
Затем необходимо добавить в окно компонент Canvas. Для этого надо развернуть панель Toolbox, которая обычно располагается у левой границы окна Visual Studio в свернутом состоянии. Если данная панель отсутствует, то ее можно отобразить с помощью команды меню View | Toolbox (Ctrl+W, X). Для быстрого поиска нужного компонента на панели Toolbox достаточно ввести начальную часть его имени в поле ввода, расположенное в верхней части панели. Например, в нашем случае достаточно ввести текст Can, чтобы на панели отобразился единственный компонент Canvas. Можно обойтись и без быстрого поиска, просто выбрав данный компонент в списке All WPF Controls. После выбора компонента Canvas достаточно перетащить его в окно дизайнера. В результате компонент Canvas появится в окне и соответствующий текст будет добавлен в xaml-файл (при этом будет восстановлено представление элемента Window в xaml-файле в виде двух тегов – открывающего
Здесь и в дальнейшем мы часто будем опускать фрагменты xaml-файла, оставшиеся неизменными, указывая вместо них символ многоточия «…». Измененную часть xaml-файла мы выделили полужирным шрифтом.
Примерный вид окна дизайнера приведен на рис. 2.
Рис. 2. Окно дизайнера после добавления компонента Canvas
Разумеется, нам не требуется такое размещение компонента Canvas. Необходимо, чтобы он занимал всю клиентскую область окна. Для того чтобы добиться этого, достаточно просто удалить в xaml-файле все атрибуты элемента Canvas (удаляемые фрагменты будем изображать перечеркнутыми):
Новый вид окна дизайнера приведен на рис. 3.
Рис. 3. Окно дизайнера после удаления атрибутов компонента Canvas
Как правило, после добавления в окно какого-либо компонента путем его перетаскивания из панели Toolbox, всегда требуется выполнить действия, связанные с удалением «лишних» атрибутов.
Теперь добавим на компонент Canvas кнопку Button, зацепив ее мышью на панели Toolbox и перетащив в окно. После появления кнопки в окне следует перетащить ее в левый верхний угол окна (при подобном перетаскивании кнопка будет автоматически «притянута» к области, расположенной на расстоянии 10 единиц от левой и верхней границы клиентской области окна, – рис. 4).