views:

640

answers:

3

I'm having the darndest time figuring this out: say I've got two Button and three TextBlocks. I want either button to trigger a simple Storyboard on ALL TextBlocks. Currently I'm trying to define a generic Textblock style that contains the Storyboard, and then the trigger comes from any Button click. This is the closest I've come but the app crashes on startup...what am I don't wrong here:

<Window.Resources>

<Style TargetType="TextBlock" >
 <Setter Property="Foreground" Value="Blue" />
 <Style.Resources>
  <Storyboard x:Key="TextBlockOpacity" Storyboard.TargetProperty="Opacity">
   <DoubleAnimation From="0" To="1" />
  </Storyboard>
 </Style.Resources>  
</Style>

<Window.Triggers>
 <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="button">
  <BeginStoryboard Storyboard="{StaticResource TextBlockOpacity}"/>
 </EventTrigger>
</Window.Triggers>


<Grid x:Name="LayoutRoot">
 <Button x:Name="button" HorizontalAlignment="Left" Margin="51,54,0,0" VerticalAlignment="Top" Width="96" Height="45" Content="Button"/>

 <TextBlock x:Name="textBlock1" Margin="228,54,172,0" VerticalAlignment="Top" Height="45" FontSize="26.667" Text="TextBlock" TextWrapping="Wrap" />
 <TextBlock x:Name="textBlock2" Margin="228,103,172,0" VerticalAlignment="Top" Height="45" FontSize="26.667" Text="Hello" TextWrapping="Wrap"/>
</Grid>
+6  A: 

If you "dedicate" the button to changing the opacity, you could harness its DataContext and animate it. Then simply bind your elements' Opacity to the DataContext:

(I've also refactored your xaml a bit)

<Window x:Class="SomeNamespace.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:System="clr-namespace:System;assembly=mscorlib"
    Title="Window1" Height="300" Width="300">
    <Window.Resources>        
        <Storyboard x:Key="TextBlockOpacity" Storyboard.TargetName="button1" Storyboard.TargetProperty="DataContext" >
            <DoubleAnimation From="0.1" To="1"/>
        </Storyboard>        
        <Style TargetType="TextBlock" >
            <Setter Property="Foreground" Value="Blue" />
            <Setter Property="Background" Value="LightGray" />
            <Setter Property="FontSize" Value="26.667" />
            <Setter Property="TextWrapping" Value="Wrap" />
            <Setter Property="Height" Value="45" />            
            <Setter Property="Opacity" Value="{Binding ElementName=button1, Path=DataContext}"/>
        </Style>
    </Window.Resources>

    <Window.Triggers>
        <EventTrigger RoutedEvent="ButtonBase.Click">
            <BeginStoryboard Storyboard="{StaticResource TextBlockOpacity}" >
            </BeginStoryboard>
        </EventTrigger>

        <EventTrigger RoutedEvent="ListBox.SelectionChanged">
            <BeginStoryboard Storyboard="{StaticResource TextBlockOpacity}" >
            </BeginStoryboard>
        </EventTrigger>
    </Window.Triggers>

    <Grid x:Name="LayoutRoot">
        <Button x:Name="button1" HorizontalAlignment="Left" Margin="51,54,0,0" VerticalAlignment="Top" Width="96" Height="45" Content="Button">
            <Button.DataContext>
                <System:Double>0</System:Double>
            </Button.DataContext>
        </Button>

        <Button x:Name="button2" HorizontalAlignment="Right" Margin="0,54,29,0" VerticalAlignment="Top" Width="96" Height="45" Content="Button"/>

        <ListBox x:Name="listBox1" Height="50" VerticalAlignment="Top">
            <ListBox.Items>
                <System:String>Text1</System:String>
                <System:String>Text2</System:String>
            </ListBox.Items>
        </ListBox>

        <TextBlock x:Name="textBlock1" Margin="51,114,61,0" Text="TextBlock" Height="45" VerticalAlignment="Top" Width="166" />
        <TextBlock x:Name="textBlock2" Margin="51,0,74,42" Text="Hello" Height="45" Width="153" VerticalAlignment="Bottom" />
    </Grid>
</Window>

Also note one thing - this is the approach to use if you want to minimize your code, and make it all happen in xaml. Your approach would anmate the Opacity of the whole Window. That's why in the code above, TextBlocks bind to the button's DataContext, which is itself animated.

It is of course doable without binding to a common value (the DataContext), but then you need to repeat X animations (because you need to set X TargetNames). This approach above is more easily extendable and maintainable.

EDIT

Added another Button and a ListBox for variety :)

kek444
A: 

In your sample, you are defining the Storyboard inside a Style as a Resource, but then you are trying to access it as a Window resource. Try moving the Storyboard declaration to Window.Resources, then reference the Storyboard in the Style.

I don't know right off if it will do what you want, but I would start there.

Joel Cochran
Alright, I've been running around this forever, but what it boils down to is NameScope. I have a ListBox that I want to trigger the Animation on the TextBlocks, but they are not in the same NameScope, so if I put the StoryBoard in the Windows.Resources, then the ListBox trigger can find the StoryBoard but the StoryBoard cannot find the TargetName of the TextBlocks.If I put the StoryBoard in Grid.Resource (where the TextBlocks are) then the ListBox change trigger can't find the StoryBoard. Is there some naming convention I can use to cross Namescopes?Ugh! Thanks!
LSTayon
+4  A: 

Based on kek444's Xaml-only solution, I present a slightly improved version that doesn't rely on the DataContext of the button and can have multiple triggers.

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    x:Class="WpfApplication1.MainWindow"
    x:Name="Window"
    Title="MainWindow"
    Width="640" Height="480">
    <Window.Resources>
        <UIElement x:Key="OpacityCounter" Opacity="0"/>
        <Style TargetType="TextBlock">
      <Setter Property="Opacity" Value="{Binding Source={StaticResource OpacityCounter}, Path=Opacity}" />
     </Style>
     <Storyboard x:Key="OnClick1">
      <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.Target="{StaticResource OpacityCounter}" Storyboard.TargetProperty="(UIElement.Opacity)">
       <SplineDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
       <SplineDoubleKeyFrame KeyTime="00:00:00.5000000" Value="1"/>
      </DoubleAnimationUsingKeyFrames>
     </Storyboard>
    </Window.Resources>
    <Window.Triggers>
     <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="button1">
      <BeginStoryboard Storyboard="{StaticResource OnClick1}"/>
     </EventTrigger>
     <EventTrigger RoutedEvent="ButtonBase.Click" SourceName="button2">
      <BeginStoryboard Storyboard="{StaticResource OnClick1}"/>
     </EventTrigger>
    </Window.Triggers>

    <Grid x:Name="LayoutRoot">
     <StackPanel>
      <StackPanel Orientation="Horizontal">
       <Button x:Name="button1" Width="131" Height="37" Content="Button 1" Margin="0,0,0,22"/>
       <Button x:Name="button2" Width="131" Height="37" Content="Button 2" Margin="0,0,0,22"/>
      </StackPanel>
      <TextBlock x:Name="textBlock" Height="27" Text="TextBlock 1" TextWrapping="Wrap" />
      <TextBlock x:Name="textBlock1" Height="27" Text="TextBlock 2" TextWrapping="Wrap" />
      <TextBlock x:Name="textBlock2" Height="27" Text="TextBlock 3" TextWrapping="Wrap" />
      <TextBlock x:Name="textBlock3" Height="27" Text="TextBlock 4" TextWrapping="Wrap" />
     </StackPanel>
    </Grid>
</Window>

To use a ListBox as a trigger mechanism (provided you have a ListBox named "listbox1" someplace, add the following to Window.Triggers:

<EventTrigger RoutedEvent="Selector.SelectionChanged" SourceName="listbox1">
    <BeginStoryboard Storyboard="{StaticResource OnClick1}"/>
</EventTrigger>

or to trigger off a specific ListBoxItem, you'll need (where item1 is a named ListBoxItem):

<EventTrigger RoutedEvent="ListBoxItem.Selected" SourceName="item1">
    <BeginStoryboard Storyboard="{StaticResource OnClick1}"/>
</EventTrigger>
SergioL
That's pretty nice! I wanted to avoid creating UIElements as resources, but people should go with what suits them better. However, calling this approach improved based on the fact that it doesn't use DataContext and supports multiple triggers would be somewhat unfair. Using a resource UIElement over DataContext is just a matter of personal preference; and both solutions support multiple triggers just fine. Otherwise, nice work. ;)
kek444
Ok...now to make it slightly more complicated...I'm not really trying to trigger off of a button, but off of a ListBoxItem change from a ListBox that is inside a StackPanel, inside a Border, inside a Grid in the main window. I have been trying this for hours but can't get the right RoutedEvent...all ListBoxItem events give an error about class being sealed or something...
LSTayon
I only called it improved since it doesn't require a separate DataContext for each Trigger mechanism (i.e. the buttons). I didn't mean to offend.
SergioL
It's okay, no offense taken, just wanted to point it out. You should note that multiple DataContexts aren't necessary. I've expanded the example a bit for completeness.
kek444
@unknown(google): The error appears because the assembly isn't built for new resource access. It should work after a Build.
kek444
Okay, this is finally coming together...I was getting an error about event being Sealed when trying to trigger off of the ListBox...turns out this is because I was using that event in the Listbox's declaration...I got rid of that call for now...Thanks all!!!
LSTayon