Update/populate DataGrid with data asynchronously - c#

I have checked some of topics and google but can't find proper solution.
I want to make WPF application to download Items information to DataGrid (items columns) with TextBox (item name) using RestApi.
The code is correct and work but there's a problem with async updating DataGrid.
DataTable dt;
public DataTable Dt { get => dt; set { dt = value; dataGridItems.DataContext = Dt.DefaultView; } }
private async void ButtonSearch_Click(object sender, RoutedEventArgs e)
{
//buttonSearch.IsEnabled = false;
rest = new RestClass(ClientId, ClientSecret);
Task T = Task.Run(() => SearchItem(rest, textBoxProductName.Text));
T.ContinueWith((t) =>
{
dataGridItems.DataContext = Dt.DefaultView;
//buttonSearch.IsEnabled = true;
}, TaskScheduler.FromCurrentSynchronizationContext());
Above code with small changes (dataGridItems.DataBinding) worked in WinForms without any problems but I can't make it work in WPF application.
private void SearchItem(RestClass Rest, string ItemName)
{
try
{
var x = Rest.GetTokenJ().Result;
ItemsOffersWPF.Rootobject searchResponse = Rest.requestSearchItem(ItemName);
GetItemsCollection(searchResponse);
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
//dataGridItems.DataContext = Dt.DefaultView;
}
I have tried Invoke, InvokeAsync but it makes UI irresponsible which is what I want to avoid.

You should use await instead of T.ContinueWith
await Task.Run(() => SearchItem(rest, textBoxProductName.Text));
dataGridItems.DataContext = Dt.DefaultView;
anything after the "await" will be executed as soon as the Task has finished running.

Ok thanks to you, I finally found solution. It's not perfect but works well.
The problem was propably updating DataTable (Dt property) inside GetItemsCollection method and using textBoxProductName.Text inside await SearchItem function.
// its useless now
//DataTable Dt { get => dt; set { dt = value; dataGridAllegro.DataContext = Dt.DefaultView; } }
private async void ButtonSearch_Click(object sender, RoutedEventArgs e)
{
buttonSearch.IsEnabled = false;
var productName = textBoxProductName.Text; // get Text value before using Task!
await Task.Run(() => SearchItem(productName));
dataGridItems.ItemsSource = dt.DefaultView;
buttonSearch.IsEnabled = true;
}
private async void SearchItem(string ProductName)
{
try
{
var x = rest.GetTokenJ().Result;
ItemsOffersWPF.Rootobject searchResponse = rest.requestSearchItem(ProductName);
GetItemsCollection(searchResponse); // inside update dt not property DataTable Dt { get => dt; set { dt = value; dataGridAllegro.DataContext = Dt.DefaultView; } }
// = exception using another thread UI
}
catch(Exception ex)
{
MessageBox.Show(ex.Message);
}
}

Related

Is there a way for reading data asynchronously for datagridview in c#?

i am trying to fill datagridview datasourece asynchronously in entity framework, but it shows me cross-thread error and it does not work, does anybody know how?
thank you very much
private async Task FillData()
{
await Task.Run(() =>
{
Model.AsyncTestDBEntities db = new Model.AsyncTestDBEntities();
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = db.Table_1.ToList();
});
}
when i call the above method it does not work
private async void button1_Click(object sender, EventArgs e)
{
await FillData();
}
Use ToListAsync instead of Task.Run().
private async Task FillData()
{
var db = new Model.AsyncTestDBEntities();
var list = await db.Table_1.ToListAsync();
dataGridView1.AutoGenerateColumns = false;
dataGridView1.DataSource = list;
}

How to stop a method (triggered by button click) in WPF

I have a private async void Button_Click method in my WPF which runs a very complicated SQL query which can run for several minutes.
I wish the user can stop this method by clicking another button.
My code is like this:
public partial class MainWindow : Window
{
private async void Button_Click(object sender, RoutedEventArgs e)
{
string SQL_Query= " a very long and complicated SQL query ... "
SqlCommand SQL_Query_cmd = new SqlCommand(SQL_Query, conn);
DataTable dt = new DataTable();
await Task.Run(() => {
using (SqlDataAdapter a = new SqlDataAdapter(SQL_Query_cmd))
{ a.Fill(dt);}
});
}
}
I read about BackgroundWorker in this link How to use WPF Background Worker.
But didn't understand how to integrate it into my code. I think, my "filling datatable" code is already asynchronous but I don't know how to stop it. Assume that the button which is going to end this method is called stop_btn and its Click method is called cancelButton_Click.
Please please please write your answer in a post, rather than comments. I will be greatly thankful.
Here is how you could use the IDbCommand.Cancel method and a CancellationTokenSource, to perform cancellation both on the server side and on the client side.
private IDbCommand _activeSqlCommand;
private CancellationTokenSource _cts;
private async void btnExecute_Click(object sender, RoutedEventArgs e)
{
// The _activeSqlCommand and _cts should be null here.
// Otherwise, you may end up with multiple concurrent executions.
Debug.Assert(_activeSqlCommand == null);
Debug.Assert(_cts == null);
var sqlQuery = "A very long and complicated SQL query...";
var localSqlCommand = new SqlCommand(sqlQuery, _connection);
var localCts = new CancellationTokenSource();
_activeSqlCommand = localSqlCommand;
_cts = localCts;
btnExecute.IsEnabled = false;
btnCancel.IsEnabled = true;
try
{
DataTable dataTable = await AsCancelable(Task.Run(() =>
{
var dt = new DataTable();
using (SqlDataAdapter a = new SqlDataAdapter(localSqlCommand))
a.Fill(dt);
return dt;
}, localCts.Token), localCts.Token);
// Here use the dataTable to update the UI
}
catch (OperationCanceledException) { } // Ignore
catch (SqlException ex) when (ex.ErrorCode == CANCEL_ERROR_CODE) { } // Ignore
finally
{
btnCancel.IsEnabled = false;
btnExecute.IsEnabled = true;
// The _activeSqlCommand and _cts should still have the local values here.
Debug.Assert(_activeSqlCommand == localSqlCommand);
Debug.Assert(_cts == localCts);
_activeSqlCommand = null;
_cts = null;
localCts.Dispose();
}
}
private void btnCancel_Click(object sender, RoutedEventArgs e)
{
_activeSqlCommand?.Cancel();
_cts?.Cancel();
}
private static Task<T> AsCancelable<T>(Task<T> task,
CancellationToken cancellationToken)
{
var cancelable = new Task<T>(() => default, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
You'll have to figure out what kind of exception is thrown by the database server when the execution is canceled, and ignore this exception based on its ErrorCode or some other property.

Display real progress of loading WPF DataGrid instead of .IsIndeterminate

I have managed to get ProgressBar into my WPF form. However my current skill is stuck on using:
ProgressBar.IsIndeterminate = true;
DataGrid1.ItemsSource = await GetDataAsync();
ProgressBar.IsIndeterminate = false;
Any professional ready to describe how to get ProgressBar to display actual loading progress?
Here is my full code:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
try
{
ProgressBar.IsIndeterminate = true;
DataGrid1.ItemsSource = await GetDataAsync();
ProgressBar.IsIndeterminate = false;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
private Task<DataView> GetDataAsync()
{
return Task.Run(() =>
{
string connectionStringDE = "Driver={Pervasive ODBC Client Interface};ServerName=DE875;dbq=#DEDBFS;Uid=DEUsername;Pwd=DEPassword;";
string queryStringDE = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTADR,COMPANYN,COUNTRY,ID,ACTIVE from COMPANY";
string connectionStringFR = "Driver={Pervasive ODBC Client Interface};ServerName=FR875;dbq=#FRDBFS;Uid=FRUsername;Pwd=FRPassword;";
string queryStringFR = "select NRO,NAME,NAMEA,NAMEB,ADDRESS,POSTA,POSTN,POSTADR,COMPANYN,COUNTRY,ID,ACTIVE from COMPANY";
DataTable dataTable = new DataTable("COMPANY");
// using-statement will cleanly close and dispose unmanaged resources i.e. IDisposable instances
using (OdbcConnection dbConnectionDE = new OdbcConnection(connectionStringDE))
{
dbConnectionDE.Open();
OdbcDataAdapter dadapterDE = new OdbcDataAdapter();
dadapterDE.SelectCommand = new OdbcCommand(queryStringDE, dbConnectionDE);
dadapterDE.Fill(dataTable);
}
using (OdbcConnection dbConnectionFR = new OdbcConnection(connectionStringFR))
{
dbConnectionFR.Open();
OdbcDataAdapter dadapterFR = new OdbcDataAdapter();
dadapterFR.SelectCommand = new OdbcCommand(queryStringFR, dbConnectionFR);
var newTable = new DataTable("COMPANY");
dadapterFR.Fill(newTable);
dataTable.Merge(newTable);
}
return dataTable.DefaultView;
});
}
You can't display the actual process unless you know it and you don't unless the API that you are using actually reports the current progress to you.
The OdbcCommand API doesn't so you should stick to using indeterminate progress bars.

Show Loading animation during loading data in other thread

I have an application running with the database. When I load a tables in the datagridview, my form freezes. How to ensure the smooth load animation during loading tables?
I run two threads for animation and load data into the tables, but the animation still does not always work.
private volatile bool threadRun;
private void UpdateTab()
{
// Create panel for animation
Panel loadingPanel = new Panel();
// Label, where the text will change
Label loadingLabel = new Label();
loadingLabel.Text = "Loading";
loadingPanel.Controls.Add(loadingLabel);
this.Controls.Add(loadingPanel);
// thread loading animation
threadRun = true;
Task.Factory.StartNew(() =>
{
int i = 0;
string labelText;
while (threadRun)
{
Thread.Sleep(500);
switch (i)
{
case 0:
labelText = "Loading.";
i = 1;
break;
case 1:
labelText = "Loading..";
i = 2;
break;
default:
labelText = "Loading...";
i = 0;
break;
}
loadingLabel.BeginInvoke(new Action(() => loadingLabel.Text = labelText));
}
});
// thread update DataGridView
Thread update = new Thread(ThreadUpdateTab);
update.Start();
}
private void ThreadUpdateTab()
{
// SQL Query...
myDataGridView1.Invoke(new Action(() => myDataGridView1.DataSource = myDataSet1.Tables[0]));
// ...
myDataGridView10.Invoke(new Action(() => myDataGridView10.DataSource = myDataSet10.Tables[0]));
threadRun = false;
}
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 then 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 transparent loading 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();
}

Net 4.0 with Dapper and Ms Sql 2000

As we know Ms Sql 2000 does not support MultipleActiveResultSets.
Can i use Dapper with async Task without exceptions :
"There is already an open DataReader associated with this Command which must be closed first."
My code example
private async void Form_Load(object sender, EventArgs e){
var sql1 = "select * from Tab1";
var sql2 = "select * from Tab2";
var sql3 = "select * from Tab3";
await Task.Factory.StartNew(() => FillComboBoxWithData(this.cbo1, sql1));
await Task.Factory.StartNew(() => FillComboBoxWithData(this.cbo2, sql2));
await Task.Factory.StartNew(() => FillComboBoxWithData(this.cbo3, sql3));
}
public static async Task FillComboBoxWithData(ComboBox comboBox, string sql{
try
{
var data = await Task.Factory.StartNew(() => SqlConn.Query<IdName>(sql));
var d1 = data.ToNonNullList();
comboBox.DataSource = d1;
comboBox.DisplayMember = "Name";
comboBox.ValueMember = "Id";
comboBox.SelectedItem = null;
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
Thank you.
It looks like your code should work, although I'm very confused as to why you're using Task.Factory.StartNew everywhere; you shouldn't have to do that, and indeed: it is not ideal to do so.
I also can't see how / where SqlConn is defined, so for all I know it is indeed being accessed concurrently. However, to rewrite your code more idiomatically:
private async void Form_Load(object sender, EventArgs e)
{
await FillComboBoxWithData(this.cbo1, "select from Tab1");
await FillComboBoxWithData(this.cbo2, "select from Tab2");
await FillComboBoxWithData(this.cbo3, "select from Tab3");
}
public static async Task FillComboBoxWithData(ComboBox comboBox, string sql)
{
try
{
var data = (await SqlConn.QueryAsync<IdName>(sql)).AsList();
comboBox.DataSource = data;
comboBox.DisplayMember = "Name";
comboBox.ValueMember = "Id";
comboBox.SelectedItem = null;
}
catch (Exception ex)
{
Console.Write(ex.ToString());
}
}
Notice: no Task.Factory.StartNew; it uses the async implementation of the backing service.

Categories

Resources