Reputation: 122
I'm working on a Windows Store App with C# and ran into something where I can't imagine it's difficult, but I can't find out how to do it.
On my MainPage.xaml
I created a user control: A StackPanel
with horizontal orientation, with just an Image
and a TextBlock
inside. Like this:
<!-- language: lang-xml -->
<StackPanel Width="300" Height="100" Orientation="Horizontal" Margin="0,0,0,20" Tapped="LoremIpsum_Tapped">
<Image Source="/Assets/pic.jpg" Margin="20"/>
<TextBlock FontFamily="Segoe UI" FontSize="30" VerticalAlignment="Center">
Lorem Ipsum
</TextBlock>
</StackPanel>
Which looks like this:
I use it as a custom kind of button to get to some sub-page. Now the thing is, there are no animations. I wanted to have the typical animations that ListItem
s have in a SplitView: shrink when pressed, grow back to normal when either released or when the pointer exits the virtual borders of the control. I couldn't find the animation declaration/definition associated with those ListItems (Common.StandardStyles.xaml
Standard130ItemTemplate
). But I found out (here) that there are predefined animations for that: PointerDownThemeAnimation
and PointerUpThemeAnimation
. But then it took me quite a while to find out how to apply them to the control. You might think the sample mentioned on this site specifically about those pointer theme animations (about C#) should help, but it leads to sample for HTML/Javascript animations. But I found solutions here, here and here.
So I needed Storyboard
s in the control resources, a name so the control can be targeted and event handlers in the code-behind.
Applied on my XAML it becomes this:
<!-- language: lang-xml -->
<StackPanel x:Name="LoremIpsum" Width="300" Height="100" Orientation="Horizontal" Margin="0,0,0,20" Tapped="LoremIpsum_Tapped" PointerPressed="LoremIpsum_AnimDown" PointerReleased="LoremIpsum_AnimUp" PointerExited="LoremIpsum_AnimUp">
<Image Source="/Assets/pic.jpg" Margin="20"/>
<TextBlock FontFamily="Segoe UI" FontSize="30" VerticalAlignment="Center">
Lorem Ipsum
</TextBlock>
<StackPanel.Resources>
<Storyboard x:Name="pointerDownStoryboard">
<PointerDownThemeAnimation TargetName="LoremIpsum" />
</Storyboard>
<Storyboard x:Name="pointerUpStoryboard">
<PointerUpThemeAnimation TargetName="LoremIpsum" />
</Storyboard>
</StackPanel.Resources>
</StackPanel>
Plus the additional event handlers in the code-behind:
<!-- language: lang-cs -->
private void Latest_AnimDown(object sender, PointerRoutedEventArgs e)
{
pointerDownStoryboard.Begin();
}
private void Latest_AnimUp(object sender, PointerRoutedEventArgs e)
{
pointerUpStoryboard.Begin();
}
This worked. But... as I have lots of those kind of user controls, I certainly don't want to add all that for every control. As mentioned before the Standard130ItemTemplate
didn't help. So I thought about custom controls. I was hoping I could just define a MyStackPanel
that's nothing but a StackPanel
+ the StoryBoard
s, and that the targeting to the x:Name would work and maybe I could put the event handlers in the LayoutAwarePage code-behind, so all others inherit from it.
I started looking for how to do that and found this sample of how to create custom controls: XAML user and custom controls sample.
There's the custom control in a Generic.xml:
<!-- language: lang-xml -->
xmlns:local="using:UserAndCustomControls">
<Style TargetType="local:BasicCustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BasicCustomControl">
<Border
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And a code file, but not code-behind:
<!-- language: lang-cs -->
namespace UserAndCustomControls
{
public sealed class BasicCustomControl : Control
{
public BasicCustomControl()
{
this.DefaultStyleKey = typeof(BasicCustomControl);
}
}
}
But the sample didn't contain anything about animations. So I played around with the example, trying to add the StoryBoard
s to the Border or to the ControlTemplate, adding event handlers to a self created Generic.xaml.cs code-behind. Nothing worked.
Then I found this, but wasn't sure about how and why to put an event handler into the BasicCustomControl
class. I tried it anyway:
In Generic.xaml
:
<!-- language: lang-xml -->
xmlns:local="using:UserAndCustomControls">
<Style TargetType="local:BasicCustomControl">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="local:BasicCustomControl">
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border.Resources>
<Storyboard x:Name="pointerDownStoryboard">
<PointerDownThemeAnimation TargetName="Border" />
</Storyboard>
<Storyboard x:Name="pointerUpStoryboard">
<PointerUpThemeAnimation TargetName="Border" />
</Storyboard>
</Border.Resources>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
And in BasicCustomControl.cs
:
<!-- language: lang-cs -->
protected override void OnPointerPressed(PointerRoutedEventArgs e)
{
((Storyboard)this.Resources["PointerDownThemeAnimation"]).Begin(this);
}
That didn't work. The Begin()
method doesn't take arguments and without the argument the build succeeded, but I got an System.Runtime.InteropServices.COMException when clicking on the control.
Then I found this on SO: How to add XAML storyboard animation to a full blown WPF Custom Control in an XBAP?
This seems to be an interesting solution, but I don't get it. Here's also some description of VisualState stuff for custom controls, but again I don't know how to apply this to my needs: Quickstart: control templates
Now at this point I spent quite some time on this and I'm just thinking that for this simple thing - a simple, even pre-defined animation for a custom control -, there must be a simple solution. I hope I'm just overlooking something and it's not really that complicated.
To sum up the questions:
Standard130ItemTemplate
defined and "attached" to the template?Upvotes: 5
Views: 3339
Reputation: 1108
Create a custom style. In this example I bound the Image Source to the content field. So I just had to put my path to my image in that field (ex. "Assets/Image.png"). It would be nice if adding custom animations like this was easier to figure out the first time.
<Style x:Key="ImageButtonStyle" TargetType="ButtonBase">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ButtonBase">
<Grid x:Name="RootGrid" Background="Transparent">
<Image x:Name="ImageLabel" Source="{TemplateBinding Content}"/>
<Rectangle
x:Name="FocusVisualWhite"
IsHitTestVisible="False"
Stroke="{StaticResource FocusVisualWhiteStrokeThemeBrush}"
StrokeEndLineCap="Square"
StrokeDashArray="1,1"
Opacity="0"
StrokeDashOffset="1.5"/>
<Rectangle
x:Name="FocusVisualBlack"
IsHitTestVisible="False"
Stroke="{StaticResource FocusVisualBlackStrokeThemeBrush}"
StrokeEndLineCap="Square"
StrokeDashArray="1,1"
Opacity="0"
StrokeDashOffset="0.5"/>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal"/>
<VisualState x:Name="PointerOver">
</VisualState>
<VisualState x:Name="Pressed">
<Storyboard>
<PointerDownThemeAnimation TargetName="ImageLabel" />
</Storyboard>
</VisualState>
<VisualState x:Name="Disabled">
</VisualState>
</VisualStateGroup>
<VisualStateGroup x:Name="FocusStates">
<VisualState x:Name="Focused">
<Storyboard>
<DoubleAnimation
Storyboard.TargetName="FocusVisualWhite"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
<DoubleAnimation
Storyboard.TargetName="FocusVisualBlack"
Storyboard.TargetProperty="Opacity"
To="1"
Duration="0"/>
</Storyboard>
</VisualState>
<VisualState x:Name="Unfocused" />
<VisualState x:Name="PointerFocused" />
</VisualStateGroup>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Checked">
<Storyboard>
<DoubleAnimation Duration="0" To="0" Storyboard.TargetName="OutlineGlyph" Storyboard.TargetProperty="Opacity"/>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundGlyph" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource AppBarItemForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="BackgroundCheckedGlyph" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="Content" Storyboard.TargetProperty="Foreground">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource AppBarItemPressedForegroundThemeBrush}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Unchecked"/>
<VisualState x:Name="Indeterminate"/>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
Upvotes: 0