Už nějakou tu dobu jsem chtěl popsat některé postupy a vychytávky, které používáme ve svých Silverlight aplikacích. Protože jsem ale neuměl určit, co vše by v příspěvku mělo být, rozhodl jsem se, že místo jednoho většího příspěvku napíši více menších (jednodušších) příspěvků. V téhle sérii tedy budou uváděny nejrůznější tipy jak opravit, vylepšit či pozměnit standartní chování Silverlight controlů a tříd, nebo jak udělat některé mnohdy ne moc dobře popsané záležitosti. Případně také upozorním na změny v Silverlight oproti WPF.
Dnes si ukážeme použití implicitních stylů v Silverlight a nadefinujeme si dva, které budeme v aplikacích používat. Implicitní styl (Implicit style) je styl, který na rozdíl od normálního stylu nemá přiřazen své jméno (Key), ale pouze typ prvku (controlu) na který se vztahuje. Pokud takový styl začneme používat (tím, že ho zahrneme do resources aplikace), tak se aplikuje na všechny výskyty daného controlu dle typu. Tato vlastnost stylů byla od začátku ve WPF, v Silverlight jí máme k dispozici až od poslední verze 4.0.
Než se vrhneme na konkrétní styly, ještě si popíšeme nejjednodušší cestu, jak vytáhnout výchozí styl controlu. Ten pak můžeme pouze upravovat místo toho, aby jsme styl psali celý od začátku. Použijeme k tomu nástroj Expression Blend, na pracovní plochu umístíme control, kterému chceme styl měnit, a zvolíme volbu context menu Edit Template / Edit a Copy. V dialogu zvolíme umístění do nového resource dictionary souboru. Tím nám Blend vygeneruje výchozí styl controlu, který následně můžeme upravovat ať už přímo v blendu nebo pouze v XAML kódu.
Blend nám také tímto postupem do resource dictionary souboru zahrne všechny ostatní styly, šablony a resources, které jsou ve vytvořeném stylu používány, např. pokud děláme styl pro CheckBox control, je do souboru přídán i styl ValidationToolTipTemplate. Pokud chceme změnit, aby byl tento styl implicitní, odmažeme u něho x:Key atribut.
Druhou možností jak vytáhnout výchozí styl controlu je pomoci utilitky SilverlightDefaultStyleBrowser, kterou lze stáhnout zde. Po spuštění je zobrazen seznam controlů v základních Silverlight assembly, a po vybrání některého z nich lze pak zkopírovat výchozí styl controlu (neumí ovšem vytáhnou jiné používané resource). Ačkoliv je utilitka už docela stará (z doby Silverlight 2.0), lze jí pořád dobře využít např. na získání výchozího stylu ToolTip controlu, který nelze vytáhnout přes Blend (protože tam nelze Tooltip control vybrat a umístit na plochu).
Styl ToolTipu
Výchozí styl tooltipu graficky vypadá jinak než např. standardní tooltip ve Windows 7, navíc je špatně umístěn vzhledem ke kurzoru myši (zasahuje do něj). Uděláme si vlastní styl, který ToolTip všude v aplikaci změní.
<!--System.Windows.Controls.ToolTip Implicit Style-->
<Style TargetType="ToolTip" >
<Setter Property="Background">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFFFFFFF"/>
<GradientStop Color="#FFE7E7F7" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Margin" Value="0,4,0,0"/>
<Setter Property="Padding" Value="1,1,1,1"/>
<Setter Property="BorderThickness" Value="1" />
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0" />
<GradientStop Color="#FF8399A9" Offset="0.375" />
<GradientStop Color="#FF718597" Offset="0.375" />
<GradientStop Color="#FF617584" Offset="1" />
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ToolTip" xmlns:vsm="clr-namespace:System.Windows;assembly=System.Windows">
<Border x:Name="Root" Margin="{TemplateBinding Margin}" CornerRadius="3" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" Padding="{TemplateBinding Padding}" BorderBrush="{TemplateBinding BorderBrush}" >
<Border.Resources>
<Storyboard x:Key="Visible State" />
<Storyboard x:Key="Normal State" />
</Border.Resources>
<ContentPresenter Content="{TemplateBinding Content}" ContentTemplate="{TemplateBinding ContentTemplate}" Cursor="{TemplateBinding Cursor}" Margin="{TemplateBinding Padding}" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Výsledek změny můžete porovnat na tomto obrázku:
Styl CheckBoxu
CheckBox v Silverlight má ve třetím (null) stavu nestandartní vzhled, místo standartní celé výplně je pouze vodorovná čára. Uděláme styl na změnu tohoto třetího stavu CheckBoxu.
<!--System.Windows.Controls.CheckBox Implicit Style-->
<!--Change display of indeterminate state-->
<Style TargetType="CheckBox">
<Setter Property="Background" Value="#FF448DCA"/>
<Setter Property="Foreground" Value="#FF000000"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="VerticalContentAlignment" Value="Top"/>
<Setter Property="Padding" Value="4,0,0,0"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="BorderBrush">
<Setter.Value>
<LinearGradientBrush EndPoint="0.5,1" StartPoint="0.5,0">
<GradientStop Color="#FFA3AEB9" Offset="0"/>
<GradientStop Color="#FF8399A9" Offset="0.375"/>
<GradientStop Color="#FF718597" Offset="0.375"/>
<GradientStop Color="#FF617584" Offset="1"/>
</LinearGradientBrush>
</Setter.Value>
</Setter>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="CheckBox">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="16"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<vsm:VisualStateManager.VisualStateGroups>
<vsm:VisualStateGroup x:Name="CommonStates">
<vsm:VisualState x:Name="Normal"/>
<vsm:VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundOverlay"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BoxMiddleBackground"/>
<ColorAnimation Duration="0" To="#7FFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#CCFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#F2FFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Pressed">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BackgroundOverlay"/>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="BoxMiddleBackground"/>
<ColorAnimation Duration="0" To="#6BFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#C6FFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#EAFFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#F4FFFFFF" Storyboard.TargetProperty="(Shape.Fill).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Shape.Stroke).(GradientBrush.GradientStops)[3].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Shape.Stroke).(GradientBrush.GradientStops)[0].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Shape.Stroke).(GradientBrush.GradientStops)[1].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
<ColorAnimation Duration="0" To="#FF6DBDD1" Storyboard.TargetProperty="(Shape.Stroke).(GradientBrush.GradientStops)[2].(GradientStop.Color)" Storyboard.TargetName="BoxMiddle"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Disabled">
<Storyboard>
<DoubleAnimation Duration="0" To=".55" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="contentPresenter"/>
<DoubleAnimation Duration="0" To="0.55" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="DisabledVisualElement"/>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="CheckStates">
<vsm:VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="CheckIcon"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Unchecked"/>
<vsm:VisualState x:Name="Indeterminate">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="(UIElement.Opacity)" Storyboard.TargetName="IndeterminateIcon"/>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="FocusStates">
<vsm:VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation Duration="0" To="1" Storyboard.TargetProperty="Opacity" Storyboard.TargetName="ContentFocusVisualElement"/>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="Unfocused"/>
</vsm:VisualStateGroup>
<vsm:VisualStateGroup x:Name="ValidationStates">
<vsm:VisualState x:Name="Valid"/>
<vsm:VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
<vsm:VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility" Storyboard.TargetName="ValidationErrorElement">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsOpen" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean xmlns:sys="clr-namespace:System;assembly=mscorlib">True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</vsm:VisualState>
</vsm:VisualStateGroup>
</vsm:VisualStateManager.VisualStateGroups>
<Grid HorizontalAlignment="Left" VerticalAlignment="Top">
<Rectangle x:Name="Background" Fill="#FFFFFFFF" Height="14" Margin="1" RadiusY="1" RadiusX="1" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{TemplateBinding BorderThickness}" Width="14"/>
<Rectangle x:Name="BackgroundOverlay" Fill="#FFC4DBEE" Height="14" Margin="1" Opacity="0" RadiusY="1" RadiusX="1" Stroke="#00000000" StrokeThickness="1" Width="14"/>
<Rectangle x:Name="BoxMiddleBackground" Fill="{TemplateBinding Background}" Height="10" RadiusY="1" RadiusX="1" Stroke="#00000000" StrokeThickness="1" Width="10"/>
<Rectangle x:Name="BoxMiddle" Height="10" RadiusY="1" RadiusX="1" StrokeThickness="1" Width="10">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="0.64,0.88" StartPoint="0.62,0.15">
<GradientStop Color="#FFFFFFFF" Offset="0.013"/>
<GradientStop Color="#F9FFFFFF" Offset="0.375"/>
<GradientStop Color="#EAFFFFFF" Offset="0.603"/>
<GradientStop Color="#D8FFFFFF" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
<Rectangle.Stroke>
<LinearGradientBrush EndPoint=".5,1" StartPoint=".5,0">
<GradientStop Color="#FFFFFFFF" Offset="1"/>
<GradientStop Color="#FFFFFFFF" Offset="0"/>
<GradientStop Color="#FFFFFFFF" Offset="0.375"/>
<GradientStop Color="#FFFFFFFF" Offset="0.375"/>
</LinearGradientBrush>
</Rectangle.Stroke>
</Rectangle>
<Rectangle x:Name="BoxMiddleLine" Height="10" Opacity=".2" RadiusY="1" RadiusX="1" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="1" Width="10"/>
<Path x:Name="CheckIcon" Data="M102.03442,598.79645 L105.22962,597.78918 L106.78825,600.42358 C106.78825,600.42358 108.51028,595.74304 110.21724,593.60419 C112.00967,591.35822 114.89314,591.42316 114.89314,591.42316 C114.89314,591.42316 112.67844,593.42645 111.93174,594.44464 C110.7449,596.06293 107.15683,604.13837 107.15683,604.13837 z" Fill="#FF333333" FlowDirection="LeftToRight" Height="10" Margin="1,1,0,1.5" Opacity="0" Stretch="Fill" Width="10.5"/>
<Rectangle x:Name="IndeterminateIcon" Stroke="Black" Height="8" Opacity="0" Width="8">
<Rectangle.Fill>
<LinearGradientBrush EndPoint="1,1" StartPoint="0,0">
<GradientStop Color="#FF8EBBE2"/>
<GradientStop Color="#FF496074" Offset="0.6"/>
<GradientStop Color="Black" Offset="1"/>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<Rectangle x:Name="DisabledVisualElement" Fill="#FFFFFFFF" Height="14" Opacity="0" RadiusY="1" RadiusX="1" Width="14"/>
<Rectangle x:Name="ContentFocusVisualElement" Height="16" IsHitTestVisible="false" Opacity="0" RadiusY="2" RadiusX="2" Stroke="#FF6DBDD1" StrokeThickness="1" Width="16"/>
<Border x:Name="ValidationErrorElement" BorderBrush="#FFDB000C" BorderThickness="1" CornerRadius="1" Margin="1" ToolTipService.PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}" Placement="Right" PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}" Template="{StaticResource ValidationToolTipTemplate}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="IsHitTestVisible" Storyboard.TargetName="validationTooltip">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean xmlns:sys="clr-namespace:System;assembly=mscorlib">true</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Background="Transparent" HorizontalAlignment="Right" Height="10" Margin="0,-4,-4,0" VerticalAlignment="Top" Width="10">
<Path Data="M 1,0 L5,0 A 2,2 90 0 1 7,2 L7,6 z" Fill="#FFDC000C" Margin="0,3,0,0"/>
<Path Data="M 0,0 L2,0 L 7,5 L7,7" Fill="#ffffff" Margin="0,3,0,0"/>
</Grid>
</Border>
</Grid>
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" Grid.Column="1" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Styl CheckBoxu ale navíc používá šablonu ValidationToolTipTemplate, kterou musíme do resources také přidat.
Výsledek opět můžete porovnat na obrázku:
Všechny tyto styly včetně šablony ValidationToolTipTemplate umístíme do jednoho souboru s názvem např. ImplicitStyles.xaml. Výsledný tento XAML soubor je možné stáhnou zde.
Použití stylů pak nastavíme v App.xaml následujícím kódem:
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles/ImplicitStyles.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Application.Resources>
To je pro náš první tip dnes vše, příště se podíváme na nastavení sloupců DataGrid controlu.