How to execute some code before removing an element from the visual tree?

Viewed 44

I am trying to implement some fade-in and fade-out animations for a user control in WPF. For the fade-in animation I was able to use the Loaded event to accomplish that.

    public sealed partial class NowPlayingView : UserControl
    {
        public Duration AnimationDuration
        {
            get { return (Duration)GetValue(AnimationDurationProperty); }
            set { SetValue(AnimationDurationProperty, value); }
        }

        public static readonly DependencyProperty AnimationDurationProperty =
            DependencyProperty.Register("AnimationDuration", typeof(Duration), typeof(NowPlayingView), new PropertyMetadata(Duration.Automatic));

        public NowPlayingView()
        {
            Opacity = 0;
            InitializeComponent();
            Loaded += NowPlayingView_Loaded;
            Unloaded += NowPlayingView_Unloaded;

        }

        private void NowPlayingView_Unloaded(object sender, RoutedEventArgs e)
        {
            DoubleAnimation animation = new(1.0, 0.0, AnimationDuration);
            BeginAnimation(OpacityProperty, animation);
        }

        private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
        {
            DoubleAnimation animation = new (0.0, 1.0, AnimationDuration);
            BeginAnimation(OpacityProperty, animation);
        }
    }

I attempted to use the Unloaded event for the fade-out effect only to find out that the event is fired after the UserControl is removed from the visual tree (when the UserControl is no longer visible or accessible). Is there a way to run some code right before the UserControl "closes", something like the OnClosing event of a Window?

EDIT:

For a bit more context, the UserControl acts as a component of a more complex window. It is activated whenever the Property NowPlayingViewModel is not null and deactivated when null (which I do in order to hide the UserControl). It is when I set the ViewModel to null that I want to run the fade-out animation and I would like to keep the code-behind decoupled from other ViewModel logic.

<!-- Now playing View-->
<ContentControl Grid.RowSpan="3" Grid.ColumnSpan="2" Content="{Binding NowPlayingViewModel}">
    <ContentControl.Resources>
        <DataTemplate DataType="{x:Type viewmodels:NowPlayingViewModel}">
            <views:NowPlayingView AnimationDuration="00:00:00.8" />
        </DataTemplate>
    </ContentControl.Resources>
</ContentControl>

From my testing, I couldn't find any good solution to this so far, though I am open to suggestions that lead to similar behavior.

1 Answers

There is no Closing event in UserControl.. but you can get the parent window when UserControl is loaded and implement the fade-out behavior there..

First, Remove Unloaded += NowPlayingView_Unloaded;

Then, modify the Loaded code a bit..

private Window ParentWindow
{
    get
    {
        DependencyObject parentDepObj = this;
        do
        {
            parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
            if (parentDepObj is Window parentWindow) return parentWindow;
        } while (parentDepObj != null);

        return null;
    }
}

private void NowPlayingView_Loaded(object sender, RoutedEventArgs e)
{
    DoubleAnimation animation = new(0.0, 1.0, AnimationDuration);
    BeginAnimation(OpacityProperty, animation);
    var parentWindow = this.ParentOfType<Window>();
    parentWindow.Closing += WindowClosing;
}

private void WindowClosing(object sender, CancelEventArgs args)
{
    var pw = ParentWindow;
    pw.Closing -= WindowClosing;
    args.Cancel = true;
    var anim = new(1.0, 0.0, AnimationDuration);
    anim.Completed += (s, _) => pw.Close();
    BeginAnimation(OpacityProperty, anim);
}

Optional Note. You could replace the getter of ParentWindow property with a simple call

private Window ParentWindow => this.ParentOfType<Window>();

Where ParentOfType is an extension function in some public static class Utilities..

public static T ParentOfType<T>(this DependencyObject child) where T : DependencyObject
{
    var parentDepObj = child;
    do
    {
        parentDepObj = VisualTreeHelper.GetParent(parentDepObj);
        if (parentDepObj is T parent) return parent;
    } while (parentDepObj != null);

    return null;
}
Related