I have a ContentControl that I'm styling with a DataTemplate. I'd like to be able to define an animation outside of the ContentControl that animates elements in the DataTemplate. This XAML is a small, simplified example of my scenario:
<UserControl x:Class="StoryboardTesting.Stage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d">
<UserControl.Resources>
<DataTemplate x:Key="MyControlTemplate">
<StackPanel>
<TextBlock x:Name="TheBlock1" Text="Foo!" />
<TextBlock x:Name="TheBlock2" Text="Bar!" />
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup Name="ValueStates">
<VisualState Name="ToState">
<Storyboard>
<DoubleAnimation Storyboard.TargetName="MyContentControl"
Storyboard.TargetProperty="(UIElement.Opacity)"
Duration="0:0:1"
To="0" />
</Storyboard>
</VisualState>
<VisualState Name="FromState" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Grid>
<Canvas>
<ContentControl x:Name="MyContentControl"
ContentTemplate="{StaticResource MyControlTemplate}" />
</Canvas>
</Grid>
</UserControl>
I'd like the animation to be able to target either TextBox in the template (instead of "MyContentControl"), either by position or name. I'm starting the animation in the UserControl's code-behind with a call like this:
VisualStateManager.GoToElementState(this, "ToState", true);
When I run this (replacing "MyContentControl" with "TheBlock"), I get the following:
InvalidOperationException: 'TheBlock1' name cannot be found in the name scope of 'StoryboardTesting.Stage'.
Which makes sense. Is there a way to address either block using property names? I need to avoid codebehind since this is XAML that is being generated at runtime.
I'd highly suggest you to learn using Blend when working on WPF projects. While XAML by keyboard skills are indeed useful, Blend is also very helpful. It took me about 5 minutes to build the following example for you, it's a DataTemplate which has states.
(first I created an empty DataTemplate, then I edited in Blend)
User can press any of the 2 buttons on the bottom and the current state will be changed.
As you'll see below, behaviors proven to be really helpful for handling states, no code-behind at all.
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:wpfApplication3="clr-namespace:WpfApplication3"
Title="MainWindow"
Width="525"
Height="350">
<Window.Resources>
<wpfApplication3:MyObject x:Key="MyObject1" />
<DataTemplate x:Key="Template1" DataType="wpfApplication3:MyObject">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="37*" />
<RowDefinition Height="13*" />
</Grid.RowDefinitions>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="VisualStateGroup">
<VisualState x:Name="Red">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="button" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="Red" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<VisualState x:Name="Green">
<Storyboard>
<ColorAnimationUsingKeyFrames Storyboard.TargetName="button" Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)">
<EasingColorKeyFrame KeyTime="0" Value="Lime" />
</ColorAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<Button x:Name="button"
Grid.RowSpan="1"
Grid.ColumnSpan="2"
Width="100"
Height="100"
Margin="2"
Content="Button"
FontSize="26.667" />
<Button Grid.Row="1"
Width="Auto"
Margin="2"
Content="State1">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:GoToStateAction StateName="Red" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
<Button Grid.Row="1"
Grid.Column="1"
Width="Auto"
Margin="2"
Content="State2">
<i:Interaction.Triggers>
<i:EventTrigger EventName="Click">
<ei:GoToStateAction StateName="Green" />
</i:EventTrigger>
</i:Interaction.Triggers>
</Button>
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content="{StaticResource MyObject1}" ContentTemplate="{StaticResource Template1}" />
</Grid>
</Window>
Code-behind:
namespace WpfApplication3
{
public partial class MainWindow
{
public MainWindow() {
InitializeComponent();
}
}
internal class MyObject
{
public string Category { get; set; }
public int Value { get; set; }
}
}
EDIT
To answer the point of your question, those states belong to the DataTemplate; defining these states outside of it doesn't make any sense and as you've experienced it is not even possible, and this is for a good reason !
Imagine that you use this template in 2 different places, would they share the same state ? Of course no, so the states have to be defined inside it, not outside.
Related
I am looking at the windows 8 grid project that comes with VS2012 and trying to understand it. I don't know too much about XAML so I getting quite easily lost of where binding code is happening and such.
<Page.Resources>
<!--
Collection of grouped items displayed by this page, bound to a subset
of the complete item list because items in groups cannot be virtualized
-->
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="TopItems"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Horizontal scrolling grid used in most view states -->
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="2"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
<GridView.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</GridView.ItemsPanel>
<GridView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="1,0,0,6">
<Button
AutomationProperties.Name="Group Title"
Click="Header_Click"
Style="{StaticResource TextPrimaryButtonStyle}" >
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
<GroupStyle.Panel>
<ItemsPanelTemplate>
<VariableSizedWrapGrid Orientation="Vertical" Margin="0,0,80,0"/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</GridView.GroupStyle>
</GridView>
<!-- Vertical scrolling list only used when snapped -->
<ListView
x:Name="itemListView"
AutomationProperties.AutomationId="ItemListView"
AutomationProperties.Name="Grouped Items"
Grid.Row="1"
Visibility="Collapsed"
Margin="0,-10,0,0"
Padding="10,0,0,60"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard80ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
<ListView.GroupStyle>
<GroupStyle>
<GroupStyle.HeaderTemplate>
<DataTemplate>
<Grid Margin="7,7,0,0">
<Button
AutomationProperties.Name="Group Title"
Click="Header_Click"
Style="{StaticResource TextPrimaryButtonStyle}">
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
<TextBlock Text="{StaticResource ChevronGlyph}" FontFamily="Segoe UI Symbol" Margin="0,-7,0,10" Style="{StaticResource GroupHeaderTextStyle}"/>
</StackPanel>
</Button>
</Grid>
</DataTemplate>
</GroupStyle.HeaderTemplate>
</GroupStyle>
</ListView.GroupStyle>
</ListView>
<!-- Back button and page title -->
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Button x:Name="backButton" Click="GoBack" IsEnabled="{Binding Frame.CanGoBack, ElementName=pageRoot}" Style="{StaticResource BackButtonStyle}"/>
<TextBlock x:Name="pageTitle" Text="{StaticResource AppName}" Grid.Column="1" IsHitTestVisible="false" Style="{StaticResource PageHeaderTextStyle}"/>
</Grid>
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource PortraitBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Padding">
<DiscreteObjectKeyFrame KeyTime="0" Value="96,137,10,56"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
<!--
The back button and title have different styles when snapped, and the list representation is substituted
for the grid displayed in all other view states
-->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="backButton" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedBackButtonStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemListView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Visible"/>
</ObjectAnimationUsingKeyFrames>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="itemGridView" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0" Value="Collapsed"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
The first area I don't understand is this
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="TopItems"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
I don't get where this Source="{Binding Groups}" is actually coming from (I am not even sure how to find the code F12 does not work).
Same with the d:Source not sure 100% what is going on there as well.
The next part is in the gridview and again with the binding.
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="2"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
SelectionMode="None"
IsSwipeEnabled="false"
IsItemClickEnabled="True"
ItemClick="ItemView_ItemClick">
I see that Items Source is using that groupItemsViewSource but not sure how it is parsing the data into the grid look but not sure how.
The next part I don't get is how does it figure out what layout to use. I see this in the comments
<!-- Horizontal scrolling grid used in most view states -->
<!-- Vertical scrolling list only used when snapped -->
I am sure if I look even deeper into it I will find more I don't understand but maybe when I understand this stuff I will understand more(especially once I understand this binding stuff)
I don't get where this Source="{Binding Groups}" is actually coming
from (I am not even sure how to find the code F12 does not work).
Note at the top of the page:
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
which sets the context for the binding on that page to the DefaultViewModel property of the page. In the code behind for the page you'll see
this.DefaultViewModel["Groups"] = sampleDataGroups;
where sampleDataGroups is a list (IEnumerable) of groups (type SampleDataGroup) returned from a method call. Now you also have,
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="TopItems"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
and here "Groups" refers to the key that indexes the DefaultViewModel, so it's going to use that subset of the DefaultViewModel. Furthermore, each item in the CollectionViewSource itself contains a collection, and those collections are surfaced to the binding engine at whatever property ItemsPath specifies, namely, TopItems.
Now from the GridView binding:
<GridView
x:Name="itemGridView"
AutomationProperties.AutomationId="ItemGridView"
AutomationProperties.Name="Grouped Items"
Grid.RowSpan="2"
Padding="116,137,40,46"
ItemsSource="{Binding Source={StaticResource groupedItemsViewSource}}"
ItemTemplate="{StaticResource Standard250x250ItemTemplate}"
you noted that the data is coming from that specific instance of a CollectionViewSource named groupedItemsViewSource (which in this case is equivalent to this.DefaultViewModel["Groups"]). If you look at the header template a bit later, you'll see:
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Title}" Margin="3,-7,10,10" Style="{StaticResource GroupHeaderTextStyle}" />
...
The Title binding refers to the property of an element in the ItemsSource collection, so this.DefaultViewModel["Groups"][i].Title
ItemTemplate="{StaticResource Standard250x250ItemTemplate}" in the GridView binding governs how the inner collection items are displayed. You'll have to crack that style open (in Common/StandardStyles.xaml) to see that it references properties of a SampleDataItem, essentially leading to this.DefaultViewModel["Groups"][i].TopItems[j].
The d: prefix refers to design time data (well, a namespace that includes classes that manage it); it's what allows you do see the data in the designer without running your application. So at design time the data you are seeing actually comes from SampleDataSource.AllGroups.
As for the comments about scrolling, that gets into the VisualStateManager, each state essentially being a different take on the UI; states transition via a bit of code you can find inside of LayoutAwarePage.cs look for GotoState.
I don't get where this Source="{Binding Groups}" is actually coming from
Bindings target the DataContext of the element -- to find it, you typically scan up the XAML to find where DataContext was last set. So in this case, it must either set on the Page element somehow. (The DataContext can also be set from code-behind, although I'd be surprised if that were the case in the VS sample project.)
Same with the d:Source not sure 100% what is going on there as well.
The d prefix is referring to design-time data. Check for the SampleDataSource, that's where you'll find the design-time instance of AllGroups.
not sure how it is parsing the data into the grid
The Grid auto-generates its columns based on the properties of its row data type.
I have a ScrollViewer that contains a StackedPanel with rectangles laid out horizontally. I want to be able to scroll horizontally, but this isn't happening for me. Here is my XAML:
<common:LayoutAwarePage
x:Name="pageRoot"
x:Class="BlastSwing.GroupedItemsPage"
DataContext="{Binding DefaultViewModel, RelativeSource={RelativeSource Self}}"
IsTabStop="false"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:BlastSwing"
xmlns:data="using:BlastSwing.Data"
xmlns:common="using:BlastSwing.Common"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Page.Resources>
<!--
Collection of grouped items displayed by this page, bound to a subset
of the complete item list because items in groups cannot be virtualized
-->
<CollectionViewSource
x:Name="groupedItemsViewSource"
Source="{Binding Groups}"
IsSourceGrouped="true"
ItemsPath="TopItems"
d:Source="{Binding AllGroups, Source={d:DesignInstance Type=data:SampleDataSource, IsDesignTimeCreatable=True}}"/>
</Page.Resources>
<!--
This grid acts as a root panel for the page that defines two rows:
* Row 0 contains the back button and page title
* Row 1 contains the rest of the page layout
-->
<Grid Style="{StaticResource LayoutRootStyle}">
<Grid.RowDefinitions>
<RowDefinition Height="140"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Back button and page title -->
<TextBlock x:Name="pageTitle" Text="Blast Swing" Style="{StaticResource PageHeaderTextStyle}" Margin="40,0,-10,53"/>
<ScrollViewer HorizontalAlignment="Left" Height="628" Grid.Row="1" VerticalAlignment="Top" Width="1366" RenderTransformOrigin="0.476999998092651,0.998000025749207" VerticalScrollMode="Disabled" Margin="-22,0,0,0" HorizontalScrollMode="Auto">
<StackPanel Height="568" Width="1313" RenderTransformOrigin="-0.0179999992251396,0.512000024318695" Orientation="Horizontal">
<Rectangle Fill="#FF974B55" Height="568" Stroke="Black" VerticalAlignment="Top" Width="371"/>
<Rectangle Fill="#FF17179C" Height="568" Stroke="Black" VerticalAlignment="Top" Width="371"/>
<Rectangle Fill="#FF8D2B80" Height="568" Stroke="Black" VerticalAlignment="Top" Width="371"/>
<Rectangle Fill="#FF301D2F" Height="568" Stroke="Black" VerticalAlignment="Top" Width="371"/>
<Rectangle Fill="#FFF4F4F5" Height="568" Stroke="Black" VerticalAlignment="Top" Width="371"/>
</StackPanel>
</ScrollViewer>
<!-- Back button and page title -->
<!-- Horizontal scrolling grid used in most view states -->
<!-- Vertical scrolling list only used when snapped -->
<VisualStateManager.VisualStateGroups>
<!-- Visual states reflect the application's view state -->
<VisualStateGroup x:Name="ApplicationViewStates">
<VisualState x:Name="FullScreenLandscape"/>
<VisualState x:Name="Filled"/>
<!-- The entire page respects the narrower 100-pixel margin convention for portrait -->
<VisualState x:Name="FullScreenPortrait"/>
<!--
The back button and title have different styles when snapped, and the list representation is substituted
for the grid displayed in all other view states
-->
<VisualState x:Name="Snapped">
<Storyboard>
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="pageTitle" Storyboard.TargetProperty="Style">
<DiscreteObjectKeyFrame KeyTime="0" Value="{StaticResource SnappedPageHeaderTextStyle}"/>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</common:LayoutAwarePage>
You are setting Scrollviewer's Width="1366" which is more than your child stackpanel Width="1313".
If you want to see the horizontal scrollbar then you might want to set the size of ScrollViewer less than 1313 and it should be able to display the horizontal scrollbar as it is not able to display the content fully.
Set the HorizontalScrollBarVisibility property on the ScrollViewer to Visible or Auto.
You are setting the Width of your StackPanel manually and it is less than the total of its children.
In the code below, I want to start the animation when there's TextChanged() event of TextBlock is called. But when I try this code, I get an error...
"Failed to assign to property 'System.Windows.EventTrigger.RoutedEvent'"
I am lost, could someone please assist me that how can I do this?
<StackPanel>
<ListBox Name"lstSample" SelectionChanged="lstSample_SelectionChanged">
<ListBox.Triggers>
<EventTrigger RoutedEvent="ListBox.SelectionChanged">
<BeginStoryboard>
<BeginStoryboard.Storyboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="txtSample" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1.0">
<DoubleAnimation.EasingFunction>
<PowerEase EasingMode="EaseIn" Power="8"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard.Storyboard>
</BeginStoryboard>
</EventTrigger>
</ListBoxTriggers>
</ListBox>
<Border Name="brdrTextSampleLanguageOne" BorderThickness="0" BorderBrush="{StaticResource PhoneAccentBrush}">
<TextBlock
Text="This is sample text."
Name="txtSample"
TextAlignment="Right"
VerticalAlignment="Center" />
</Border>
</StackPanel>
Thanks very much.
Would be really easy using code, just create a property like:
private string _textBlockText;
public string textBlockText
{
get { return _textBlockText; }
set
{
if (txtSample.Text != value)
{
if (Storyboard1.GetCurrentState() != ClockState.Active)
Storyboard1.Begin();
txtSample.Text = value;
}
}
}
Just use textBlockText property to update text in anywhere in your code and this should work like TextChanged event... Note: Storyboard1 is the animation you desire to play on TextChanged Event.
This will help you find the code below
<UserControl x:Class="WrapPanel.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
d:DesignHeight="300"
d:DesignWidth="400"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
xmlns:toolkit="http://schemas.microsoft.com/winfx/2006/xaml/presentation/toolkit"
xmlns:sdk="http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk">
<Grid x:Name="LayoutRoot"
Background="White">
<StackPanel>
<StackPanel.Resources>
<Storyboard x:Key="mystoryboard">
<DoubleAnimation Storyboard.TargetName="txtSample"
Storyboard.TargetProperty="Opacity"
From="0"
To="1"
Duration="0:0:1.0">
<DoubleAnimation.EasingFunction>
<PowerEase EasingMode="EaseIn"
Power="8" />
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</StackPanel.Resources>
<ListBox Name="lstSample"
SelectionChanged="lstSample_SelectionChanged">
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<ei:ControlStoryboardAction ControlStoryboardOption="Play"
Storyboard="{StaticResource mystoryboard}">
</ei:ControlStoryboardAction>
</i:EventTrigger>
</i:Interaction.Triggers>
</ListBox>
<Border Name="brdrTextSampleLanguageOne"
BorderThickness="0">
<TextBlock Text="This is sample text."
Name="txtSample"
TextAlignment="Right"
VerticalAlignment="Center" />
</Border>
</StackPanel>
</Grid>
</UserControl>
Let me know if it works for you.
Cheers!
Vinod
I'm working on a C#/XAML Metro style app for Windows 8. The XAML in WinRT does not have a "tab" control. However, I'm trying to emulate the way a result in the Windows 8 store looks. For instance, this image shows "Overview", "Details", and "Reviews" tabs:
How do I create these?
A RadioButton seems to make sense. I figured I could use the GroupName to ensure only one item is selected. But if I use a RadioButton, I don't know how to make the selected item look dark gray while makig the other options light gray. Can someone show me the XAML of a RadioButton that does not show the little checked thingy? And also is dark gray when selected and light gray when not selected.
Thank you so much!
Here is the style to use for radio buttons to make them look/work like tabs:
<!-- Style for radio buttons used as tab control -->
<Style x:Key="TabRadioButtonStyle" TargetType="RadioButton">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="RadioButton">
<Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CheckStates">
<VisualState x:Name="Unchecked">
<Storyboard>
<ColorAnimation Duration="0" To="Gray" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TabButtonText" />
</Storyboard>
</VisualState>
<VisualState x:Name="Indeterminate">
<Storyboard>
<ColorAnimation Duration="0" To="White" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TabButtonText" />
</Storyboard>
</VisualState>
<VisualState x:Name="Checked">
<Storyboard>
<ColorAnimation Duration="0" To="Black" Storyboard.TargetProperty="(TextBlock.Foreground).(SolidColorBrush.Color)" Storyboard.TargetName="TabButtonText" />
</Storyboard>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
<TextBlock x:Name="TabButtonText" Text="{TemplateBinding Content}" Style="{StaticResource GroupHeaderTextStyle}" HorizontalAlignment="Left"/>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
You can then define a grid to hold the tab stack panel and a frame to hold the content associated with each tab. Use Click event on the radio buttons to navigate the frame to the appropriate pages for each "tab".
<Grid Grid.Row="1"
Margin="120,0,56,56">
<!-- Row 1 to hold the "Tabs", Row 2 to hold the content -->
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<StackPanel Grid.Row="0" Orientation="Horizontal">
<RadioButton x:Name="Tab1" Content="Tab1" Style="{StaticResource TabRadioButtonStyle}" IsChecked="True" Click="Tab1_Clicked" />
<RadioButton x:Name="Tab2" Content="Tab2" Style="{StaticResource TabRadioButtonStyle}" IsChecked="False" Click="Tab2_Clicked" Margin="30,0,0,0" />
<RadioButton x:Name="Tab3" Content="Tab3" Style="{StaticResource TabRadioButtonStyle}" IsChecked="False" Click="Tab3_Clicked" Margin="30,0,0,0"/>
</StackPanel>
<Frame x:Name="ContentFrame" Margin="0,20,0,0" Grid.Row="1" Background="{StaticResource SandstormBackgroundBrush}" Loaded="ContentFrame_Loaded" />
</Grid>
Styling a ListBox is preferable to styling a radio button group.
The following code uses a ListBox with a horizontal stack panel to create the tab item header. A ContentControl displays the tab content as a user control.
I've only tested this with WPF, but hopefully it will work on WinRT.
<Page.Resources>
<Style TargetType="ListBoxItem">
<!-- disable default selection highlight -->
<!-- Style.Resources is not supported in WinRT -->
<!--<Style.Resources>
--><!-- SelectedItem with focus --><!--
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Transparent" />
--><!-- SelectedItem without focus --><!--
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Transparent" />
</Style.Resources>-->
<!--Setter Property="FocusVisualStyle" is not supported in WinRT -->
<!--<Setter Property="FocusVisualStyle" Value="{x:Null}" />-->
</Style>
<Style x:Key="TitleStyle" TargetType="TextBlock">
<Setter Property="Foreground" Value="LightGray"/>
<!--Style.Triggers is not supported in WinRT-->
<!--<Style.Triggers>
<DataTrigger Value="True" Binding="{Binding Path=IsSelected,
RelativeSource={RelativeSource Mode=FindAncestor,
AncestorType={x:Type ListBoxItem}}}">
<Setter Property="Foreground" Value="DarkGray"/>
<Setter Property="FontWeight" Value="Bold"/>
</DataTrigger>
</Style.Triggers>-->
</Style>
</Page.Resources>
<Grid>
<Grid.DataContext>
<ViewModel:TestPage/>
</Grid.DataContext>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<ListBox x:Name="tabListBox" Grid.Row="0" ItemsSource="{Binding Items}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay}"
BorderBrush="{x:Null}" BorderThickness="0">
<ListBox.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" />
</ItemsPanelTemplate>
</ListBox.ItemsPanel>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Title}" Margin="5"
Style="{StaticResource TitleStyle}"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<ContentControl Grid.Row="1" Content="{Binding SelectedItem.Content}"/>
</Grid>
View model
public class MyTabViewModel : INotifyPropertyChanged
{
public MyTabViewModel()
{
Items =
new List<MyTabItem>
{
new MyTabItem
{
Title = "Overview",
Content = new UserControl1()
},
new MyTabItem
{
Title = "Detail",
Content = new UserControl2()
},
new MyTabItem
{
Title = "Reviews",
Content = new UserControl3()
},
};
}
public IEnumerable<MyTabItem> Items { get; private set; }
private MyTabItem _selectedItem;
public MyTabItem SelectedItem
{
get { return _selectedItem; }
set
{
_selectedItem = value;
PropertyChanged(this, new PropertyChangedEventArgs("SelectedItem"));
}
}
#region Implementation of INotifyPropertyChanged
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
public class MyTabItem
{
public string Title { get; set; }
public UserControl Content { get; set; }
}
The FlipView control might meet your needs. Sample.
I used FlipView control as well, but I created a separate templated control which is inherited from FlipView.
The main idea is to override default FlipView ControlTemplate: I added a ListBox which represents tab headers and removed "Next" and "Previous" FlipView buttons.
I can share source code if you need more details about my implementation.
I'm really new to WPF so if you can point me to a tutoiral as well i will be very happy :)
here is my curreny code:
<Grid Name="Grid">
<local:Card Loaded="Card_Loaded"
x:Name="MyCard">
<local:Card.Triggers>
<EventTrigger RoutedEvent="local:Card.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="MyCard"
Storyboard.TargetProperty="Opacity"
From="1.0"
To="0.0"
Duration="0:0:5"
AutoReverse="True"
RepeatBehavior="Forever" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</local:Card.Triggers>
</local:Card>
</Grid>
local:Card is a UserControl
Here is why i use x:Name="" and not Name="":
Because 'MS.Internal.Design.Metadata.ReflectionTypeNode' is implemented in the same assembly, you must set the x:Name attribute rather than the MS.Internal.Design.Metadata.ReflectionPropertyNode attribute.
And i can see the Card and everything but the Animation doesn't work =\
Here is the card XAML:
<UserControl.Resources>
<x:Array Type="{x:Type s:String}"
x:Key="src">
<s:String>Foo</s:String>
</x:Array>
<DataTemplate x:Key="frontTemplate">
<Grid Background="Transparent">
<Image Source="Images\Card.jpg" />
</Grid>
</DataTemplate>
<DataTemplate x:Key="backTemplate">
<GroupBox Header="Back"
Background="White">
<StackPanel>
<RadioButton Content="This"
IsChecked="True" />
<RadioButton Content="Is" />
<RadioButton Content="The" />
<RadioButton Content="Back" />
</StackPanel>
</GroupBox>
</DataTemplate>
</UserControl.Resources>
<ScrollViewer>
<ItemsControl Width="180"
Height="250"
ItemsSource="{StaticResource src}"
ItemTemplate="{StaticResource flipItemTemplate}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<WrapPanel />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
</ScrollViewer>
I copied your XAML exactly and ran it in a window. The only change I made was to replace the local:Card object with a TextBlock (since I didn't have a Card usercontrol). The animation ran perfectly.
So either your local:Card object has something strange that isn't allowing the animation to work or the Loaded="Card_Loaded" method in this line:
<local:Card Loaded="Card_Loaded" x:Name="MyCard">
is interfering with the event trigger:
<EventTrigger RoutedEvent="local:Card.Loaded">
Put the storyboard in either the EventTrigger.EnterActions or EventTrigger.Actions tags.
I recently dived in to some WPF animations....here's the link that got me started:
http://www.galasoft.ch/mydotnet/articles/article-2006102701.aspx
Solution:
<Grid Name="Grid">
<local:Card x:Name="MyCard" MouseEnter="MyCard_MouseEnter" />
</Grid>
<Window.Resources>
<Storyboard x:Key="sbdCardAnim">
<DoubleAnimation
Storyboard.TargetName="MyCard"
Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="0:0:5"
AutoReverse="True" RepeatBehavior="Forever" />
</Storyboard>
</Window.Resources>
C#:
private void MyCard_MouseEnter(object sender, MouseEventArgs e)
{
Storyboard sbdCardAnim = (Storyboard)FindResource("sbdCardAnim");
sbdCardAnim.Begin(this);
}