How to show animation while the code is being processed in an event in C# WPF

Mojtaba_Hakim 321 Reputation points
2021-08-03T14:23:18.703+00:00

I'm using C# WPF and Blend for Visual Studio 2019 I have a button when user click on it it will get data from the SQL Server database

Also, a made Animation for this button in Blend that will run rotate animation OnPreviewMouseDown

But the problem is when I click the button in one-moment animation is running but suddenly stopped

I think this problem is because of the button on Click event running some code and that prevent to animation continue

Long story short: the animation for the button not working when event click is processin

in Blend :
120215-wt0fo.jpg

XAML :

<Window.Resources>  
    <Storyboard x:Key="RotationBtn">  
        <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="Command4">  
            <EasingDoubleKeyFrame KeyTime="0:0:1" Value="720"/>  
        </DoubleAnimationUsingKeyFrames>  
    </Storyboard>  
</Window.Resources>  
 <Window.Triggers>  
        <EventTrigger RoutedEvent="UIElement.PreviewMouseDown" SourceName="Command4">  
            <BeginStoryboard x:Name="RotationBtn_BeginStoryboard" Storyboard="{StaticResource RotationBtn}"/>  
        </EventTrigger>  
    </Window.Triggers>  
  
    <Grid>  
  
<Button x:Name="Command4" Content="Next Stage" Margin="599,406,304,84"   Width="130" Height="37" FontSize="8" Click="Command4_Click" Background="#FFDDDDDD" BorderBrush="{x:Null}" RenderTransformOrigin="0.5,0.5">  
            <Button.RenderTransform>  
                <TransformGroup>  
                    <ScaleTransform/>  
                    <SkewTransform/>  
                    <RotateTransform/>  
                    <TranslateTransform/>  
                </TransformGroup>  
            </Button.RenderTransform>  
        </Button>  
  
 </Grid>  

CS Code :

 private void Command4_Click(object sender, RoutedEventArgs e)  
     {  
            new Thread(() =>  
     {  
         App.Current.Dispatcher.Invoke((Action)delegate  
         {  
              Storyboard sb = TryFindResource("RotationBtn") as Storyboard;  
              Storyboard.SetTarget(sb, Command4);  
              sb.Begin();  
          });  
     }).Start();  
        var MyQuery = dbms.Database.SqlQuery<DB_Cust>("SELECT * FROM Customers").ToList();  
}  

Result of The result of the upper lines ↑ :
120226-2.gif
as you can see it stop

what I need is something like this simulated :
120231-1.gif

keep animation show when that's runnig this :

  var MyQuery = dbms.Database.SqlQuery<DB_Cust>("SELECT * FROM Customers").ToList();  

My Thread for animation does not work correctly it mean's when I click on the button
in a few moment animation will run but after In a short moment suddenly animation will stop until the code of query for get data from database in Click Event To end

how to fix this I want to show animation until code is processing in an event

Please help and thanks for taking your time for answer this question

Edit : When I use this code :

if(string.IsNullOrEmpty(MyTextBox))  
{  
 //Do somthing  
}  

I Have This Error :

The calling thread cannot access this object because a different thread owns it
and Next I have tried This:

Dispatcher.Invoke(new Action(() =>  
                {  
                    if (string.IsNullOrEmpty(MAH.Text))  
                    {  
                        //Do somthing  
                    }  
                }));  

but again my program will freeze or lock until process this :string.IsNullOrEmpty(MAH.Text) and animation will stop

Please Help?

My Project Link :
kj8BXD2Q

in One Drive :
s!Aq2vCYipSlKYggAypH3MeVMIMV5I

Developer technologies | Windows Presentation Foundation
Developer technologies | C#
{count} votes

Accepted answer
  1. Adnan Dedic 406 Reputation points
    2021-08-07T20:20:14.047+00:00

    Hi,
    Thanks for the link.
    Please find solution attached. Can't post code block because of the WAF2.

    121259-mainwindow.txt

    BR,
    Adnan

    1 person found this answer helpful.
    0 comments No comments

3 additional answers

Sort by: Most helpful
  1. Viorel 122.6K Reputation points
    2021-08-03T19:12:23.64+00:00

    In my opinion, to continue animation indefinitely until the database operation is finished, add an attribute:

    <Storyboard x:Key="RotationBtn" RepeatBehavior="Forever">

    Also remove the trigger because you will start the animation programmatically.

    The lengthy database operation can be started as a background task. For example:

    private void Command4_Click( object sender, RoutedEventArgs e )
    {
        // start the animation
        {
            Storyboard sb = (Storyboard)FindResource( "RotationBtn" );
            Storyboard.SetTarget( sb, Command4 );
            sb.Begin( );
        }
    
        // start the background database operation
        var t = new Thread( ( ) =>
            {
                var MyQuery = dbms.Database.SqlQuery<DB_Cust>( "SELECT * FROM Customers" ).ToList( );
    
                // stop the animation and display the results
                App.Current.Dispatcher.Invoke( ( ) =>
                {
                    Storyboard sb = (Storyboard)FindResource( "RotationBtn" );
                    sb.Stop( );
                    // . . .
                } );
    
            } )
        {
            IsBackground = true
        };
        t.Start( );
    }
    

    After finishing the database operation, you can stop the animation and access the elements (to display the results, for example) inside the Invoke block.

    1 person found this answer helpful.

  2. Adnan Dedic 406 Reputation points
    2021-08-06T03:07:03.363+00:00

    Hi,
    There is a few problems with your code.

    1) Threading
    All code inside Command4_Click is executing in the main thread, including long running DB query execution.

           private void Command4_Click(object sender, RoutedEventArgs e) {
                // The new thread is created from the main thread.
                new Thread(() =>
                {
                    // Then delegate is invoked in the main thread.
                    App.Current.Dispatcher.Invoke((Action)delegate
                    {
                        // This code block is executing in the main thread.
                        Storyboard sb = TryFindResource("RotationBtn") as Storyboard;
                        Storyboard.SetTarget(sb, Command4);
                        sb.Begin();
                    });
                }).Start();
                // The long running operation in executing in the main thread.
                var MyQuery = dbms.Database.SqlQuery<DB_Cust>("SELECT * FROM Customers").ToList();
            }
    

    2) Storyboard RepeatBehaviour
    The storyboard animation will be played just once. In your case RepeatBehaviour property should be set to "Forever".

        <Window.Resources>
            <Storyboard x:Key="RotationBtn" RepeatBehavior="Forever">
    

    3) EventTrigger
    You are starting and stopping the animation manually and therefore event trigger should be removed.

    Solution (based on your code):
    XAML:

        <Window.Resources>
            <Storyboard x:Key="RotationBtn" RepeatBehavior="Forever">
                <DoubleAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.RenderTransform).(TransformGroup.Children)[2].(RotateTransform.Angle)" Storyboard.TargetName="Command4">
                    <EasingDoubleKeyFrame KeyTime="0:0:1" Value="720"/>
                </DoubleAnimationUsingKeyFrames>
            </Storyboard>
        </Window.Resources>
    
        <Grid>
            <Button x:Name="Command4" Content="Next Stage" Margin="599,406,304,84"   Width="130" Height="37" FontSize="8" Click="Command4_Click" Background="#FFDDDDDD" BorderBrush="{x:Null}" RenderTransformOrigin="0.5,0.5">
                <Button.RenderTransform>
                    <TransformGroup>
                        <ScaleTransform/>
                        <SkewTransform/>
                        <RotateTransform/>
                        <TranslateTransform/>
                    </TransformGroup>
                </Button.RenderTransform>
            </Button>
        </Grid>
    

    CS:

            private async void Command4_Click(object sender, RoutedEventArgs e) {
                // Get storyboard.
                var storyboard = GetStoryboard();
                // Start animation.
                StartAnimation(storyboard, Command4);      
                // Get customers.
                var customers = await GetCustomersAsync();
                // TODO: Process result.
                // Stop animation.
                StopAnimation(storyboard);
            }
    
            private async Task<List<string>> GetCustomersAsync() {
                // Simulate long running operation.
                await Task.Delay(5000);
                return new List<string> {
                    "Customer 1",
                    "Customer 2"
                };
            }
    
            private Storyboard GetStoryboard() 
            {
                return TryFindResource("RotationBtn") as Storyboard;
            }
    
            private void StartAnimation(Storyboard storyboard)   
            {
                Storyboard.SetTarget(storyboard, Command4);
                storyboard.Begin();
            }
    
            private void StopAnimation(Storyboard storyboard)
            {
                storyboard.Stop();
            }
    

    In general it will be better to utilize full potential of WPF and .NET framework.

    • TAP or TPL for asynchronous operations instead of the custom multi-threading,
    • Design patterns like MVVM,
    • Binding and data templates,
    • Triggers instead of the code behind,
    • etc.

    BR,
    Adnan

    1 person found this answer helpful.

  3. Sajad Bagherzadeh 1 Reputation point
    2021-08-03T16:31:37.003+00:00

    You begin the storyboard (at line 9 in CS Code) and never stop it, then this animation start and never stop until you close the application.
    so you can Stop this animation on any event (eg: MouseLeave) :

            private void Command4_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
            {
                new Thread(() =>
                {
                    App.Current.Dispatcher.Invoke((Action)delegate
                    {
                        Storyboard sb = TryFindResource("RotationBtn") as Storyboard;
                        Storyboard.SetTarget(sb, Command4);
                        sb.Stop();
                    });
                }).Start();
            }
    

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.