I made an application for viewing, editing and creating data stored at a SQL-Server. My problem is, that the interactions with the SQL Server can take some time, especially because the program is also used by my colleagues in China and my Server is located in Germany. So I want to show a dialog with a waiting animation while data is loaded. There is no need to go on working with my GUI while loading. My first solution was to make this class:
public class LongTimeAction
{
public void Run(Action MyMethod)
{
try
{
var thread = new Thread(() =>
{
Dispatcher.CurrentDispatcher.BeginInvoke((Action)(() => new MyWindows.CMN_Splash().Show()));
Dispatcher.Run();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true;
thread.Start();
MyMethod();
thread.Abort();
}
catch (Exception exp)
{
new Errorlogger().write_Error(this.GetType().Name, System.Reflection.MethodBase.GetCurrentMethod().Name, exp.Message);
}
}
}
The splash window has an "Knight Rider"-Style Animation made in XAML that starts automatically when the window is loaded:
<Window.Triggers>
<EventTrigger RoutedEvent="Window.Loaded">
<BeginStoryboard>
<Storyboard RepeatBehavior="Forever" Duration="0:0:1.4">
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar1" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.5" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar2" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0.8" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="0.6" KeyTime="0:0:0.1" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.1" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.6" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1.3" />
<LinearDoubleKeyFrame Value="0.8" KeyTime="0:0:1.4" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar3" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0.6" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="0.2" KeyTime="0:0:0.2" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.2" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.7" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1.2" />
<LinearDoubleKeyFrame Value="0.6" KeyTime="0:0:1.4" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar4" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0.4" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.3" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.3" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.8" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1.1" />
<LinearDoubleKeyFrame Value="0.4" KeyTime="0:0:1.4" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar5" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0.2" KeyTime="0:0:0" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.1" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.4" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:0.9" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:1" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.4" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar6" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.5" />
<LinearDoubleKeyFrame Value="0.2" KeyTime="0:0:0.9" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.9" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.3" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar7" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.6" />
<LinearDoubleKeyFrame Value="0.6" KeyTime="0:0:0.8" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.8" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.2" />
</DoubleAnimationUsingKeyFrames>
<DoubleAnimationUsingKeyFrames Storyboard.TargetName="Bar8" Storyboard.TargetProperty="Opacity">
<DiscreteDoubleKeyFrame Value="0" KeyTime="0:0:0" />
<DiscreteDoubleKeyFrame Value="1" KeyTime="0:0:0.7" />
<LinearDoubleKeyFrame Value="0" KeyTime="0:0:1.1" />
</DoubleAnimationUsingKeyFrames>
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</Window.Triggers>
<Canvas Grid.Row="1" HorizontalAlignment="Stretch" Height="100" Width="275">
<TextBlock Canvas.Top="12" Canvas.Left="39" FontSize="32" FontWeight="Bold" Foreground="Gray" Text="Loading Data" />
<TextBlock Canvas.Top="10" Canvas.Left="37" FontSize="32" FontWeight="Bold" Foreground="White" Text="Loading Data" />
<Rectangle Canvas.Top="67" Canvas.Left="15" Name="Rect" Width="245" Height="20" Stroke="White" RadiusY="2" RadiusX="2" StrokeThickness="2" Fill="Black" />
<Rectangle Canvas.Top="70" Canvas.Left="18" Name="Bar1" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="48" Name="Bar2" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="78" Name="Bar3" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="108" Name="Bar4" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="138" Name="Bar5" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="168" Name="Bar6" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="198" Name="Bar7" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
<Rectangle Canvas.Top="70" Canvas.Left="228" Name="Bar8" Height="14" RadiusY="2" RadiusX="2" Fill="Red" HorizontalAlignment="Left" Width="29" />
</Canvas>
This worked well, but not perfect. Sometimes my program completely crashes ("Application is not responding"), so I looked for a better solution. Because there is no Need to interact with my GUI while loading I changed my Program to async-await. Therefore I changed all my Functions that interact with SQL to async. After that, I changed all the functions that call this functions to async and so on. Now at least half of my program is async :-) I also changed my class LongTimeAction like this:
public static class LongTimeAction
{
public static async Task Run(Func<Task> MyMethod)
{
MyWindows.CMN_Splash MySplashWindow = new MyWindows.CMN_Splash();
MySplashWindow.Show();
await MyMethod();
MySplashWindow.Close();
}
}
It's called like this:
await LongTimeAction.Run(VT_ReloadAllAction);
It's running in principle, but the Animation in Splash Screen is not smooth. Also sometimes the program is completely freezing.
Can you please help me find out what's my Problem here?
EDIT:
Now I've downloaded the Extended WPF Toolkit and added it to my MainWindow:
<wpfx:BusyIndicator Name="BusyBar" BusyContent="Loading Data..." />
In my code behind I added this function
public async Task StartLongRunningTask(Func<Task> MyMethod)
{
BusyBar.IsBusy = true;
await MyMethod();
BusyBar.IsBusy = false;
}
which i call like this:
await StartLongRunningTask(TT_ReloadAllAction);
My application works, but the BusyIndicator is not shown. What am I missing?
EDIT2 + 4
I still have the Problem that my program frezes, if I start it and then use some other windows. When I come back to my window then it's completely frozen and I can't even close it. Solved. I had an Problem in my polling soutine that checks the database every view minutes for a special flag. I made it with an DispatcherTimer and this in combination with async seems to be a bit tricky. so I changed this back to sync. Now it works fine.
EDIT3
Here's my surrounding Code:
public ICommand Cmd_OnlyMyItems { get; set; }
Cmd_OnlyMyItems = new CMN_RelayCommand(Execute => CMN_OnlyMyItems(), CanExecute => true);
private async void CMN_OnlyMyItems()
{
//some Code here
await StartLongRunningTask(VT_ApplyFilterAction);
//some Code here
}
private async Task VT_ApplyFilterAction()
{
await DBConnect.VT_Get_FilteredData_V2();
VT_RefreshGrid();
}
public static class DBConnect
{
public async static Task VT_Get_FilteredData_V2()
{
//some Code here
SqlDataReader reader = command.ExecuteReader()
while (await reader.ReadAsync())
{
//some Code here
}
}
}
Now I will take a look at the examples provided in the comment section.