locked
Binding element to StoryBoard Target which is outside DataTemplate RRS feed

  • Question

  • Hi

    I have a DataGrid and an Image in the window. I'm using DataTemplate for the cell. This datatemplate has a stackpanel which contains a canvas and textblock. The datatemplate is in different xaml file [ Not in the same xaml file where the Datagrid and image lives ]. Hope it is clear. What I want to do is whenever the mouse is on the textblock of the cell, I want to animate the opacity of the Image from 1.0 to 0.5. I dont know how to specify the image as target for the storyboard. This is Datatemplae I have tried :

    <StackPanel>
    	<Canvas>
    <!-- For simplicity I have not mentioned the content -->
    		</Canvas>
    	<TextBlock Text="Helo">
    		<TextBlock.Triggers>
                        <EventTrigger RoutedEvent="Mouse.MouseEnter">                        
                            <BeginStoryboard>
                                <Storyboard>            
    
                                <DoubleAnimation
                                                Storyboard.Target="{Binding ElementName=BillBoard_Image, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Image}}}"
                                                Storyboard.TargetProperty="(Image.Opacity)"
                                                From="1.0" To="0.5" Duration="0:0:0.25"/>
                            </Storyboard>                            
                            </BeginStoryboard>
                        </EventTrigger>                  
    
                    </TextBlock.Triggers>
    	</TextBlock>
    </StackPanel>

    When I tried this, it throwed me an execption saying " 'System.Windows.Data.RelativeSource' value cannot be assigned to property 'RelativeSource' of object 'System.Windows.Data.Binding'. Binding.RelativeSource cannot be set while using Binding.ElementName.". I tried by specifying the Ancestor level as well. But no use...

    I dont know how to specify the target name or target for the storyboard as it is outside the namescope.

    Friday, February 10, 2012 1:39 PM

Answers

  • Hi again Syed,

    Although Vallarasu's method may work, I'd view that as kind of 'hard coding'. It means as soon as you or any subsequent developer starts fiddling with the UI, changing the position of the windows children, it will break. Besides, you only want to generate one animation for this, so best to house it over in the image, or window resources.


    The problem is that you can't gat to the image, as it is not anywhere upstream, through the visual tree, to the Window base. It is on a side branch.

    However all branches point back up to somewhere common, like a containing grid or the Window itself, where you could have a ViewModel attached to the datacontext.


    I always install a framwork like MVVMLite at the start of any projects. So, although I haven't tested it for you, I think my solution would be to use EventToCommand to pass the MouseEnter/MouseLeave events to a RelayCommands in my ViewModel.


    <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Mouse.MouseEnter"> 
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseEnterCommand}" /> 
        </i:EventTrigger> 
    </i:Interaction.Triggers> 
    <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Mouse.MouseLeave"> 
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseLeaveCommand}" /> 
        </i:EventTrigger> 
    </i:Interaction.Triggers> 

    The RelayCommands would set a public property called something like IsMouseOverDataGridItem to true/false.

    Your image on the other branch can then have a DataTrigger, bound to the same ViewModel property and start/stop the animation as the IsMouseOverDataGridItem property changes.


    I haven't time to make you a complete example, but I think that's how I'd do it.
    So I hope that gives you some ideas, or better direction.


    Best regards,
    Pedro

     


    If you find my post helpful, please remember to "Mark As Answer" and/or "Vote as Helpful"

    • Proposed as answer by VallarasuS Saturday, February 11, 2012 5:04 AM
    • Marked as answer by Syed Babu Monday, February 13, 2012 4:58 AM
    Saturday, February 11, 2012 12:06 AM

All replies

  •  Hope the message in the exception is clear that while you explicitly point to an target object, RelativeSource makes no sense. Further the Image is not an ancester of this element and the binding would result in error.

    I hope you can achieve it with FindAncestor, find the ancester of type window and in the target property drill down to the image's opacity. something like "(Window.Children[1])(Image.Opacity)"

    Hope it helps!


    Regards Vallarasu S. FSharpMe.blogspot.com

    Friday, February 10, 2012 7:16 PM
  • Hi again Syed,

    Although Vallarasu's method may work, I'd view that as kind of 'hard coding'. It means as soon as you or any subsequent developer starts fiddling with the UI, changing the position of the windows children, it will break. Besides, you only want to generate one animation for this, so best to house it over in the image, or window resources.


    The problem is that you can't gat to the image, as it is not anywhere upstream, through the visual tree, to the Window base. It is on a side branch.

    However all branches point back up to somewhere common, like a containing grid or the Window itself, where you could have a ViewModel attached to the datacontext.


    I always install a framwork like MVVMLite at the start of any projects. So, although I haven't tested it for you, I think my solution would be to use EventToCommand to pass the MouseEnter/MouseLeave events to a RelayCommands in my ViewModel.


    <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Mouse.MouseEnter"> 
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseEnterCommand}" /> 
        </i:EventTrigger> 
    </i:Interaction.Triggers> 
    <i:Interaction.Triggers> 
        <i:EventTrigger EventName="Mouse.MouseLeave"> 
            <cmd:EventToCommand Command="{Binding Mode=OneWay, Path=MouseLeaveCommand}" /> 
        </i:EventTrigger> 
    </i:Interaction.Triggers> 

    The RelayCommands would set a public property called something like IsMouseOverDataGridItem to true/false.

    Your image on the other branch can then have a DataTrigger, bound to the same ViewModel property and start/stop the animation as the IsMouseOverDataGridItem property changes.


    I haven't time to make you a complete example, but I think that's how I'd do it.
    So I hope that gives you some ideas, or better direction.


    Best regards,
    Pedro

     


    If you find my post helpful, please remember to "Mark As Answer" and/or "Vote as Helpful"

    • Proposed as answer by VallarasuS Saturday, February 11, 2012 5:04 AM
    • Marked as answer by Syed Babu Monday, February 13, 2012 4:58 AM
    Saturday, February 11, 2012 12:06 AM
  • Thanks. I'm able to do with DataTrigger. Now I have an issue.

    As you said the IsMouseOverDataGridItem is set to true when the mouse enters the item and set to false when the mouse leaves the item. And I have done my style for the image like

     <Style x:Key="AnimatedImage_Style" TargetType="{x:Type Image}">
                <Style.Triggers>
                    
                    <DataTrigger Binding="{Binding DoChangeImage, Source={x:Static vm:ViewModelLocator.OptimizeStatic}}" Value="True">
                        <DataTrigger.EnterActions>
                            <BeginStoryboard>
                           
                                <!-- SLIDE OUT ANIMATION -->
                                <Storyboard >
    
                                    <!-- Animate from Left to Right-->
                                    <DoubleAnimation
                                                    Storyboard.TargetProperty="(Image.RenderTransform).(TranslateTransform.X)"
                                                    From="0.0" To="200" Duration="0:0:0.25"/>
    
                                    <!-- Animate to fading out effect by changing the opacity -->
                                    <DoubleAnimation                    
                                                    Storyboard.TargetProperty="(Image.Opacity)"
                                                    From="1.0" To="0.0" Duration="0:0:0.25"/>
                               
                                </Storyboard>
                            </BeginStoryboard>
                        </DataTrigger.EnterActions>
    
                    </DataTrigger>
                </Style.Triggers>
            </Style>

    And my image is given with the above style

     <Image x:Name="BillBoard_Image" Style="{StaticResource AnimatedImage_Style}">

    The issues is I want to do some action once after the storyboard is completed. I'm not able to attach my "Completed"  event of storyboard to a command in the view model.


    So what I did is, I was trying to achieve using attached property as suggested by http://marlongrech.wordpress.com/2009/06/13/animations-and-mvvm/ .

    The attached property is something like

    public class CommandStoryboard
        {
         
            #region Command
    
            /// <summary>
            /// Command Attached Dependency Property
            /// </summary>
            public static readonly DependencyProperty CommandProperty =
                DependencyProperty.RegisterAttached("Command", typeof(ICommand), typeof(CommandStoryboard),
                    new FrameworkPropertyMetadata((ICommand)null,
                        new PropertyChangedCallback(OnCommandChanged)));
    
            /// <summary>
            /// Gets the Command property.  This dependency property 
            /// indicates ....
            /// </summary>
            public static ICommand GetCommand(DependencyObject d)
            {
                return (ICommand)d.GetValue(CommandProperty);
            }
    
            /// <summary>
            /// Sets the Command property.  This dependency property 
            /// indicates ....
            /// </summary>
            public static void SetCommand(DependencyObject d, ICommand value)
            {
                d.SetValue(CommandProperty, value);
            }
    
            /// <summary>
            /// Handles changes to the Command property.
            /// </summary>
            private static void OnCommandChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
            {
                var timeline = d as Timeline;
                if (timeline != null && !timeline.IsFrozen)
                {
                    object param1 = GetCommandParameter(d);
                    timeline.Completed += delegate
                    {
                        ICommand command = GetCommand(d);
                        object param = GetCommandParameter(d);
    
                        if(command != null && command.CanExecute( param ))
                            GetCommand(d).Execute(GetCommandParameter(d));
                    };
                }
            }
    
            #endregion
        }
    So there is a change in my style to have attached property

    <Storyboard csb:CommandStoryboard.Command="{Binding ImageSwitchingCompleteCommand}">

    ImageSwitchingCompleteCommand is the place where I want to do my action once the storyboard completes.

    Now the problem starts. If I use the above attached property it throws an exception "Cannot convert the valye in attribute "stlye" to object of type "System.Windows.Style". Cannot freeze this storyboard timeline tree for use across threads.

    How can I overcome this? Or is there any way to wire the Storyboard completed event to my command in the view model? I'm really stuck...





    Saturday, February 11, 2012 7:48 AM
  • Hi again syed

    If I answered your original thread question I'd appreciate it marked as answer, as this is a new issue for a new thread.

    I haven't time to test your implementation above, but I have to say there are many examples of how to attach to the storyboard completed event from attached properties, code behind or ViewModel. Don't waste too long on one if it's not working for you. Just search for "mvvm animation completed" and you'll find many more ways to try, possibly even just EventToComand again.

    Regards,
    Pedro


    If you find my post helpful, please remember to "Mark As Answer" and/or "Vote as Helpful"

    Saturday, February 11, 2012 10:38 AM