So I have a path setup like this
<Grid x:Name="arrowPanel">
<Path x:Name="arrow" Data="M0,4 H8 M4,4 V8Z" Stroke="Black" StrokeThickness="2" Height="8" RenderTransformOrigin="0.5,0.5" Stretch="None" Width="8">
<!--Not sure what to do here -->
</Path>
</Grid>
I have a storyboard setup like this
<VisualStateGroup x:Name="ExpandStateGroup">
<VisualState x:Name="Expanded">
<Storyboard>
<!-- Something to change the data to this 'M0,5 H10'(A minus sign)-->
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
<Storyboard>
<!-- Something to change the data back to it's original 'M0,5 H10 M5,5 V10Z' (A plus sign)-->
</Storyboard>
</VisualState>
</VisualStateGroup>
I do not know how to alter the data aspect of the path to make these transformations.
You could use an ObjectAnimationUsingKeyFrames to "animate" the Data property of the Path:
<Storyboard>
<ObjectAnimationUsingKeyFrames Duration="0"
Storyboard.TargetName="arrow" Storyboard.TargetProperty="Data">
<DiscreteObjectKeyFrame>
<DiscreteObjectKeyFrame.Value>
<Geometry>M0,5 H10</Geometry>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
In order to get the results I was looking for I created two paths that occupied the same area. One with the plus sign and the other with the minus sign (The plus sign without the vertical path).
Object:
<Grid>
<Grid x:Name="plusSignPanel">
<!-- Minus -->
<Path Data="M0,4 H8" Stroke="Red" StrokeThickness="2" Height="8" RenderTransformOrigin="0.5,0.5" Stretch="None" Width="8"/>
<!-- Plus -->
<Path x:Name="plusSign" Data="M0,4 H8 M4,4 V8Z" Stroke="Black" StrokeThickness="2" Height="8" RenderTransformOrigin="0.5,0.5" Stretch="None" Width="8"/>
</Grid>
</Grid>
And then all I did was change the color from black to transparent using the coloranimation on the plus sign for expanded state and vice versa with the collapsed state:
Animation:
<VisualStateGroup x:Name="ExpandStateGroup">
<VisualState x:Name="Expanded">
<Storyboard>
<ColorAnimation Duration="0:0:0.1" Storyboard.TargetName="plusSign" Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="Transparent"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Collapsed">
<Storyboard>
<ColorAnimation Duration="0:0:0.1" Storyboard.TargetName="plusSign" Storyboard.TargetProperty="(Path.Stroke).(SolidColorBrush.Color)" To="Black"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
Related
So, what I set out to do is template my radio buttons and tab items to look the same so that they look the same but can act differently. SoIi created a Control template and targeted it to the Content Control type and used it as a static resource to template the two types.
Everything works except i cant figure out how to set the header to display as the label. The 'Content' Property picks up the radio button label text, but when I try binding to the 'Header' property I fail at getting the label text.
Here is the content control Control Template Xaml:
<!-- Tab Selector Style -->
<ControlTemplate x:Key="TabSelectorStyle" TargetType="ContentControl">
<Grid x:Name="Root" HorizontalAlignment="Stretch">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates" >
<VisualState x:Name="Normal" >
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Box" Storyboard.TargetProperty="Fill">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="0:0:0.1">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="#FF323232"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="Box" Storyboard.TargetProperty="StrokeThickness" To="1" Duration="0:0:0.1" />
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Box" Storyboard.TargetProperty="Fill">
<ObjectAnimationUsingKeyFrames.KeyFrames>
<DiscreteObjectKeyFrame KeyTime="0:0:0.3">
<DiscreteObjectKeyFrame.Value>
<SolidColorBrush Color="#FF326699"/>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames.KeyFrames>
</ObjectAnimationUsingKeyFrames>
<DoubleAnimation Storyboard.TargetName="Box" Storyboard.TargetProperty="StrokeThickness" To="1.5" Duration="0:0:0.3" />
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="SelectionStates" >
<VisualState x:Name="Selected" >
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Check" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.3" />
</Storyboard>
</VisualState>
<VisualState x:Name="Unselected" >
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Check" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.3" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Stretch" Margin="{Binding Path=Margin}">
<Grid Margin="5,2,5,2" HorizontalAlignment="Center">
<Rectangle x:Name="Box"
Width="10" Height="10"
Fill="#FF323232" Stroke="#FFFFFFFF" StrokeThickness="1"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
<Rectangle x:Name="Check"
Width="8" Height="8"
Fill="#FFFFFFFF" Opacity="0"
VerticalAlignment="Center" HorizontalAlignment="Center"/>
</Grid>
<TextBlock x:Name="RadioButtionTitle" Foreground="#FFFFFFFF" Text="{TemplateBinding Content}" HorizontalAlignment="Stretch" Width="Auto" Margin="5,2,5,2"/>
<!-- F I X E D H E R E -->
<TextBlock x:Name="TabitemTitle" Foreground="#FFFFFFFF" Text="{Binding Header, RelativeSource={RelativeSource FindAncestor, AncestorType=presentcontrols:TabItem}}" HorizontalAlignment="Stretch" Width="Auto" Margin="5,2,5,2"/>
<!-- =================== -->
</StackPanel>
</Grid>
</ControlTemplate>
I have the following XAML that animates a arrow moving on a path for an ESRI Map control. The animation works as expected, except that the animation is not invalidated when the map is zoomed in or out. If I move the animation off screen and back on screen, the animation is correctly re-synced to the scaled path. How do I setup a trigger to force the redraw when the map scale changes?
<esri:LineSymbol x:Key="tunnelSymbol">
<esri:LineSymbol.ControlTemplate>
<ControlTemplate>
<Canvas>
<Path x:Name="Element" StrokeStartLineCap="Round" StrokeEndLineCap="Round" StrokeDashCap="Round" StrokeLineJoin="Round" Opacity="0.5" RenderTransformOrigin="2,0" StrokeThickness="5" Stroke="#FF05AA05">
<Path.Effect>
<DropShadowEffect/>
</Path.Effect>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SelectionStates">
<VisualState x:Name="Selected">
</VisualState>
<VisualState x:Name="Unselected"/>
</VisualStateGroup>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal">
<Storyboard>
<DoubleAnimation BeginTime="0" Duration="0:0:0.1" Storyboard.TargetName="Element" Storyboard.TargetProperty="(Path.StrokeThickness)" To="5"/>
</Storyboard>
</VisualState>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation BeginTime="0" Duration="0:0:0.1" Storyboard.TargetName="Element" Storyboard.TargetProperty="(Path.StrokeThickness)" To="10"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Path>
<Path x:Name="Arrow" Stretch="Fill" Width="16" Height="16" StrokeLineJoin="Miter"
Data="M 0 -5 L 10 -5 M 5 0 L 10 -5 L 5 -10"
Stroke="Black" StrokeThickness="3">
<Path.RenderTransform>
<TransformGroup>
<TranslateTransform X="-8" Y="-8"/>
<MatrixTransform>
<MatrixTransform.Matrix>
<Matrix/>
</MatrixTransform.Matrix>
</MatrixTransform>
</TransformGroup>
</Path.RenderTransform>
<Path.Triggers>
<EventTrigger RoutedEvent="Path.Loaded">
<BeginStoryboard>
<Storyboard>
<MatrixAnimationUsingPath x:Name="MatrixAnimation" Storyboard.TargetName="Arrow"
Storyboard.TargetProperty="RenderTransform.Children[1].Matrix"
DoesRotateWithTangent="True"
Duration="0:0:5"
BeginTime="0:0:0"
RepeatBehavior="Forever" PathGeometry="{Binding Data, Source=Element, BindsDirectlyToSource=True}">
</MatrixAnimationUsingPath>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Path.Triggers>
</Path>
</Canvas>
</ControlTemplate>
</esri:LineSymbol.ControlTemplate>
</esri:LineSymbol>
I have a lot of styles for hiperlink button that are quite the same, just a different path in the template.
Here is the XAML fragment
<Style x:Key="HeatmapLinkStyle" TargetType="HyperlinkButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="HyperlinkButton">
<Grid Margin="4,2" Height="40" VerticalAlignment="Top">
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="MouseOver">
<Storyboard>
<DoubleAnimation Duration="0:0:0.2" To="1.5" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleX)" Storyboard.TargetName="element" d:IsOptimized="True"/>
<DoubleAnimation Duration="0:0:0.2" To="1.5" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.ScaleY)" Storyboard.TargetName="element" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="LinkStates">
<VisualState x:Name="ActiveLink">
<Storyboard>
<ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0010000" Storyboard.TargetName="ActiveLinkBorder" Storyboard.TargetProperty="(UIElement.Visibility)">
<DiscreteObjectKeyFrame KeyTime="00:00:00">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InactiveLink"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid Width="32" Height="32" VerticalAlignment="Top">
<Path Data="M50.5,4.7500001C25.232973,4.75 4.75,25.232973 4.7500001,50.5 4.75,75.767029 25.232973,96.25 50.5,96.25 75.767029,96.25 96.25,75.767029 96.25,50.5 96.25,25.232973 75.767029,4.75 50.5,4.7500001z M50.5,0C78.390381,0 101,22.609621 101,50.5 101,78.390381 78.390381,101 50.5,101 22.609621,101 0,78.390381 0,50.5 0,22.609621 22.609621,0 50.5,0z" Stretch="Fill" Fill="#FF3FA9F5" Visibility="Visible" />
<Path x:Name="element" Data="F1M-1834.73,-354.432L-1772.06,-354.432 -1772.06,-417.099 -1834.73,-417.099 -1834.73,-354.432z M-1832.68,-385.765L-1803.39,-385.765 -1803.39,-415.052 -1774.11,-415.052 -1774.11,-385.765 -1803.39,-385.765 -1803.39,-356.484 -1832.68,-356.484 -1832.68,-385.765z" Stretch="Uniform" Fill="#FF3FA9F5" Width="16" Height="16" Margin="0,0,0,0" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<CompositeTransform ScaleY="1" ScaleX="1"/>
</Path.RenderTransform>
</Path>
<Ellipse x:Name="container" Fill="Transparent" Cursor="Hand" Height="32" Width="32"/>
</Grid>
<Border x:Name="ActiveLinkBorder" Width="32" Height="4" VerticalAlignment="Bottom" Opacity="0.5" Background="#FF3FA9F5" Visibility="Collapsed"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The only changing part is the Data of "element" Path.
Is there a way to use just one base style and change the Path in the others?
Thanks everyone!
You can create a base style an inherit it.
here is an nice example :
basedOn Style
Hope it helps
I'm currently building some custom controls in Silverlight. I want these controls to respond to validation errors. What I'm trying to do is to get that red border around my control, just like the default Silverlight controls.
What I understand is that I need to add this to my template:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="ValidationStates">
<VisualState x:Name="Valid"/>
<VisualState x:Name="InvalidUnfocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="InvalidFocused">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ValidationErrorElement" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsOpen">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>True</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border x:Name="ValidationErrorElement" BorderThickness="1" CornerRadius="1" BorderBrush="#FFDB000C" Visibility="Collapsed">
<ToolTipService.ToolTip>
<ToolTip x:Name="validationTooltip" Template="{StaticResource ValidationToolTipTemplate}" Placement="Right"
PlacementTarget="{Binding RelativeSource={RelativeSource TemplatedParent}}"
DataContext="{Binding RelativeSource={RelativeSource TemplatedParent}}">
<ToolTip.Triggers>
<EventTrigger RoutedEvent="Canvas.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="validationTooltip" Storyboard.TargetProperty="IsHitTestVisible">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<sys:Boolean>true</sys:Boolean>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</ToolTip.Triggers>
</ToolTip>
</ToolTipService.ToolTip>
<Grid Width="12" Height="12" HorizontalAlignment="Right" Margin="1,-4,-4,0" VerticalAlignment="Top" Background="Transparent">
<Path Margin="1,3,0,0" Data="M 1,0 L6,0 A 2,2 90 0 1 8,2 L8,7 z" Fill="#FFDC000C"/>
<Path Margin="1,3,0,0" Data="M 0,0 L2,0 L 8,6 L8,8" Fill="#ffffff"/>
</Grid>
</Border>
and
<ControlTemplate x:Key="ValidationToolTipTemplate">
<Grid x:Name="Root" Margin="5,0" RenderTransformOrigin="0,0" Opacity="0">
<Grid.RenderTransform>
<TranslateTransform x:Name="xform" X="-25"/>
</Grid.RenderTransform>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="OpenStates">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0"/>
<VisualTransition To="Open" GeneratedDuration="0:0:0.2">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="xform" Storyboard.TargetProperty="X" To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<BackEase Amplitude=".3" EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:0.2"/>
</Storyboard>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Closed">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="0" Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Open">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="xform" Storyboard.TargetProperty="X" To="0" Duration="0"/>
<DoubleAnimation Storyboard.TargetName="Root" Storyboard.TargetProperty="Opacity" To="1" Duration="0"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Border Margin="4,4,-4,-4" Background="#052A2E31" CornerRadius="5"/>
<Border Margin="3,3,-3,-3" Background="#152A2E31" CornerRadius="4"/>
<Border Margin="2,2,-2,-2" Background="#252A2E31" CornerRadius="3"/>
<Border Margin="1,1,-1,-1" Background="#352A2E31" CornerRadius="2"/>
<Border Background="#FFDC000C" CornerRadius="2"/>
<Border CornerRadius="2">
<TextBlock
UseLayoutRounding="false"
Foreground="White" Margin="8,4,8,4" MaxWidth="250" TextWrapping="Wrap" Text="{Binding (Validation.Errors)[0].ErrorContent}"/>
</Border>
</Grid>
</ControlTemplate>
And I need to add this for every control that I create (with the exception of the ValidationToolTipTemplate-ControlTemplate).
How can I add the validation style to all my controls and still be DRY?
If that is not possible, does this mean that when the style of the validation template changes, I have to copy and paste the templates of all the controls in the known universe in a file called 'generic.xaml' and change it accordingly?
I've worked with Microsoft APIs long enough to know that (2) is probably the way to go, but I want to make sure first.
With the way that Silveright/WPF work there are some gotchas in these sorts of situations. You DO end up repeating XAML as ControlTemplates are all or nothing. You either end up replacing the Style's template or not. You can't pick and choose what to override like you do with a class.
I will try to answer your specific questions then try to provide a couple of broad approaches that should help to eliminate redundancy in XAML files.
How can you add the validation style to all controls?
First, take that border and make it a StaticResoruce by giving in a Key attribute. Then put it in a shared resource dictionary. When you need the style again you can use it in your templates like this:
<ContentControl x:Name="ValidationErrorElement" Content={StaticResource MyBorderResource}" />
You can't do much more than that for encapsulating the template. As far as the Validation VisualStates, those are as compact as possible already. What you can do however, is combine multiple templates into one larger template. I like to do this with ToggleButtons, Buttons and RadioButtons that look like buttons. See point #2 below.
General Rules of Thumb
Encapsulate commonly used XAML fragments into templated Controls and Control templates. For instance, let's say you have a border within a border that you're using in a few different places. That could be described as a ControlTemplate resource and loaded into a ContentControl.
If you have several styles or control templates that are largely the same (Button,ToggleButton,RadioButton come to mind), then you can apply the style to a common ancestor ButtonBase, in this case. Your new button ControlTemplate would contain all the VisualStates and graphic elements needed by Button,ToggleButton and RadioButton and be applied to the type ButtonBase. As noted above, this approach works well for very similar looking controls but would be a poor choice for controls that aren't that similar.
If I do something like this to change the opacity of an Ellipse:
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualStateGroup.Transitions>
<VisualTransition GeneratedDuration="0:0:1">
<VisualTransition.GeneratedEasingFunction>
<CircleEase EasingMode="EaseIn"/>
</VisualTransition.GeneratedEasingFunction>
</VisualTransition>
</VisualStateGroup.Transitions>
<VisualState x:Name="Lit"/>
<VisualState x:Name="Unlit">
<Storyboard>
<DoubleAnimation Duration="0" To="0.225"
Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="ellipse" d:IsOptimized="True"/>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Ellipse x:Name="ellipse" Width="100" Height="100" Fill="Azure"/>
VisualStateManager.GoToState(this, "Unlit", true);
It works just fine.
But let's say I have a handful of Ellipses and want to apply the same StoryBoard to them, how do I do that?
<Ellipse x:Name="ellipse1" Width="100" Height="100" Fill="Azure"/>
<Ellipse x:Name="ellipse2" Width="100" Height="100" Fill="Azure"/>
<Ellipse x:Name="ellipse3" Width="100" Height="100" Fill="Azure"/>
One way would be to define multiple DoubleAnimations to the same StoryBoard:
<Storyboard>
<DoubleAnimation Duration="0" To="0.225"
Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="ellipse1"
d:IsOptimized="True"/>
<DoubleAnimation Duration="0" To="0.225"
Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="ellipse2"
d:IsOptimized="True"/>
</Storyboard>
But this is somewhat cumbersome when I have a dynamic number of ellipses.
Is there any more elegant way?
I'm not sure about elegant, but one way could be to animate an intermediate property, and bind the real target properties to that:
<Grid x:Name="animationTarget" Visibility="Collapsed" />
<Ellipse x:Name="ellipse1" Width="100" Height="100" Fill="Azure"
Opacity={Binding Opacity, ElementName=animationTarget}/>
<Ellipse x:Name="ellipse2" Width="100" Height="100" Fill="Azure"
Opacity={Binding Opacity, ElementName=animationTarget}/>
and
<Storyboard>
<DoubleAnimation Duration="0" To="0.225"
Storyboard.TargetProperty="(UIElement.Opacity)"
Storyboard.TargetName="animationTarget" />
</Storyboard>
(If using an invisible extra element as an intermediate binding target doesn't seem nice it could instead be on some attached property on whatever container your ellipses are in)