Laden...

Databinding: Animation zwischen altem und neuem Wert

Erstellt von Big Al vor 12 Jahren Letzter Beitrag vor 12 Jahren 2.704 Views
B
Big Al Themenstarter:in
342 Beiträge seit 2006
vor 12 Jahren
Databinding: Animation zwischen altem und neuem Wert

Hallo,
was ich mich schon oft gefragt und oft gegoogelt habe, wofür ich aber noch keine Lösung gefunden habe, ist folgendes Problem:
Wenn ich eine Eigenschaft eines Controls, z.B. die Breite eines Buttons, an ein DependencyProperty in meinem ViewModel o.ä. binde und sich der Wert im ViewModel ändert, dann wird die Änderung selbstverständlich direkt zu dem Button durchgereicht und dieser bekommt die neue Breite. Wie kann ich aber diese Änderung animieren, sodass der Button von der alten Größe auf die neue wächst bzw. schrumpft? Man findet im Internet nur Beispiele, bei denen zwar eine Animation abläuft, diese aber nicht den alten Wert kennt.
Ein Beispiel hier von Josh Smith:
Orchestrating Animated Transitions between View and ViewModel
Er benutzt aber nur eine Fade-In und Fade-Out Animation für die Transitions. Wie sieht es mit konkreten Start- und Endwerten aus? Kennt da jemand eine gute Lösung? Vielleicht ein zwischengeschaltetes Objekt, welches beide Werte enthält, an die man dann die To und From Properties der Animation binden kann?
Vielen Dank,
Big Al

//Edit: Oder wäre es vielleicht der richtige Weg, den Wert im ViewModel zu animieren? Gehören Animationen da rein?
//Edit 2: Hier ist noch eine Lösung von Josh Twist, die mir aber nicht so gut gefällt, da man jedes gebundene Control noch einmal in ein UserControl verpacken muss:
Animating when data changes Part 2

Da man Spatzen nicht mit Kanonen jagt, sollte man auch nicht mit Computern auf Spatzenhirne losgehen.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo,

das Stichwort lautet "Storyboard".

Wenn es nicht im Link zu Josh Smith's Beitrag ein Spiel* ist gehört eine Animation nicht in das ViewModel. Die Animation ist "Darstellung" und keine "(Daten)Logik". Dem ViewModel sollte egal sein was mit seinen Daten passiert - ob UI, UNit-Test oder sonst was. Deshalb gehört das da nicht rein.

* oder sonst was wo die Animation Kernbestandteil ist

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

B
Big Al Themenstarter:in
342 Beiträge seit 2006
vor 12 Jahren

Hallo gfoidl,
ich denke nicht, dass ein Storyboard die Lösung für dieses Problem ist, denn es hat keinen Zugriff auf den alten Wert.
Hier noch einmal das Szenario:
Ich habe eine Button, dessen Width-Property an ein DependencyProperty des ViewModels gebunden ist.
Nun ändert sich das Property im ViewModel und das wird auch gleich vom Button übernommen. Jetzt kann ich zwar über einen DataTrigger bei einer Änderung eine Animation abspielen, aber diese kennt ja nur die aktuelle, also neue, Breite des Buttons. Wie soll ich denn nun eine Animation so abspielen, dass der Button die Breite animiert?
Soll ich hier ganz auf das Binding verzichten und nur den DataTrigger benutzen, sodass die Werte von der Animation an die des ViewModels angepasst werden?
Big Al

Da man Spatzen nicht mit Kanonen jagt, sollte man auch nicht mit Computern auf Spatzenhirne losgehen.

5.742 Beiträge seit 2007
vor 12 Jahren

Hallo Big Al,

ich habe da mal was gebastelt:

Folgendes ViewModel:


public sealed class Foo
    : INotifyPropertyChanged
{
    private int _width;

    public ICommand NewWidthCommand
    {
        get;
        private set;
    }
    public int Width
    {
        get
        {
            return this._width;
        }
        set
        {
            if (this.Width == value)
                return;

            this._width = value;
            this.OnPropertyChanged("Width");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public Foo()
    {
        this.NewWidthCommand = new DelegateCommand(this.NewWidth);

        this.Width = 150;
    }
    private void NewWidth()
    {
        this.Width = new Random().Next(100, 500);
    }

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
}

Hier soll Width an die Breite an eines Buttons gebunden werden und dieser entsprechend animiert.

Meine Lösung verwendet hierzu ein Behavior - im XAML sieht das dann so aus:


<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:Interactivity="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:WpfApplication1="clr-namespace:WpfApplication1" Title="MainWindow" Height="350" Width="525">
  <StackPanel>
    <Rectangle Fill="Red" Height="150" Margin="20">
      <Interactivity:Interaction.Behaviors>
        <WpfApplication1:DoubleAnimated Property="Rectangle.Width" Value="{Binding Path=Width}" />
      </Interactivity:Interaction.Behaviors>
    </Rectangle>
    <Button Command="{Binding Path=NewWidthCommand}" Margin="20" Content="Neue Breite" />
  </StackPanel>
</Window>

Und natürlich noch das Behavior:


public abstract class Animated<T>
    : Behavior<FrameworkElement>
{
    public static readonly DependencyProperty ValueProperty = DependencyProperty.Register("Value", typeof(T), typeof(Animated<T>), new UIPropertyMetadata(default(T), ValueChanged));
        
    private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((Animated<T>) d).OnValueChanged((T) e.OldValue, (T) e.NewValue);
    }

    public DependencyProperty Property
    {
        get;
        set;
    }
    public T Value
    {
        get
        {
            return (T) GetValue(ValueProperty);
        }
        set
        {
            SetValue(ValueProperty, value);
        }
    }
        
    private void OnValueChanged(T oldValue, T newValue)
    {
        Storyboard storyboard = new Storyboard();

        storyboard.Duration = TimeSpan.FromMilliseconds(500);

        Timeline animation = this.CreateAnimation(oldValue, newValue, storyboard.Duration);
        Storyboard.SetTarget(animation, this.AssociatedObject);
        Storyboard.SetTargetProperty(animation, new PropertyPath(this.Property));
            
        storyboard.Children.Add(animation);

        this.AssociatedObject.BeginStoryboard(storyboard);
    }
    protected override void OnAttached()
    {
        this.AssociatedObject.SetValue(this.Property, this.Value);
    }

    protected abstract Timeline CreateAnimation(T from, T to, Duration duration);
}

public sealed class DoubleAnimated
    : Animated<double>
{
    protected override Timeline CreateAnimation(double from, double to, Duration duration)
    {
        return new DoubleAnimation(from, to, duration);
    }
}

B
Big Al Themenstarter:in
342 Beiträge seit 2006
vor 12 Jahren

Hallo winsharp93,
Respekt, das ist eine ziemlich clevere Lösung! So werde ich das benutzen.
Big Al

Da man Spatzen nicht mit Kanonen jagt, sollte man auch nicht mit Computern auf Spatzenhirne losgehen.

U
1.688 Beiträge seit 2007
vor 12 Jahren

Animationen gehen doch immer vom aktuellen (alten) Wert aus, solange man kein "From" angibt.