Search code examples
c#wpfdependency-propertiesfreezable

Cannot set a property on object because it is in a read-only state


In my XAML I have the following:

<DataTemplate x:Key="ItemTemplate">
        <DockPanel Width="Auto">
            <Button Click="SelectMovie_Click" DockPanel.Dock="Top">
                <Button.Template>
                    <ControlTemplate >
                        <Image Source="{Binding image}"/>
                    </ControlTemplate>
                </Button.Template>                   
                <Button.Triggers>
                    <EventTrigger RoutedEvent="Button.Click">
                        <BeginStoryboard>
                            <Storyboard>
                                <local:GridLengthAnimation
                                    Storyboard.Target="{Binding ElementName=col2}"
                                    Storyboard.TargetProperty="Width"
                                    Duration="0:0:2"/>
                            </Storyboard>
                        </BeginStoryboard>
                    </EventTrigger>
                </Button.Triggers>
            </Button>
            <TextBlock Text="{Binding title}" HorizontalAlignment="Center" DockPanel.Dock="Bottom"/>
        </DockPanel>
    </DataTemplate>

<Grid Grid.Row="2" >
        <Grid.ColumnDefinitions>
            <ColumnDefinition Name="col1"  Width="{Binding ElementName=root, Path=DataContext.gla.LeftGridWidth}"/>
            <ColumnDefinition Name="col2" Width="{Binding ElementName=root, Path=DataContext.gla.RightGridWidth}"/>
        </Grid.ColumnDefinitions>
         ...
         ...
</Grid>

gla is a GridLengthAnimationObject.

I am getting the above error when I try to set my Dependency Property

public class GridLengthAnimation : AnimationTimeline
{
    public override Type TargetPropertyType
    {
        get
        {
            return typeof(GridLength);
        }
    }

    protected override System.Windows.Freezable CreateInstanceCore()
    {
        return new GridLengthAnimation();
    }

    public GridLengthAnimation()
    {
        LeftGridWidth = new GridLength(7, GridUnitType.Star);
        RightGridWidth = new GridLength(0, GridUnitType.Star);
    }

    public static readonly DependencyProperty LeftGridWidthProperty = DependencyProperty.Register("LeftGridWidth", typeof(GridLength), typeof(GridLengthAnimation));
    public GridLength LeftGridWidth
    {
        get { return (GridLength)this.GetValue(LeftGridWidthProperty); }
        set { this.SetValue(LeftGridWidthProperty, value); }
    }

    public static readonly DependencyProperty RightGridWidthProperty = DependencyProperty.Register("RightGridWidth", typeof(GridLength), typeof(GridLengthAnimation));
    public GridLength RightGridWidth
    {

        get { return (GridLength)this.GetValue(RightGridWidthProperty); }
        set { this.SetValue(RightGridWidthProperty, value); }
    }

    public override object GetCurrentValue(object defaultOriginValue, object defaultDestinationValue, AnimationClock animationClock)
    {
        double rightGridVal = ((GridLength)GetValue(GridLengthAnimation.RightGridWidthProperty)).Value;
        double leftGridVal = ((GridLength)GetValue(GridLengthAnimation.LeftGridWidthProperty)).Value;

        RightGridWidth = rightGridVal == 0 ? new GridLength(3, GridUnitType.Star) : new GridLength(0, GridUnitType.Star);

        return RightGridWidth;
    }        
}

Error occurs here:

 RightGridWidth = rightGridVal == 0 ? new GridLength(3, GridUnitType.Star) : new GridLength(0, GridUnitType.Star);

Stack Trace

System.InvalidOperationException: Cannot set a property on object   'VideoManager.GridLengthAnimation' because it is in a read-only state.
at System.Windows.DependencyObject.SetValueCommon(DependencyProperty dp, Object value,   PropertyMetadata metadata, Boolean coerceWithDeferredReference, Boolean coerceWithCurrentValue,   OperationType operationType, Boolean isInternal)
at System.Windows.DependencyObject.SetValue(DependencyProperty dp, Object value)
at VideoManager.GridLengthAnimation.set_RightGridWidth(GridLength value) in    c:\Users\Giri\Documents\Visual Studio   2013\Projects\VideoManager\VideoManager\GridLengthAnimation.cs:line 47
at VideoManager.GridLengthAnimation.GetCurrentValue(Object defaultOriginValue, Object    defaultDestinationValue, AnimationClock animationClock) in c:\Users\Giri\Documents\Visual Studio   2013\Projects\VideoManager\VideoManager\GridLengthAnimation.cs:line 56
A first chance exception of type 'System.InvalidOperationException' occurred in WindowsBase.dll     

In my LeftGrid I have a number of Buttons. The LeftGrid has a default width of 7* while the RightGrid is initially set to 0* (not visible). When a Button is clicked in the LeftGrid, the RightGrid should expand to a width of 3*. This expansion of the RightGrid should be animated. Finally if the RightGrid is expanded and a button in the LeftGrid is clicked twice in succession, the RightGrid should shrink back to 0*.


Solution

  • The simplest implementation of a GridLengthAnimation could look like shown below. It only adds a To property (like e.g. DoubleAnimation has), but no From or By properties. It can hence only animate a property from its current value to a specified target value.

    public class GridLengthAnimation : AnimationTimeline
    {
        public static readonly DependencyProperty ToProperty =
            DependencyProperty.Register(
                "To", typeof(GridLength), typeof(GridLengthAnimation));
    
        public GridLength To
        {
            get { return (GridLength)GetValue(ToProperty); }
            set { SetValue(ToProperty, value); }
        }
    
        public override Type TargetPropertyType
        {
            get { return typeof(GridLength); }
        }
    
        protected override Freezable CreateInstanceCore()
        {
            return new GridLengthAnimation();
        }
    
        public override object GetCurrentValue(
            object defaultOriginValue, object defaultDestinationValue,
            AnimationClock animationClock)
        {
            var from = (GridLength)defaultOriginValue;
    
            if (from.GridUnitType != To.GridUnitType ||
                !animationClock.CurrentProgress.HasValue)
            {
                return from;
            }
    
            var p = animationClock.CurrentProgress.Value;
    
            return new GridLength(
                (1d - p) * from.Value + p * To.Value,
                from.GridUnitType);
        }
    }
    

    You would use it like this:

    <local:GridLengthAnimation
        Storyboard.Target="{Binding ElementName=col2}"
        Storyboard.TargetProperty="Width"
        Duration="0:0:2" To="3*"/>