Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF Animate property of child without using Name

I'm trying to create a storyboard in XAML that animates a property of one of the child elements of an element which raises an event. But I can't seem to get it to work without using Names, which is something I can't really do in this specific situation.

I'm basically trying something like this (much simplified of course):

   <Canvas>
      <Canvas.Triggers>
         <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <EventTrigger.Actions>
               <BeginStoryboard>
                  <Storyboard>
                     <DoubleAnimation Storyboard.TargetProperty="Children[0].(Canvas.Left)" From="0" To="400" />
                  </Storyboard>
               </BeginStoryboard>
            </EventTrigger.Actions>
         </EventTrigger>
      </Canvas.Triggers>

      <Button Canvas.Left="20" Canvas.Top="20">A</Button>
      <Button Canvas.Left="40" Canvas.Top="20">B</Button>
   </Canvas>

Any ideas on how this could be achieved?

like image 840
PJanssen Avatar asked Jul 29 '13 19:07

PJanssen


2 Answers

Providing that the UIElement you are indexing in the animation exists (i.e. already present on the Canvas) then you can do the following:

<Canvas x:Name="MyCanvas">
    <Button x:Name="btn" Canvas.Left="20" Canvas.Top="20">A</Button>
    <Button Canvas.Left="40" Canvas.Top="20">B</Button>
    <Canvas.Triggers>
        <EventTrigger RoutedEvent="FrameworkElement.Loaded">
            <EventTrigger.Actions>
                <BeginStoryboard>
                    <Storyboard>
                        <DoubleAnimation Storyboard.Target="{Binding ElementName=MyCanvas, Path=Children[0]}"
                                         Storyboard.TargetProperty="(Canvas.Left)" From="0" To="400" />
                    </Storyboard>
                </BeginStoryboard>
            </EventTrigger.Actions>
        </EventTrigger>
    </Canvas.Triggers>
</Canvas>

Notice how I have moved the addition of the Buttons above the Trigger. If the Buttons are below the Trigger as in your question, trying to access Children[0] will throw an ArgumentOutOfRangeException because there are no children at this point.

like image 185
Richard E Avatar answered Oct 11 '22 05:10

Richard E


To use the Storyboard.TargetProperty in the animation, it should always be a dependency property. Children property gets a UIElementCollection of child elements of this Panel (Canvas). Therefore, the following construction Children [n] return UIElement, which should lead to a certain type, to access its dependency property.

This can be done in the code as follows:

Button MyButton = (Button)MyCanvas.Children[0];

MessageBox.Show(MyButton.Width.ToString());

All of these actions missing in the animation by default, this is your construction will not work.

I propose to create animations in the code where this conversion possible.

To demonstrate this, I created a Canvas, in the event Loaded having registered animation. Element number is set via an attached dependency property (of course, the example can be implemented in various ways). Below is my example:

XAML

<Grid>
    <local:MyCanvas x:Name="MyCanvas" local:ClassForAnimation.Children="1">
        <Button Canvas.Left="20" Canvas.Top="20">A</Button>
        <Button Canvas.Left="40" Canvas.Top="20">B</Button>
    </local:MyCanvas>
</Grid>

Code behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }       
}

public class MyCanvas : Canvas 
{
    public MyCanvas() 
    {
        this.Loaded += new RoutedEventHandler(MyCanvas_Loaded);
    }

    private void MyCanvas_Loaded(object sender, RoutedEventArgs e)
    {
        MyCanvas myCanvas = sender as MyCanvas;

        // Get No. of children
        int children = ClassForAnimation.GetChildren(myCanvas);

        // Get current Button for animation
        Button MyButton = (Button)myCanvas.Children[children];

        if (myCanvas != null)
        {
            DoubleAnimation doubleAnimation = new DoubleAnimation();

            doubleAnimation.From = 0;
            doubleAnimation.To = 400;

            MyButton.BeginAnimation(Button.WidthProperty, doubleAnimation);
        }
    }
}

public class ClassForAnimation : DependencyObject
{
    public static readonly DependencyProperty ChildrenProperty;

    public static void SetChildren(DependencyObject DepObject, int value)
    {
        DepObject.SetValue(ChildrenProperty, value);
    }

    public static int GetChildren(DependencyObject DepObject)
    {
        return (int)DepObject.GetValue(ChildrenProperty);
    }

    static ClassForAnimation()
    {
        PropertyMetadata MyPropertyMetadata = new PropertyMetadata(0);

        ChildrenProperty = DependencyProperty.RegisterAttached("Children",
                                                            typeof(int),
                                                            typeof(ClassForAnimation),
                                                            MyPropertyMetadata);
    }       
}

Note: Access to the items in the Canvas should only be done in the event Loaded, or when it ended. Otherwise, the items are not available because they are not loaded.

like image 29
Anatoliy Nikolaev Avatar answered Oct 11 '22 05:10

Anatoliy Nikolaev