Show Loading Animation During Loading Data in Other Thread

Show Loading animation during loading data in other thread

When the form is frozen, it means the UI thread is too busy and so even if you try to show a loading animation, it will not animate. You should load data asynchronously.

You can have an async method which returns Task<DataTable> like the GetDataAsync method which you can see in this post. Then call it in an async event handler. In the event handler, first show the loading image, then load data asynchronously and at last hide the loading image.

You can simply use a normal PictureBox showing a gif animation as loading control. Also you may want to take a look at this post to show a trasparent loading image.

Sample Image

public async Task<DataTable> GetDataAsync(string command, string connection)
{
var dt = new DataTable();
using (var da = new SqlDataAdapter(command, connection))
await Task.Run(() => { da.Fill(dt); });
return dt;
}

private async void LoadDataButton_Click(object sender, EventArgs e)
{
loadingPictureBox.Show();
loadingPictureBox.Update();
try
{
var command = @"SELECT * FROM Category";
var connection = @"Your Connection String";
var data = await GetDataAsync(command, connection);
dataGridView1.DataSource = data;
}
catch (Exception ex)
{
//Handle Exception
}
loadingPictureBox.hide();
}

How to manage loading animation thread? - C#

You will need to run the database connection in the background. There are many ways of doing this but with the advent of async/await, this would be the easiest way:

private async void btnLogin_Click (object sender, EventArgs e)
{
loading.Visible = true;
if (await Task.Run(() => ConnectDataBase()))
{
OpenForm2();
This.Close();
}
else MessageBox.Show ("User or password, incorrect");
loading.Visible = false;
}

Note the following changes:

  • I made the method async void to signal to the compiler that we want to use async/await
  • I created a task to run the database connection:

    await Task.Run(() => ConnectDataBase())

    This will return a bool, the result of ConnectDataBase() and will
    essentially do the same thing as the other answer here, just with
    less manual clutter and handling of tasks.

Observe: With background execution like this it is possible for the user to click on the Login button again, while the first click is still executing (and trying to connect to the database). You should take steps to ensure this is not possible, like disabling the login button (and other fields, such as username, password, etc.) while this is executing, something like:

private async void btnLogin_Click (object sender, EventArgs e)
{
btnLogin.Enabled = eUsername.Enabled = ePassword.Enabled = false;
loading.Visible = true;
... rest of method
loading.Visible = false;
btnLogin.Enabled = eUsername.Enabled = ePassword.Enabled = true;
}

Note It would also be better, if possible, to rewrite the ConnectDataBase method to be async in nature as most .NET database connection services nowadays have methods to do this. Without knowing more about what this method does I can only make a general comment.

Here's an example of how to write a SQL Server connection attempt:

public static async Task<bool> ConnectDataBase()
{
var connection = new SqlConnection("connection string");
try
{
await connection.OpenAsync();
// store "connection" somewhere I assume?
return true;
}
catch (SqlException)
{
connection.Dispose();
return false;
}
}

With this change you would change your if-statement in your original method to this:

     if (await ConnectDataBase())

How to show an animated loading form while executing code in Windows Forms C#

If you need an animated progress form, try to use BackgroundWorker class to perform loading in an additional thread:

    public partial class MainForm : Form
{
/// <summary>
/// Some progress form
/// </summary>
WaitForm waitForm = new WaitForm();

/// <summary>
/// https://msdn.microsoft.com/library/cc221403(v=vs.95).aspx
/// </summary>
BackgroundWorker worker = new BackgroundWorker();

public MainForm()
{
InitializeComponent();

worker.DoWork += (sender, args) => PerformReading();
worker.RunWorkerCompleted += (sender, args) => ReadingCompleted();
}

/// <summary>
/// This method will be executed in an additional thread
/// </summary>
void PerformReading()
{
//some long operation here
Thread.Sleep(5000);
}

/// <summary>
/// This method will be executed in a main thread after BackgroundWorker has finished
/// </summary>
void ReadingCompleted()
{
waitForm.Close();
}

private void button1_Click(object sender, EventArgs e)
{
//Run reading in an additional thread
worker.RunWorkerAsync();
//Show progress form in a main thread
waitForm.ShowDialog();
}
}

Show ProgressIndicator during heavy work in GUI thread has to be done

Load the data in a Task, running in a background thread. Display the loading animation (or ProgressIndicator, etc) when you start the task, and remove it when the task finishes.

The basic idea looks like:

TableView<MyDataType> table = new TableView<>();
// set up columns...

Task<List<MyDataType>> loadDataTask = new Task<List<MyDataType>>() {
@Override
protected List<MyDataType> call() throws Exception {
List<MyDataType> data = ... ;
// load data and populate list ...
return data ;
}
};
loadDataTask.setOnSucceeded(e -> table.getItems().setAll(loadDataTask.getValue()));
loadDataTask.setOnFailed(e -> { /* handle errors... */ });

ProgressIndicator progressIndicator = new ProgressIndicator();
table.setPlaceHolder(progressIndicator);

Thread loadDataThread = new Thread(loadDataTask);
loadDataThread.start();

Android: run animation while loading stuff

Your main thread cannot be simultaneously downloading data and running an animation, also you cannot modify the UI from a background thread. As a result, you cannot "run an animation in the background", your animations and all UI tasks must be executed on the main thread while your data is downloading on a background thread.

Android, fortunately, has AsyncTask just for this purpose...

new AsyncTask<String, Integer, List<MyData>>() {
@Override public void onPreExecute() {
startMyAnimation();
}

@Override public List<MyData> doInBackground(String... urls) {
List<MyData> data = new ArrayList<>();
int counter = 0;
publishProgress(counter);
for(String url : urls) {
data.add(getMyDataFromNetwork(url));
publishProgress(++counter);
}
return data;
}

@Override public void onPostExecute(List<MyData> result) {
stopMyAnimation();
updateMyUiWithData(result);
}

@Override public void onProgressUpdate(Integer filesDownloaded) {
updateUiWithFileCount(filesDownloaded);
}
}.execute(url1, url2, url3, etc);

Java FX showing a LoadingScreen while code and other objects are running/drawn

You really shouldn't need an AnimationTimer for something like this. If you are doing the initial loading in a background thread, use a Task. Show a loading screen and hide it in the task's onSucceeded handler. You can create node instances in a background thread as long as they are not part of the scene graph, so while it's not a particularly good design, you can do something like:

Task<Parent> createMainScene = new Task<Parent>() {
@Override
public Parent call() {
Parent root = ... ;
// load data etc., create structure below root
// call updateMessage(...) to update a status message if needed
// call updateProgress(...) to update the progress if needed
// ...
return root ;
}
};

ProgressBar pBar = new ProgressBar();
pBar.progressProperty().bind(createMainScene.progressProperty());
Label statusLabel = new Label();
statusLabel.textProperty().bind(createMainScene.messageProperty());
VBox root = new VBox(5, statusLabel, pBar);
Stage loadingStage = new Stage(new Scene(root));
loadingStage.show();

createMainScene.setOnSucceeded(e -> {
primaryStage.setScene(new Scene(createMainScene.getValue()));
primaryStage.show();
loadingStage.hide();
});

new Thread(createMainScene).start();

A better (more properly-separated) design would be to have the task just load the application data and process it, and return an object encapsulating the data. Then in the onSucceeded handler you would create the UI from the data (which should not take a long time). However, it sounds like you cannot do that with the current code with which you're working.

WPF loading animation on a separate UI thread? (C#)

There is only one UI thread. What you need to do is to load the data in the DataTable on a different thread.

If you want to show progress to the DataTable loading along the way (either directly, or through a ProgressBar or some other mechanism), the BackgroundWorker is a fairly straight-forward way to do that.

UPDATE: Very Simple Background Worker example

Here is a fairly simple example. It adds 100 random numbers to a collection, pausing the thread for a short time between each to simulate a long loading process. You can simply cut and paste this into a test project of your own to see it work.

The thing to notice is that the heavy lifting (the stuff that takes a while) is done in the DoWork, while all UI updates are done in ProgressChanged and RunWorkerCompleted. In fact, a separate list (numbers) is created in the DoWork handler because the global mNumbers collection is on the UI thread, and can't interact in the DoWork handler.

XAML







<Button x:Name="btnGenerateNumbers"
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="Generate Numbers" />

C# Code-Behind

BackgroundWorker bgWorker = new BackgroundWorker();
ObservableCollection<int> mNumbers = new ObservableCollection<int>();

public Window1()
{
InitializeComponent();
bgWorker.DoWork +=
new DoWorkEventHandler(bgWorker_DoWork);
bgWorker.ProgressChanged +=
new ProgressChangedEventHandler(bgWorker_ProgressChanged);
bgWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(bgWorker_RunWorkerCompleted);
bgWorker.WorkerReportsProgress = true;

btnGenerateNumbers.Click += (s, e) => UpdateNumbers();

this.DataContext = this;
}

void bgWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progress.Visibility = Visibility.Collapsed;
lstItems.Opacity = 1d;
btnGenerateNumbers.IsEnabled = true;
}

void bgWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
List<int> numbers = (List<int>)e.UserState;
foreach (int number in numbers)
{
mNumbers.Add(number);
}

progress.Value = e.ProgressPercentage;
}

void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
Random rnd = new Random();
List<int> numbers = new List<int>(10);

for (int i = 1; i <= 100; i++)
{
// Add a random number
numbers.Add(rnd.Next());

// Sleep from 1/8 of a second to 1 second
Thread.Sleep(rnd.Next(125, 1000));

// Every 10 iterations, report progress
if ((i % 10) == 0)
{
bgWorker.ReportProgress(i, numbers.ToList<int>());
numbers.Clear();
}
}
}

public ObservableCollection<int> NumberItems
{
get { return mNumbers; }
}

private void UpdateNumbers()
{
btnGenerateNumbers.IsEnabled = false;
mNumbers.Clear();
progress.Value = 0;
progress.Visibility = Visibility.Visible;
lstItems.Opacity = 0.5;

bgWorker.RunWorkerAsync();
}


Related Topics



Leave a reply



Submit