Хабрахабр. Я думаю что каждый (или почти каждый), из тех, кто приходит в WPF из Win. Forms, поначалу испытывает растерянность по поводу функционала стандартных контролов. Даже сигнатура обычных методов либо полностью совпадает, либо претерпела незначительную трансформацию (ну, например, свойство Enabled получило приставку Is). Настроек у контролов много, от параметров визуализации рябит в глазах.
Сглаживать растровые изображения не нужно. Либо используйте изображение в нативном размере (вот тут много хороших советов), . Тень от кнопки but1 в XAML выглядит так. Есть словарь ресурсов, там описаны основные стили. Тень приведена на скриншоте.
Компоненты поддерживают визуальные стили и имеет современный стиль. В xaml файле контрола ApplicationToolbar создадим команду, привяжем к ней данный метод и. А именно переопределение стиля для NavigationWindow. Вот небольшой скриншот сравнения данных программ. Избавляемся от StandardStyles.xaml в Windows 8.1.
Ну неужели у кнопки нет свойства Image? Вы ведь шутите, правда? Все дело в том, что у WPF (точнее у XAML) совершенно иная идеология организации интерфейса.
Базовые контролы представляют лишь базовый (простите за тавтологию) функционал. Простота базовых контролов компенсируется мощными механизмами шаблонов и стилей. Существуют и сторонние библиотеки компонентов, но они, чаще всего, либо бесполезны, либо безнадежно устарели, либо сильно платные.
Не так давно я в очередной раз столкнулся с необходимостью решения этой очень простой (казалось бы) задачи. Я истерзал весь гугл запросами типа “XAML button with image” “WPF button image text” и т. Среди десятков просмотренных результатов нашлись очевидные как очевидные (и при этом неудобные) пути решения, так и более изощренные.
Небольшое отступление номер 1. После первых же экспериментов стало очевидно, что XAML и иконки в виде png – вещи несовместимые.
Не буду долго растекаться почему так – литературы на эту тему хватает, скажу только что в итоге получается и некрасиво, и неудобно, и нефункционально. Картинки размытые, наложенные эффекты и анимация выглядят удручающе и т. Лучшее из того, что я нашел – Sync. Fusion Metro Studio 2 (не реклама).
Это бесплатный продукт, в котором есть 1. XAML. Результат получается в виде сложного элемента, из которого достаточно скопировать лишь Path, который описывает саму геометрию иконки. С этим элементом я поступаю так – в проект добавляю Resource. Dictionary с именем Icons. Resource. Dictionary. Есть кнопка, у нее есть иконка и текст. Тех, кто поверит в то, что всё так просто и попытается организовать таким подходом красивый и стильный интерфейс, очень скоро ждёт разочарование.
И вот почему: простая настройка стиля приводит к распуханию XAML за счет дублирования описания параметров элементов прямо в коде формы. Простое изменение типа «а давайте- ка покрасим текст на кнопках в зеленый цвет» превращается в утомительный копипаст и еще большее распухание формы.
Второй очевидный способ — наследование от Button. А давайте напишем «свою кнопку с блекджеком и сами знаете с чем еще»? Не буду останавливаться на подробностях реализации (внизу будет ссылка на источники, там можно будет почитать), но опишу минусы – содержимое кнопки придется задавать из конструктора наследника, на C#. Отсюда получаем массу очевидных и неочевидных проблем, не говоря уже о том, что это не очень хорошо пахнет. Третий очевидный способ — создадим User. Control. В User. Control создадим Dependency.
Property, через которое будем задавать иконку для Content. Control, который лежит в кнопке. Этот способ по праву заслуживает медаль за максимальную корявость. Он наследует почти все недостатки предыдущих способов, и добавляет множество собственных. В коде формы мы получаем User.
Control, но теряем кнопку. Теряем вместе со всеми свойствами и событиями. Автор идеи предлагает вытащить все, что было потеряно, через те самые Dependency.
Property, в общем вы поняли. Становится непонятно за что же мы боролись. Четвёртый способ — Attached. Property. Этот способ я отношу к неочевидным и изощренным. В оригинале статьи автор предлагает задавать картинку через Attached.
Property. Забегая вперед скажу, что именно этот способ я и выбрал для использования в своем продукте и именно его я опишу максимально подробно. Он не лишен некоторых недостатков на этапе разработки (опишу ниже), но всё же мне он понравился больше остальных. В оригинале автор использовал иконку в виде картинки png, я же модифицировал способ для использования векторной иконки и добавил плюшек. Итак, совсем немного теории. Что же такое это самое Attached. Property. Каждый XAML разработчик сталкивался с Attached свойствами когда, например, задавал контролу свойство Grid. Column. Можно зарегистрировать свойство, значение которого можно задать любому Dependency.
Object. Выглядит это примерно так (пример из MSDN): public class Aquarium. Object. . В результате любому Dependency. Object, например тому же Button, можно задать его значение: < Button Aquarium. Object. Is. Bubble. Source=. При получении значения, соответственно, попадаем в метод Get.
Is. Bubble. Source. Это все происходит автоматически, достаточно лишь написать методы с именами Set и Get, остальное – дело платформы. Несмотря на то, что написано не так уж и мало кода, самому Button от такой операции ни жарко ни холодно – он просто становится хранилищем обособленного значения, которое можно задавать и спрашивать. Конечно, можно реализовать в методах Set.
Is. Bubble. Source и Get. Is. Bubble. Source хитрую логику, которая будет приводить element к Button, доставать из него содержимое, и производить с содержимым различные операции, но это опять плохо пахнет, делать так не нужно. Приступаем к практической части. Небольшое отступление 2. В оригинале автор использует имя класса Eye. Candy и namespace проекта, но это слишком длинно и я надеюсь, что мне простят сокращение – namespace Ext и имя класса E.
В проект WPF добавляем следующий класс: using System. Windows. using System. Windows. Controls. System. Windows. Media. Мы зарегистрировали Attached свойство Icon типа Framework. Element со значением по умолчанию равным null.
Теперь создадим шаблон для нашей кнопки. Я не буду останавливаться на объяснения «что такое шаблоны и как они работают» – если вдруг кому- то это неизвестно – информации в сети очень много. Итак, добавляем в наш проект Resource. Dictionary с именем Styles. В этом Resource. Dictionary добавим следующий код: < Resource.
Dictionary. xmlns=. В этом шаблоне задается обычное оформление для кнопки WPF, но переопределяется ее содержимое. В качестве содержимого выступает Stack. Panel, в котором лежат Content. Control и Text. Block, т.
Кроме этого в шаблоне я задал следующее поведение для иконки – если для кнопки задано Is. Enabled == False, то иконка получает прозрачность 5. Добавим на нашу форму 4 простые кнопки. Назначим каждой кнопке свой текст, например вот так: Content=«Кнопка 1». Запускаем приложение.
Идентичные иконки на каждой кнопке приложения – это не то, чего мы добиваемся, и именно здесь мы пускаем в дело наше секретное оружие – класс Ext. E и механизм Attached. Property. Итак, идем в наш ресурсный файл Styles. Ext=. После этого остается добавить код, устанавливающий значение свойства Ext. E. Icon в саму кнопку. Делается это в коде формы, на которой создается кнопка.< Button. Ext: E. Icon=. Меняя значение Icon.
Triangle на имена других ресурсов, можно задавать различные иконки на кнопках. При этом, в отличии от первых трех способов, мы сохраняем у кнопки все ее врожденные способности к стилизации (за исключением возможности менять структуру Content, само собой). Содержимое кнопки задается не из C#, и все свойства с событиями остались на своем месте.
Пойдем немного дальше. Если мы попытаемся использовать эту кнопку в реальном проекте, то столкнемся вот с чем: Размер иконки не настраивается. Ориентация кнопки (вертикальная или горизонтальная) не настраивается. Если точнее, то все настраивается, но только в шаблоне, т.
Боролись мы не за это. Расширим класс Ext. E. Добавим туда еще два Attached. Property. Icon. Size типа double. Orientation типа Orientation.
Хотя ресурсы можно применять для хранения самых различных объектов, чаще всего в них хранятся стили. Стилем называется коллекция значений свойств, которые могут применяться к элементу. Система стилей WPF играет ту же роль, которую играет стандарт каскадных таблиц стилей (Cascading Style Sheet — CSS) в HTML- разметке. Подобно CSS, стили WPF позволяют определять общий набор характеристик форматирования и применять его повсюду в приложении для обеспечения согласованного вида.
Как и CSS, они могут работать автоматически, предназначаться для элементов конкретного типа и каскадироваться через дерево элементов. Однако стили являются более мощными, поскольку способны устанавливать любое свойство зависимости. Это означает, что их можно применять и для стандартизации не связанных с форматированием характеристик, таких как поведение какого- то элемента управления. Вдобавок стили WPF поддерживают триггеры, которые позволяют изменять стиль элемента управления при изменении какого- то свойства, и могут использовать шаблоны для переопределения встроенного внешнего вида элемента управления. Освоив стили, вы обязательно начнете включать их во все разрабатываемые WPF- приложения.
Чтобы понять, каким образом стили вписываются в приложения, следует рассмотреть простой пример. Предположим, что требуется стандартизировать используемый в окне шрифт. Простейший подход предусматривает установку свойств шрифта содержащего окна. Белки Животные Описание тут. Эти свойства, определенные в классе Control, включают Font.
Family, Font. Size, Font. Weight (для полужирного начертания), Font.
Style (для курсивного начертания) и Font. Stretch (для сжатого и разрешенного вариантов). Благодаря функции наследования значений свойств, при установке этих свойств на уровне окна все элементы внутри окна получат одинаковые значения, если только те не будут переопределены в них явным образом. Функция наследования значений свойств является лишь одной из многих дополнительных средств, которые могут предоставлять свойства зависимости. Теперь рассмотрим другую ситуацию. Предположим, что требуется заблокировать шрифт, который используется только в определенной части пользовательского интерфейса.
Если есть возможность изолировать эти элементы в специальном контейнере (например, внутри одного элемента управления Grid или Stack. Panel), можно воспользоваться в точности тем же подходом и установить свойства шрифта этого контейнера. Однако в реальности все обычно оказывается не так просто. Например, может требоваться, чтобы все кнопки получили свою гарнитуру и размер шрифта независимо от настроек шрифта, используемых другими элементами. В таком случае необходим какой- то способ определения этих деталей в одном месте и затем повторного их использования везде, где они нужны.
Ресурсы предлагают возможное, но несколько громоздкое решение. Поскольку объекта, представляющего шрифт, в WPF не существует (есть только коллекция связанных со шрифтом свойств), соответствующие ресурсы можно определять только так, как показано ниже: < Window. Resources>. < Font.
Family х: Key=. Здесь предполагается, что . NET- пространство имен System было отображено на префикс sys пространства имен XML. При установке свойств с использованием ресурса важно, чтобы типы данных в точности совпадали. Конвертеры типов, которые применяются при установке значения атрибута напрямую, здесь отсутствуют. Например, при установке атрибута Font.
Family в элементе можно использовать строку . Однако попытка установить свойство Font. Family с использованием строкового ресурса ничего подобного не произойдет, и анализатор XAML сгенерирует исключение. После определения всех необходимых ресурсов их можно использовать в элементе.
Поскольку за время жизненного цикла приложения ресурсы никогда не изменяются, имеет смысл применять статические ресурсы. Этот пример работает и позволяет устранить из разметки детали, касающиеся шрифта. Однако он также приводит к появлению двух новых проблем: Отсутствие четкого указания, что все три ресурса связаны между собой (за исключением разве что похожих имен). Это усложняет сопровождение приложения.
Затем можно создать один объект Font. Settings как ресурс и использовать его различные свойства в разметке. Тем не менее, это не устранит вторую проблему, к тому же потребует довольно много дополнительных усилий.
Стили предлагают практически идеальное решение. Как показано ниже, можно определить единственный стиль, который упаковывает все свойства, подлежащие установке: < Window. Resources>. < Style x: Key=. В этом объекте размещается коллекция Setters с пятью объектами Setter, по одному для каждого свойства, которое подлежит установке.
В каждом объекте Setter указывается имя свойства, на которое он влияет, и значение, которое он должен применять к этому свойству. Как и все ресурсы, объект стиля имеет ключевое имя, по которому его можно при необходимости извлекать из коллекции. В данном случае это ключевое имя выглядит как My. Button. Style. Стиль подключается к элементу через свойство Style (которое определено в базовом классе Framework. Element). Например, чтобы сконфигурировать кнопку для использования созданного выше стиля, необходимо указать кнопке ресурс стиля: < Button Style=. Все, что для этого понадобится — это просто извлечь стиль из ближайшей коллекции Resources с помощью уже знакомого метода Find. Resource(). Ниже приведен код, который можно применить для объекта Button по имени cmd: cmd.
Style = (Style)cmd. Find. Resource(. Например, если применить стиль My. Button. Style и явно установить свойство Font. Size, параметр Font. Size в дескрипторе кнопки переопределит этот стиль. В идеале полагаться на такое поведение не стоит — вместо этого лучше создать больше стилей, которые позволяют устанавливать максимальное количество деталей на уровне стиля.
В результате обеспечивается большая гибкость при настройке пользовательского интерфейса в будущем.