silviubogan
silviubogan

Reputation: 3461

How to make this drop overlay's fade-in animation work well?

I have a simple MainWindow with a root Grid that contains Grid and in it a cell with a Button, and the root grid also contains an overlay control (control with partially transparent background and some short text like Drop here...). This full-window overlay shows up with a fade-in animation when the user drags a file over the window. The problem is my animation does not finish, it almost does not start, and repeats itself while the user drags the file over the window.

I am not sure where to look next. The DropOverlay class and XAML looks well. I think the problem is in the d&d event handling. When I use IsHitTestVisible="False" it seems to work better when I do not have an animation.

DropOverlay

XAML

<UserControl x:Class="cs_wpf_test_16.DropOverlay"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
             xmlns:local="clr-namespace:cs_wpf_test_16"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800"
             Style="{DynamicResource BorderStyle1}">
    <UserControl.Resources>
        <Style TargetType="UserControl" x:Key="BorderStyle1">
            <Style.Triggers>
                <Trigger Property="Visibility" Value="Visible">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.5">
                                </DoubleAnimation>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:0.5">
                                </DoubleAnimation>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>
            </Style.Triggers>
        </Style>
    </UserControl.Resources>
    <Border Background="#99000000">
        <Viewbox>
            <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
                        Orientation="Horizontal" Margin="100" IsHitTestVisible="False">
                <TextBlock Foreground="White" FontSize="50" Margin="15,0,0,0" Name="MyTextBlock">Drop Here...</TextBlock>
            </StackPanel>
        </Viewbox>
    </Border>
</UserControl>

Code-behind

Nothing special.

MainWindow

XAML

<Window x:Class="cs_wpf_test_16.MainWindow"
        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:cs_wpf_test_16"
        mc:Ignorable="d"
        Title="MainWindow" Height="209.375" Width="317.969"
        PreviewDragEnter="MyWindow_DragEnter"
        PreviewDragLeave="MyWindow_DragLeave"
        PreviewDrop="MyWindow_Drop"
        AllowDrop="True">
    <Grid>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>

            <Button>Test</Button>
        </Grid>
        <local:DropOverlay Visibility="Collapsed" x:Name="MyDropOverlay"/>
    </Grid>
</Window>

Code-behind

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }

    internal bool _DraggingOver = false;
    internal bool DraggingOver
    {
        get
        {
            return _DraggingOver;
        }
        set
        {
            if (_DraggingOver != value)
            {
                _DraggingOver = value;
                UpdateDropIndicator();
            }
        }
    }

    private void UpdateDropIndicator()
    {
        if (DraggingOver)
        {
            MyDropOverlay.Visibility = Visibility.Visible;
        }
        else
        {
            MyDropOverlay.Visibility = Visibility.Collapsed;
        }
    }

    private void MyWindow_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.FileDrop) &&
            !(e.Source is DropOverlay))
        {
            e.Effects = DragDropEffects.Link;

            var paths = e.Data.
                GetData(DataFormats.FileDrop) as string[];

            string path = paths[0];

            DraggingOver = true;

            e.Handled = true;
        }
        else
        {
            DraggingOver = false;

            //e.Handled = true;
        }
    }

    private void MyWindow_Drop(object sender, DragEventArgs e)
    {
        if (GetVisualParent<DropOverlay>(e.Source) == null)
        {
            e.Handled = true;
            return;
        }

        string[] paths = null;

        if (e.Effects != DragDropEffects.None &&
            e.Data.GetDataPresent(DataFormats.FileDrop))
        {
            paths = e.Data.
                GetData(DataFormats.FileDrop) as string[];
        }

        DraggingOver = false;

        if (paths != null)
        {
            MessageBox.Show(this, "Dropped.");
            e.Handled = true;
        }
    }

    private void MyWindow_DragLeave(object sender, DragEventArgs e)
    {
        if (GetVisualParent<DropOverlay>(e.Source) == null)
        {
            e.Handled = true;
            return;
        }

        Point p = Mouse.GetPosition(this);
        if (p.X < 0 || p.Y < 0 ||
            p.X > ActualWidth || p.Y > ActualHeight)
        {
            DraggingOver = false;

            e.Handled = true;
        }
    }

    public static T GetVisualParent<T>(object childObject) where T : System.Windows.Media.Visual
    {
        var child = childObject as System.Windows.DependencyObject;
        // iteratively traverse the visual tree
        while ((child != null) && !(child is T))
        {
            child = System.Windows.Media.VisualTreeHelper.GetParent(child);
        }
        return child as T;
    }
}

Expected result: the fade-in animation runs and finishes one time when the user starts dragging a file over the window.

Actual result: the fade-in animation does not finish, it starts over and over again and runs just up to some point where it is hardly visible that the overlay is there. Sometimes when I leave the window with the mouse while dragging the overlay fades-in nicely (but not in the good moment), sometimes not.

Screenshots

screenshot with the problem

screenshot with the drop overlay

Upvotes: 0

Views: 252

Answers (1)

Zer0
Zer0

Reputation: 1166

Found it just delete your else case in MyWindow_DragEnter

copy the code below and paste instead of your MyWindow_DragEnter method.

in case you are lazy :)

    private void MyWindow_DragEnter(object sender, DragEventArgs e)
    {
        if (e.Data.GetDataPresent(DataFormats.FileDrop) &&
            !(e.Source is DropOverlay))
        {
            e.Effects = DragDropEffects.Link;

            var paths = e.Data.
                GetData(DataFormats.FileDrop) as string[];

            string path = paths[0];

            DraggingOver = true;

            e.Handled = true;
        }

    }

FOR FADE IN/OUT ANIMATIONS

The problem is your UserControl is set to Collapsed while animating => we need "connected animations" (one animation after another)

in our case we need some independent boolen: i took Selector.IsSelected.

Change your UpdateDropIndicator method


        private void UpdateDropIndicator()
        {
            if (DraggingOver)
            {
                Selector.SetIsSelected(MyDropOverlay, true);
                //   MyDropOverlay.Visibility = Visibility.Visible;
            }
            else
            {
                Selector.SetIsSelected(MyDropOverlay, false);
                //MyDropOverlay.Visibility = Visibility.Collapsed;
            }
        }

and your xaml animation


             <Trigger Property="Selector.IsSelected" Value="True">
                    <Trigger.EnterActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" 
                                                 From="0" 
                                                 To="1" 
                                                 Duration="0:0:0.5"/>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
                                                               Duration="0:0:0">
                                    <DiscreteObjectKeyFrame Value="{x:Static Visibility.Visible}" />
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.EnterActions>
                    <Trigger.ExitActions>
                        <BeginStoryboard>
                            <Storyboard>
                                <DoubleAnimation Storyboard.TargetProperty="Opacity" 
                                                 From="1" 
                                                 To="0" 
                                                 Duration="0:0:0.5"/>
                                <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="Visibility"
                                                               Duration="0:0:0.5">
                                    <DiscreteObjectKeyFrame Value="{x:Static Visibility.Collapsed}" />
                                </ObjectAnimationUsingKeyFrames>
                            </Storyboard>
                        </BeginStoryboard>
                    </Trigger.ExitActions>
                </Trigger>

Upvotes: 1

Related Questions