#008 Эффекты в стиле Windows Vista - BitmapEffects и WPF
Avalon содержмт множество средств оформления. Одними из таких средств являются растровые эффекты (Bitmap Effects). Они применимы практически к любому элементу WPF и позволяют легко и быстро создавать свечение, размытие, выдавливание и другие эффекты. Если применить фантазию, можно приспособить эти эффекты для создания чего-то менее тривиального - например окна в стиле Windows Vista. Этим мы и займемся сегодня.
Создайте новый проект типа WinFX Windows Application и назовите его "MyVistaStyle".
Зададим свойства для формы, чтобы сделать ее прозрачной прямо в XAML-коде:
WindowStyle ="None" AllowsTransparency="True" Background ="Transparent"
Далее разместим на форме прямоугольник - он и будет отвечать за внешний вид нашего окна:
<Rectangle HorizontalAlignment ="Stretch" VerticalAlignment ="Stretch"
Opacity ="0.9"
Stroke ="Gray" Fill ="WhiteSmoke"
StrokeThickness ="1"
RadiusX ="5" RadiusY ="5"/>
Увеличьте значения свойств Width и Height нашего окна Height="480" Width="640". Теперь давайте создадим загаловок окна. Для этого будем использовать хорошо знакомый вам по обычному программированию для .NET 2.0 - Label. Привожу полный код для сверки:
<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyVistaStyle" Height="480" Width="640"
WindowStyle ="None" AllowsTransparency="True" Background ="Transparent"
>
<Grid>
<Rectangle HorizontalAlignment ="Stretch" VerticalAlignment ="Stretch"
Opacity ="0.9"
Stroke ="Gray" Fill ="WhiteSmoke"
StrokeThickness ="1"
RadiusX ="5" RadiusY ="5" />
<Label FontFamily ="SgoeUI" FontSize ="15" Foreground ="Black"
Content ="Мое приложение в стиле Vista"
HorizontalAlignment ="Stretch" VerticalAlignment ="Top"
Margin ="10,10,0,0"
Name="MyCaptionLabel" />
</Grid>
</Window>
ndow>[/code]
Давайте посмотрим на результат:
Обратите внимание, что я задал для элемента Label имя MyCaptionLabel - я сделал это, чтобы дать возможность пользователю перетаскивать окно не за любою точку, а только за заголовок. Для этого добавьте обработчик события MouseLeftButtonDown для нашей надписи MyCaptionLabel и внесите в его тело код: Me.DragMove()
Запустите проект и убедитесь, окно теперь легко перемещается за заголовок. Теперь давайте займемся кнопками сворачивания, разворачивания и закрытия окна. Вы можете подготовить сами их изобржение в формате png, однако для того, чтобы вы легко выполнили задание этой статьи, я разместил небольшой архив с нужными изображениями
Скачайте его и добавьте все содержащиеся в архиве изображения к ресурсам проекта (если вы затрудняетесь - прочтите статью № 006 ). Теперь давайте разместим их на форме:
[code]<Image Source ="btnClose.png" Width ="44" Margin ="0,2,2,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"/>
<Image Source ="btnMaximize.png" Width ="26" Margin ="0,2,46,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"/>
<Image Source ="btnMinimize.png" Width ="26" Margin ="0,2,72,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"/>[/code]
Посмотрите на результат:
Теперь придадим кнопкам функциональность. Для этого задайте им следующие имена: для первой Name="myBtnClose" для второй Name="myBtnMax" и для третьей Name="myBtnMin". Перекомпилируйте проект и задайте для всех трех элементов обработчики уже давно знакомого события MouseLeftButtonDown. Давайте теперь внесем код (примеры на VB):
[code]Для первой: Me.Close()
Для второй:
If Me.WindowState = Windows.WindowState.Maximized Then
Me.WindowState = Windows.WindowState.Normal
Else
Me.WindowState = Windows.WindowState.Maximized
End If
Для третьей: Me.WindowState = Windows.WindowState.Minimized[/code]
Запустите проект и убедитесь, что кнопки выполняют нужные функции.
Теперь давайте займемся оформлением уже подготовленных элементов! Сделаем наши кнопки похожими на кнопки окон Windows Vista. Для этого применим растровые эффекты. Добавьте такой код к определению изображения MyBtnClose:
[code]<Image Source ="btnClose.png" Width ="44" Margin ="0,2,2,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"
Name="myBtnClose">
<Image.BitmapEffect>
<OuterGlowBitmapEffect x:Name="myOuterGlowBitMapEffect"
GlowColor="Red" GlowSize="0" Noise="0"
Opacity="0.5" />
</Image.BitmapEffect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect"
Storyboard.TargetProperty="GlowSize"
To="8" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect"
Storyboard.TargetProperty="GlowSize"
To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>[/code]
Давайте разберемся, что и как тут работает. Прежде всего секция <Image.BitmapEffect> открывает доступ к различным растровым эффектам. Мы применим эффект внутреннего свечения при помощи определителя <OuterGlowBitmapEffect>. Именно этот эффект сделает нашу кнопку похожей на кнопку из Vista. Для того, чтобы кнопка "светилась изнутри" при наведении мыши и затухала, когда мышь покидает область кнопки, мы используем анимацию. Здесь все как обычно - свойство Triggers и две доски <Storyboard> - одна для события MouseEnter а другая для MouseLeave. Подробно объяснять не буду, так как мы рассматривали подобные анимации уже неоднократно. Скажу лишь, что анимации мы подвергаем свойство GlowSize нашего BitmapEffect-а.
Добавьте практически тотже самый код для двух других кнопок. Измените лишь свойство GlowColor на DodgerBlue для эффектов свечения, так как наши оставшиеся кнопки должны светиться не красным, а голубым цветом. Также не забудьте изменить имя для второго и третьего OuterGlowBitmapEffect с myOuterGlowBitMapEffect на какие-нибудь другие, чтобы не вызвать конфликта имен и подредактируйте ссылающиеся на них свойста TargetName. Думаю, вы справитесь без проблем.
Запускаем и смотрим на результат:
Обратите внимание, что кнопки не только светяться, но и обеспечивают плавные переходы, при движении мышью. Есть только один недостаток - наше свечение ограничивается верхней границей окна. Это легко исправить!
Дело в том, что наш прямоугольник, формирующий внешний вид окна, плотно прилегает к форме. Немного отредактируем свойства прямоугольника, чтобы он оставил небольшую зону перед верхней границей. Новые свойства прямоугольника:
[code]Margin ="0,7,0,0"[/code]
Запускаем:
Уупс.. забыли про кнопки. Меняем свойства Margin для всех трех кнопок на такие:
[code]Для первой: Margin ="0,9,2,0"
Для второй: Margin ="0,9,46,0"
Для третьей: Margin ="0,9,72,0"[/code]
Проверяем:
Посмотрите, какого замечательного эффекта мы смогли добиться!
В завершении статьи традиционно привожу полный листинг:
VB-листинг:
[code]Partial Public Class Window1
Inherits Window
Public Sub New()
InitializeComponent()
End Sub
Private Sub MyCaptionLabel_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs)_
Handles MyCaptionLabel.MouseLeftButtonDown
Me.DragMove()
End Sub
Private Sub myBtnClose_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles myBtnClose.MouseLeftButtonDown
Me.Close()
End Sub
Private Sub myBtnMax_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles myBtnMax.MouseLeftButtonDown
If Me.WindowState = Windows.WindowState.Maximized Then
Me.WindowState = Windows.WindowState.Normal
Else
Me.WindowState = Windows.WindowState.Maximized
End If
End Sub
Private Sub myBtnMin_MouseLeftButtonDown(ByVal sender As Object, _
ByVal e As System.Windows.Input.MouseButtonEventArgs) _
Handles myBtnMin.MouseLeftButtonDown
Me.WindowState = Windows.WindowState.Minimized
End Sub
End Class[/code]
XAML-листинг:
[code]<Window x:Class="Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MyVistaStyle" Height="480" Width="640"
WindowStyle ="None" AllowsTransparency="True" Background ="Transparent"
>
<Grid>
<Rectangle Margin ="0,7,0,0" HorizontalAlignment ="Stretch"
VerticalAlignment ="Stretch" Opacity ="0.9"
Stroke ="Gray" Fill ="WhiteSmoke"
StrokeThickness ="1" RadiusX ="5" RadiusY ="5" />
<Label FontFamily ="SgoeUI" FontSize ="15" Foreground ="#FFFFFF"
Content ="Мое приложение в стиле Vista"
HorizontalAlignment ="Stretch" VerticalAlignment ="Top"
Margin ="10,10,0,0"
>
<Label.BitmapEffect >
<BlurBitmapEffect Radius="10" KernelType="Gaussian" />
</Label.BitmapEffect>
</Label>
<Label FontFamily ="SgoeUI" FontSize ="15" Foreground ="Black"
Content ="Мое приложение в стиле Vista"
HorizontalAlignment ="Stretch" VerticalAlignment ="Top"
Margin ="10,10,0,0"
Name="MyCaptionLabel" />
<Image Source ="btnClose.png" Width ="44" Margin ="0,9,2,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"
Name="myBtnClose">
<Image.BitmapEffect>
<OuterGlowBitmapEffect x:Name="myOuterGlowBitMapEffect"
GlowColor="Red" GlowSize="0" Noise="0"
Opacity="0.5" />
</Image.BitmapEffect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect"
Storyboard.TargetProperty="GlowSize"
To="8" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect"
Storyboard.TargetProperty="GlowSize"
To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
<Image Source ="btnMaximize.png" Width ="26" Margin ="0,9,46,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"
Name="myBtnMax">
<Image.BitmapEffect>
<OuterGlowBitmapEffect x:Name="myOuterGlowBitMapEffect2"
GlowColor="DodgerBlue" GlowSize="0"
Noise="0"
Opacity="0.5" />
</Image.BitmapEffect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect2"
Storyboard.TargetProperty="GlowSize"
To="8" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect2"
Storyboard.TargetProperty="GlowSize"
To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image>
<Image Source ="btnMinimize.png" Width ="26" Margin ="0,9,72,0"
HorizontalAlignment ="Right" VerticalAlignment ="Top"
Name ="myBtnMin">
<Image.BitmapEffect>
<OuterGlowBitmapEffect x:Name="myOuterGlowBitMapEffect3"
GlowColor="DodgerBlue" GlowSize="0"
Noise="0"
Opacity="0.5" />
</Image.BitmapEffect>
<Image.Triggers>
<EventTrigger RoutedEvent="Mouse.MouseEnter">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect3"
Storyboard.TargetProperty="GlowSize"
To="8" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="Mouse.MouseLeave">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="myOuterGlowBitMapEffect3"
Storyboard.TargetProperty="GlowSize"
To="0" Duration="0:0:0.2" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Image.Triggers>
</Image >
</Grid>
</Window>[/code]
Июль, 2006
Комментарии
А можно совет дам ненавязчивый?
Обычно принято к статье прилагать весь код в виде законченого компилируемого и запускаемого solution...
При "обратных" анимациях можно не указывать To (тогда анимация будет от текущего значения к исходному).
По теме
- Создаем контекстно-зависимое WPF-приложение
- #024 – Знакомство с WPF/E
- #023 – Введение в WPF, reloaded…
- #022 Введение в Microsoft Interactive Designer RC1
- #021 Применение 3D в WPF - Часть 2
- #020 Применение 3D в WPF - Часть 1
- #019 Введение в возможности 3D на WPF
- #018 Размещение контрола NET 2.0 на форме WPF
- #017 Первое Web-приложение / Подробнее о Grid / Элемент Frame
- #016 EXE, XBAP, XAML? - Все равно!