none
Value eines SplineDoubleKeyFrame ändern RRS feed

  • Frage

  • Hallo!

    Ich habe in den Ressourcen eines ControlTemplate ein SplineDoubleKeyFrame, dessen Value ich zur Laufzeit ändern möchte:

    <ControlTemplate TargetType="{x:Type CheckBox}">
        <ControlTemplate.Resources>
            <Storyboard x:Key="OnChecking">
                <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
                    <SplineDoubleKeyFrame x:Name="daOnChecking" KeyTime="00:00:00.3000000" Value="{StaticResource Slider_Switch_Slider_X}"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>

    System.Windows.Media.Animation.Storyboard sb = (System.Windows.Media.Animation.Storyboard)cb.Template.Resources["OnChecking"];       // Storyboard
    System.Windows.Media.Animation.DoubleAnimationUsingKeyFrames da = (System.Windows.Media.Animation.DoubleAnimationUsingKeyFrames)sb.Children[0];
    System.Windows.Media.Animation.SplineDoubleKeyFrame kf = (System.Windows.Media.Animation.SplineDoubleKeyFrame)da.KeyFrames[0];
    kf.SetValue(TranslateTransform.XProperty, 50.0);
    

    Zur Laufzeit wird mir bei der Zuweisung des Value-Wertes (kf.SetValue) eine Fehlermeldung geworfen:

    Für dieses System.Windows.Media.Animation.SplineDoubleKeyFrame-Objekt kann keine Eigenschaft festgelegt werden, da es schreibgeschützt ist.

    Ich habe auch schon probiert, einen neuen SplineDoubleKeyFrame zu erstellen und diesen gegen den alten auszutauschen (da.KeyFrames.Clear(); da.KeyFrames.Add(kf2);) es kommt aber immer die Fehlermeldung: Objekt ist schreibgeschützt.

    Wie kann man denn den Wert ändern???



    • Bearbeitet perlfred Mittwoch, 11. April 2018 06:52
    Mittwoch, 11. April 2018 06:50

Antworten

  • Hi Fred,
    ich kenne Deine  Anwendung nicht. Das Binden der value ist erst einmal kein Problem. Hier mal eine Demo:

    XAML:

    <Window x:Class="WpfApp1CS.Window47"
            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"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            xmlns:local="clr-namespace:WpfApp1CS"
            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
            mc:Ignorable="d"
            Title="Window47" Height="300" Width="300">
      <Window.Resources>
        <local:Window47VM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
          <Label Content="End-Winkel:"/>
          <TextBox Text="{Binding Winkel, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
        </StackPanel>
        <Border Grid.Row="1" BorderBrush="Red" BorderThickness="3" Canvas.Top="100" Canvas.Left="100" Height="100" Width="100">
          <Border.LayoutTransform>
            <RotateTransform x:Name="trans" CenterX="50" CenterY="50"/>
          </Border.LayoutTransform>
          <Border.Triggers>
            <EventTrigger RoutedEvent="MouseLeftButtonDown">
              <BeginStoryboard>
                <Storyboard>
                  <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                                               Storyboard.TargetName="trans" 
                                               Storyboard.TargetProperty="Angle">
                    <SplineDoubleKeyFrame x:Name="daOnChecking" KeyTime="00:00:03.000000" Value="{Binding Winkel}"/>
                    <!--Value="{StaticResource Slider_Switch_Slider_X}"/>-->
                  </DoubleAnimationUsingKeyFrames>
                </Storyboard>
              </BeginStoryboard>
            </EventTrigger>
          </Border.Triggers>
          <Label Content="Text" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
      </Grid>
    </Window>
    

    Dazu der ViewModel:

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
    using System.Windows.Media;
    
    namespace WpfApp1CS
    {
      public class Window47VM : INotifyPropertyChanged
      {
        private double _winkel = 90;
        public double Winkel
        {
          get { return this._winkel; }
          set
          {
            this._winkel = value;
            OnPropertyChanged();
          }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert perlfred Donnerstag, 12. April 2018 15:12
    Donnerstag, 12. April 2018 09:37
  • Hi Fred,
    die Value-Eigenschaft lässt sich ganz einfach beschreiben, z.B. so:

    Auszug aus Deinem XAML:

          <TextBlock.Resources>
            <Storyboard x:Key="OnUnchecking" >
              <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
                <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
              </DoubleAnimationUsingKeyFrames>
            </Storyboard>
            <Storyboard x:Key="OnChecking" >
              <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
                <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="30">
                  <i:Interaction.Behaviors>
                    <local:Window48Behavior/>
                  </i:Interaction.Behaviors>
                </SplineDoubleKeyFrame>
              </DoubleAnimationUsingKeyFrames>
            </Storyboard>
          </TextBlock.Resources>

    Und dazu die Behavior-Klasse ohne große Logik:

      public class Window48Behavior : Behavior<SplineDoubleKeyFrame>
      {
        protected override void OnAttached() => AssociatedObject.Value = 200;
      }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert perlfred Donnerstag, 12. April 2018 15:11
    Donnerstag, 12. April 2018 10:01

Alle Antworten

  • Hi Fred,
    woher nimmst Du die Information, dass ein SplineDoubleKeyFrame-Objekt eine X-Eigenschaft einer TranslateTransform-Klasse hat?

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 11. April 2018 07:56
  • Hallo Peter!

    Die DoubleAnimationUsingKeyFrames verändert (animiert) doch die TranslateTransform.X, deshalb dachte ich, das dies die richtige Property sei... Aber egal welche Property ich auch eintrage, ein Zugriff auf den SplineDoubleKeyFrame scheint generell nicht möglich zu sein?!

    Mittwoch, 11. April 2018 10:19
  • Hi Fred,
    um Dir helfen zu können, solltest Du mal Dein Ziel beschreiben. Werte irgendwelchen Eigenschaften zuweisen, die es nicht gibt, bringt kein Ergebnis (außer Fehler).


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Mittwoch, 11. April 2018 13:58
  • Hallo Peter!

    Ich weiß nicht, wie ich es anders ausdrücken sollte:

    Ich habe in den Ressourcen eines ControlTemplate ein SplineDoubleKeyFrame, dessen Value-Wert ich zur Laufzeit ändern möchte.

    <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetName="slider" Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[3].(TranslateTransform.X)">
        <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="{StaticResource Slider_Switch_Slider_X}"/>
    </DoubleAnimationUsingKeyFrames>


    Für meine Begriffe, interpoliert der SplineDoubleKeyFrame den TranslateTransform.X-Wert (vom Typ Double) vom Start (0) bis zum KeyFrame-Value-Wert.

    Auch ein setzen des Value-Wertes über das Ansprechen der TargetPropertyProperty erzeugt den Schreibschutz-Fehler:

    kf.SetValue(System.Windows.Media.Animation.Storyboard.TargetPropertyProperty, (double)(st.Resources["Slider_Switch_Slider_X"]));

    Eine Eigenschaft, die in der SplineDoubleKeyFrame-Klasse deklariert wurde, die Value-Property, erzeugt den Schreibschutz-Fehler:

    kf.SetValue(System.Windows.Media.Animation.SplineDoubleKeyFrame.ValueProperty, (double)(st.Resources["Slider_Switch_Slider_X"]));







    • Bearbeitet perlfred Donnerstag, 12. April 2018 08:08
    Donnerstag, 12. April 2018 07:51
  • Hallo!

    Ich habe eine kleine Anwendung erstellt, um das Problem nachvollziehbarer darzustellen:

    <Window x:Class="Energieausweis.Window1"
            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"
            xmlns:local="clr-namespace:Energieausweis"
            mc:Ignorable="d"
            Title="Test" Height="150" Width="200" WindowStartupLocation="CenterScreen">
        <Grid>
            <TextBlock x:Name="tbl" Text="ABC" HorizontalAlignment="Center" VerticalAlignment="Center" Background="Yellow" FontSize="20">
                <TextBlock.RenderTransform>
                    <TranslateTransform X="0" Y="0"/>
                </TextBlock.RenderTransform>
                <TextBlock.Resources>
                    <Storyboard x:Key="OnUnchecking" >
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                    <Storyboard x:Key="OnChecking" >
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
                            <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="30"/>
                        </DoubleAnimationUsingKeyFrames>
                    </Storyboard>
                </TextBlock.Resources>
                <TextBlock.Style>
                    <Style TargetType="TextBlock">
                        <Setter Property="Foreground" Value="Black"/>
                        <Style.Triggers>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Trigger.ExitActions>
                                    <BeginStoryboard Storyboard="{StaticResource OnUnchecking}"/>
                                </Trigger.ExitActions>
                                <Trigger.EnterActions>
                                    <BeginStoryboard Storyboard="{StaticResource OnChecking}"/>
                                </Trigger.EnterActions>
                            </Trigger>
                        </Style.Triggers>
                    </Style>
                </TextBlock.Style>
            </TextBlock>
            <Button Content="SplineDoubleKeyFrame-Ändern" Name="btn" Click="btn_Click" VerticalAlignment="Bottom" HorizontalAlignment="Center" />
        </Grid>
    </Window>
    

    using System.Windows;
    
    namespace Energieausweis
    {
        /// <summary>
        /// Interaktionslogik für Window1.xaml
        /// </summary>
        public partial class Window1 : Window
        {
            public Window1()
            {
                InitializeComponent();
            }
    
            private void btn_Click(object sender, RoutedEventArgs e)
            {
                System.Windows.Media.Animation.Storyboard sb = (System.Windows.Media.Animation.Storyboard)tbl.Resources["OnChecking"];                              // Storyboard
                System.Windows.Media.Animation.DoubleAnimationUsingKeyFrames da = (System.Windows.Media.Animation.DoubleAnimationUsingKeyFrames)sb.Children[0];     // Animation
                System.Windows.Media.Animation.SplineDoubleKeyFrame kf = (System.Windows.Media.Animation.SplineDoubleKeyFrame)da.KeyFrames[0];                      // KeyFrames
                // Schreibschutz-Fehler!
                // kf.Value = 50;
                // kf.SetValue(System.Windows.Media.Animation.SplineDoubleKeyFrame.ValueProperty, 50.0); 
                // kf.SetValue(System.Windows.Media.TranslateTransform.XProperty, 50.0);
            }
        }
    }

    Ich möchte den Value-Wert des SplineDoubleKeyFrame (kf) zur Laufzeit ändern.

    Ich hoffe, so ist es gut nachvollziehbar.

    Donnerstag, 12. April 2018 09:20
  • Hi Fred,
    ich kenne Deine  Anwendung nicht. Das Binden der value ist erst einmal kein Problem. Hier mal eine Demo:

    XAML:

    <Window x:Class="WpfApp1CS.Window47"
            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"
            xmlns:sys="clr-namespace:System;assembly=mscorlib"
            xmlns:local="clr-namespace:WpfApp1CS"
            xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
            mc:Ignorable="d"
            Title="Window47" Height="300" Width="300">
      <Window.Resources>
        <local:Window47VM x:Key="vm"/>
      </Window.Resources>
      <Grid DataContext="{StaticResource vm}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <StackPanel Orientation="Horizontal">
          <Label Content="End-Winkel:"/>
          <TextBox Text="{Binding Winkel, UpdateSourceTrigger=PropertyChanged}" Width="100"/>
        </StackPanel>
        <Border Grid.Row="1" BorderBrush="Red" BorderThickness="3" Canvas.Top="100" Canvas.Left="100" Height="100" Width="100">
          <Border.LayoutTransform>
            <RotateTransform x:Name="trans" CenterX="50" CenterY="50"/>
          </Border.LayoutTransform>
          <Border.Triggers>
            <EventTrigger RoutedEvent="MouseLeftButtonDown">
              <BeginStoryboard>
                <Storyboard>
                  <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" 
                                               Storyboard.TargetName="trans" 
                                               Storyboard.TargetProperty="Angle">
                    <SplineDoubleKeyFrame x:Name="daOnChecking" KeyTime="00:00:03.000000" Value="{Binding Winkel}"/>
                    <!--Value="{StaticResource Slider_Switch_Slider_X}"/>-->
                  </DoubleAnimationUsingKeyFrames>
                </Storyboard>
              </BeginStoryboard>
            </EventTrigger>
          </Border.Triggers>
          <Label Content="Text" HorizontalAlignment="Center" VerticalAlignment="Center"/>
        </Border>
      </Grid>
    </Window>
    

    Dazu der ViewModel:

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Interactivity;
    using System.Windows.Media;
    
    namespace WpfApp1CS
    {
      public class Window47VM : INotifyPropertyChanged
      {
        private double _winkel = 90;
        public double Winkel
        {
          get { return this._winkel; }
          set
          {
            this._winkel = value;
            OnPropertyChanged();
          }
        }
        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged([CallerMemberName] string propertyName = "") =>
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
      }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert perlfred Donnerstag, 12. April 2018 15:12
    Donnerstag, 12. April 2018 09:37
  • Hi Fred,
    die Value-Eigenschaft lässt sich ganz einfach beschreiben, z.B. so:

    Auszug aus Deinem XAML:

          <TextBlock.Resources>
            <Storyboard x:Key="OnUnchecking" >
              <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
                <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="0"/>
              </DoubleAnimationUsingKeyFrames>
            </Storyboard>
            <Storyboard x:Key="OnChecking" >
              <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Storyboard.TargetProperty="(UIElement.RenderTransform).(TranslateTransform.X)">
                <SplineDoubleKeyFrame KeyTime="00:00:00.3000000" Value="30">
                  <i:Interaction.Behaviors>
                    <local:Window48Behavior/>
                  </i:Interaction.Behaviors>
                </SplineDoubleKeyFrame>
              </DoubleAnimationUsingKeyFrames>
            </Storyboard>
          </TextBlock.Resources>

    Und dazu die Behavior-Klasse ohne große Logik:

      public class Window48Behavior : Behavior<SplineDoubleKeyFrame>
      {
        protected override void OnAttached() => AssociatedObject.Value = 200;
      }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert perlfred Donnerstag, 12. April 2018 15:11
    Donnerstag, 12. April 2018 10:01
  • Hallo Peter!

    Danke, jetzt hast du mich mit zwei völlig anderen Herangehensweisen überrascht :-)) (Ich hatte mich zu sehr an dem SetValue festgebissen...)

    Erst einmal: Beide Lösungen sind in der Lage, die Value-Eigenschaft des SplineDoubleKeyFrame zu setzen!

    Bei beiden Lösungen stoße ich aber auf ganz unterschiedliche Hürden bei der Umsetzung, deshalb möchte ich sie getrennt beantworten.

    1. Lösung Behavior:

    AssociatedObject.Value = setzt die Value-Eigenschaft ohne Fehler !

    Da der Wert, der gesetzt werden soll aber von dem Style der Combobox "geholt" werden soll, habe ich das Problem, dass die FindCheckBox-Methode nicht korrekt arbeiten kann, da ich als Objekt diesmal nur ein nicht visuelles Objekt (AssociatedObject = SplineDoubleKeyFrame) übergeben kann:

    private CheckBox FindCheckBox(DependencyObject obj)
    {
        // DependencyObject parent = LogicalTreeHelper.GetParent(obj);
        DependencyObject parent = VisualTreeHelper.GetParent(obj);              // Übergeordnetes Objekt bestimmen.
        if (parent == null) return null;                                        // Wenn es kein übergeordnetes Objekt gibt, abbrechen.
        if (parent.GetType() == typeof(CheckBox)) return (CheckBox)parent;      // Wenn akt. Objekt eine CheckBox ist, diese zurückgeben.
        return FindCheckBox(parent);                                            // Wenn DP-Objekt keine CheckBox war, Funktion rekursiv aufrufen
    }

    VisualTreeHelper.GetParent(obj)  
    
    löst den Fehler:
    
    System.Windows.Media.Animation.SplineDoubleKeyFrame ist kein Visual oder Visual3D
    
    aus.

    Wie könnte ich mich wieder auf das erste visuelle Objekt "hoch" arbeiten???

    Diese Lösung ist für mich die primäre Herangehensweise.

    2. Lösung DP-Binding:

    Wenn das Binding (wie bei dir) nicht in Resourcen definiert wird, funktioniert es, ansonsten kommt folgende Fehlermeldung zur Laufzeit:

    Message=Zeilennummer "37" und Zeilenposition "27" von "Beim Festlegen der Eigenschaft "System.Windows.FrameworkElement.Style" wurde eine Ausnahme ausgelöst.".
    
    InnerException: 
    Message=Diese Storyboard-Zeitleistenstruktur kann nicht für die threadübergreifende Verwendung fixiert werden.

    Das Problem hatte ich schon einmal gepostet, aber wir sind auf keine andere Lösung, als einen festen Wert zu benutzen (was diesmal nicht geht), gekommen.

    Die Lösung ist für mich allerdings sekundär.

    Insgesamt aber ein großes DANKESCHÖN!!!! für deine Lösungen!!!

    Fred.

    Donnerstag, 12. April 2018 15:11
  • Hi Fred,
    das StoryBoard organisiert Animationen in separaten Threads. Um thread-übergreifende Zugriffe zu verwalten, erben alle damit in Verbindung stehenden Klassen von der Freezable-Klasse. Das besondere dieser Klasse ist, dass mit Beginn der Nutzung ein Objekt in den "gefrorenen" Zustand überführt wird (Methode Freeze). Danach (IsFrozen=true) sind Änderungen vieler Eigenschaften nicht mehr möglich.

    Ein Lösung wäre bei Bedarf unterschiedlicher Parameter zur Laufzeit, das StoryBoard per Code neu zu erzeugen, zuzuweisen und ggf. zu starten.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Donnerstag, 12. April 2018 19:32
  • Hallo Peter!

    Danke! für das Erklären der inneren Zusammenhänge!

    Ich werde jetzt noch einmal versuchen, über den Logical Tree an die CheckBox zu gelangen, um mit dem Behavior die Value des SplineDoubleKeyFram's mit den richtigen Werten setzen zu können, aber das ist auch der letzte Versuch. Letztendlich ging es ja nur um die Ableitung eines Styl's dessen Parameter ich unterschiedlich setzen wollte. Deine Lösungsvorschläge sind richtig, stehen aber in keinem vernünftigen Verhältnis zum Aufwand.

    Es war sehr lehrreich und ich habe dank deiner Unterstützung sehr viel über Behavior's gelernt, aber man muss auch einschätzen, dass in diesem Fall die Lösung nicht angestrebt werden sollte :-).   Operation fast geglückt, Patient gestorben.

    Ich werde im zweiten Style die Parameter anders benennen und nicht vom 1. Style ableiten.

    Nochmals vielen Dank für deine unermüdliche Hilfe!!!!

    Fred.

    Freitag, 13. April 2018 08:49