Reputation: 173
I'm trying to make a Button
that opens/closes some panel using animation on the Height
property of that panel.
Approach is to bind "To" value of the animation to viewmodel property, which is changed in the button's command.
I suppose that it should work like this:
Button
is clickedTargetHeight
to 0; If it is False, then vm sets it to True and TargetHeight
to 128But in reality it works this way:
Button
is clickedHeight
to previously set valueBecause of that, viewmodel is setting target height not for current click on the button, but for next time in future.
What should I change to make it right?
Already tried setting BeginTime
property to delay animation, it didn't help, unfortunately.
Code is as follows:
XAML
<Button Command="{Binding SwitchBottomPanel}">
<Button.Triggers>
<EventTrigger RoutedEvent="Button.Click">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetName="BottomPanel"
Storyboard.TargetProperty="Height"
To="{Binding TargetHeight}"
Duration="0:0:0.3"
BeginTime="0:0:0.3"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Button.Triggers>
</Button>
Viewmodel class:
class MainViewModel : ViewModelBase
{
private double _targetHeight;
private bool _isBottomOpened;
private double _bottomHeightBig = 128;
public double TargetHeight
{
get { return _targetHeight; }
set
{
_targetHeight = value;
RaisePropertyChanged(nameof(TargetHeight));
}
}
public bool IsBottomOpened
{
get { return _isBottomOpened; }
set
{
_isBottomOpened = value;
if (value == true) TargetHeight = _bottomHeightBig;
else TargetHeight = 0;
RaisePropertyChanged(nameof(IsBottomOpened));
}
}
public ICommand SwitchBottomPanel { get; set; }
public MainViewModel()
{
SwitchBottomPanel = new DelegateCommand(() => IsBottomOpened = !IsBottomOpened);
RaisePropertyChanged(nameof(SwitchBottomPanel));
IsBottomOpened = false;
}
}
Upvotes: 2
Views: 813
Reputation: 169200
You could handle the PropertyChanged
event for the view model and start the animation programmatically in the view:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
private MainViewModel ViewModel => DataContext as MainViewModel;
private void OnLoaded(object sender, RoutedEventArgs e) =>
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
private void OnUnloaded(object sender, RoutedEventArgs e) =>
ViewModel.PropertyChanged += ViewModel_PropertyChanged;
private void ViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e) =>
BottomPanel.BeginAnimation(HeightProperty, new DoubleAnimation()
{
To = ViewModel.TargetHeight,
Duration = TimeSpan.FromSeconds(0.3)
});
}
Just because you can implement things like animations in pure XAML, it doesn't mean that you always should. This is an example where it makes perfect sense to implement your view-logic using an expressive programming language like C# instead of using a markup language like XAML.
You might also want to consider specifying the actual height in the view, and bind to the IsBottomOpened
property:
<Grid x:Name="BottomPanel" Background="Yellow" Height="0" Width="200">
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<DataTrigger Binding="{Binding IsBottomOpened}">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Height"
To="128"
Duration="0:0:0.3" />
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
If you do this, you could also replace the Button
element with a ToggleButton
and bind to its IsChecked
property rather than using an IsBottomOpened
source property. Whether you actually need these properties in the view model depends on your requirements.
Upvotes: 1