Im trying to populate a datagridview from a query of sql but it takes a long time , what im trying to do is show a .gif "loading" meanwhile is populating the grid, im using threads but the .gif freezes , and if I use the CheckForIllegalCrossThreadCalls = false; the datagridview doesnt load the scroll bar acts weird. here is my code
delegate void CambiarProgresoDelegado();
BUTTON CLICK
private void btn_busca_Click(object sender, EventArgs e)
{
pictureBox1.Visible = true;
thread= new Thread(new ThreadStart(ejecuta_sql));
thread.Start();
}
method
private void ejecuta_sql()
{
if (this.InvokeRequired)
{
CambiarProgresoDelegado delegado = new CambiarProgresoDelegado(ejecuta_sql);
object[] parametros = new object[] { };
this.Invoke(delegado, parametros);
}
else
{
myConnection.Open();
SqlCommand sql_command2;
DataSet dt2 = new DataSet();
sql_command2 = new SqlCommand("zzhoy", myConnection);
sql_command2.CommandType = CommandType.StoredProcedure;
sql_command2.Parameters.AddWithValue("#FechaIni", dateTimePicker1.Value.ToShortDateString());
sql_command2.Parameters.AddWithValue("#FechaFin", dateTimePicker2.Value.ToShortDateString());
SqlDataAdapter da2 = new SqlDataAdapter(sql_command2);
da2.Fill(dt2, "tbl1");
grid_detalle.DataSource = dt2.Tables[0];
myConnection.Close();
pictureBox1.Visible = false;
}
and the .gif freezes until the thread finish his job.
You created a thread but then immediately made the code switch back to the main UI thread with Invoke(), negating any benefits of making the thread in the first place.
Run the query on the other thread, then Invoke() just the part that updates the UI:
private string FechaIni;
private string FechaFin;
private void btn_busca_Click(object sender, EventArgs e)
{
btn_busca.Enabled = false;
pictureBox1.Visible = true;
FechaIni = dateTimePicker1.Value.ToShortDateString();
FechaFin = dateTimePicker2.Value.ToShortDateString();
thread = new Thread(new ThreadStart(ejecuta_sql));
thread.Start();
}
private void ejecuta_sql()
{
myConnection.Open();
SqlCommand sql_command2;
DataSet dt2 = new DataSet();
sql_command2 = new SqlCommand("zzhoy", myConnection);
sql_command2.CommandType = CommandType.StoredProcedure;
sql_command2.Parameters.AddWithValue("#FechaIni", FechaIni);
sql_command2.Parameters.AddWithValue("#FechaFin", FechaFin);
SqlDataAdapter da2 = new SqlDataAdapter(sql_command2);
da2.Fill(dt2, "tbl1");
myConnection.Close();
this.Invoke((MethodInvoker)delegate {
grid_detalle.DataSource = dt2.Tables[0];
pictureBox1.Visible = false;
btn_busca.Enabled = true;
});
}
You may consider changing your approach, especially if you are doing a lot of GUI updates from your background threads. Reasons:
UI update takes time and it slows down background processing as you have to lock
too many updates coming from the background threads will overwhelm the UI and might cause freezes.
you probably don't want to update GUI every millisecond or so
What I prefer is to do polling the background thread data instead. Set up GUI timer for say 300ms, then check if there is any data ready to be updated, then do quick update with proper locking.
Here is code example:
private string text = "";
private object lockObject = new object();
private void MyThread()
{
while (true)
{
lock (lockObject)
{
// That can be any code that calculates text variable,
// I'm using DateTime for demonstration:
text = DateTime.Now.ToString();
}
}
}
private void timer_Tick(object sender, EventArgs e)
{
lock(lockObject)
{
label.Text = text;
}
}
Note, that while the text variable is updated very frequently the GUI still stays responsive. If, instead, you update GUI on each "text" change, your system will freeze.
Related
Im working with WinForms.
I want to populate ListView from background thread but when im Invoking listview my program stops and shows an error. The error is "Cannot acces a disposed object. Object name is: ListView." And when i put this method
lvValidate.Invoke((Action)delegate
{
lvValidate.Items.Add(listitem);
});
in a try-catch block my program starts lagging. I dont know where is the problem,but my Invoke method is:
static class Intercept
{
internal static void Invoke(this Control control, Action action)
{
control.Invoke(action);
}
}
The error only showing when i close the form and open another form (in the same program). In the Form which contains the ListView the data is unreadable and it seems loads a thousands times.
Here's what my DoWork,ProgressChanged,RunWorkerCompleted event does.
private void bgwLoad_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
string commandText = "SELECT * FROM works";
MySqlCommand command = new MySqlCommand(commandText, connection);
MySqlDataAdapter da = new MySqlDataAdapter(command);
connection.Close();
connection.Open();
reader = command.ExecuteReader();
connection.Close();
DataTable dt = new DataTable();
da.Fill(dt);
for (int i = 0; i < dt.Rows.Count; i++)
{
DataRow dr = dt.Rows[i];
ListViewItem listitem = new ListViewItem(dr["ID"].ToString(), dr["Date"].ToString());
listitem.SubItems.Add(dr["Date"].ToString());
listitem.SubItems.Add(dr["Name"].ToString());
listitem.SubItems.Add(dr["WorkNumber"].ToString());
listitem.SubItems.Add(dr["WorkCode"].ToString());
listitem.SubItems.Add(dr["CoreThread"].ToString());
listitem.SubItems.Add(dr["Tech"].ToString());
listitem.SubItems.Add(dr["From"].ToString());
listitem.SubItems.Add(dr["To"].ToString());
listitem.SubItems.Add(dr["Validate"].ToString());
listitem.SubItems.Add(dr["Validate2"].ToString());
lvValidate.Items.Add(listitem);
}
}
private void bgwLoad_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
}
private void bgwLoad_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
{
picLoading.Visible = false;
}
Try the following:
Dispatcher.CurrentDispatcher.Invoke(() => {
lvValidate.Items.Add(listitem);
});
EDIT:
Or Try this:
public static void AddItem(ListItem listitem)
{
if (lvValidate.InvokeRequired)
{
AddItemDelegate d = new AddItemDelegate (AddItem);
lvValidate.Invoke(d, new object[] { listitem });
}
else
{
lvValidate.Invoke(new Action(() =>
{
lvValidate.Items.Add(listitem);
}));
}
}
delegate void AddItemDelegate(ListItem listitem);
Then call:
AddItem(listitem);
According to your comments, you are trying to retrieve Data from a DB using a DataAdapter, then handing out the Data piecemeal. This will not work for several reasons:
DataAdapter
The DataAdapter Classes all have in common that they only work, while the DBConnection is actively open. That is why you get "Cannot acces a disposed object". Because by the time you try to use it, it the connection is already Disposed. And disposing is not a thing you should ever delay. Or split up at all. Keep the using (that you hopefully got) right where it is, inside the DoWork().
For that reason you always have to copy the data of the DataAdapter into a non-Adapter collection. Really any old list would do. This will temporarily double the Memory load and might procude some stuff for the GC to clean up, but is really they only adviseable way.
Bulk Writes only in Completed
While you can theoretically hand out Partial Reads/process results via Progress Reporting, this is not adviseable to try. Writing a GUI has a massive overhead. The first and only time I did that, I ended up locking up my GUI thread with Write and Draw Operations. It looked like I had never done Multitasking. Updating a Progress bar is just about low cost enough to never really cause issues.
The default pattern is to only over write relevant amount of data after you got it all.
If you run into a exception or cancel, the pattern is to asume that all data is faulty and not have it on the UI.
I like to call the BackgroundWorker "Multitasking/-Threading Training Wheels". They teach you all those things. The firt part, by making the handing out akward. The second part, by actually throwing a Exception if you try to use the Result in invalid cases.
You propably retreive too much
Perhaps the most common mistake with DB's, is trying to retrieve a lot of data to then do processing or filtering in the Client. A common mistake, so avoid it.
There is a limit to how much data a User can process whatever you would define as 1 page. Never more then 100 Data Fields at once is my advice. If you got do Filtering, Pagination or the sort, always do it in the Query. Moving this stuff to the client moves a lot of unesessary data over the Network to then be slower at processing/Filtering then the DB would ever have been.
Example code
This actually is my first BGW Project. I updated it a bit over the years, but the bulk of it is still valid:
#region Primenumbers
private void btnPrimStart_Click(object sender, EventArgs e)
{
if (!bgwPrim.IsBusy)
{
//Prepare ProgressBar and Textbox
int temp = (int)nudPrim.Value;
pgbPrim.Maximum = temp;
tbPrim.Text = "";
//Start processing
bgwPrim.RunWorkerAsync(temp);
}
}
private void btnPrimCancel_Click(object sender, EventArgs e)
{
if (bgwPrim.IsBusy)
{
bgwPrim.CancelAsync();
}
}
private void bgwPrim_DoWork(object sender, DoWorkEventArgs e)
{
int highestToCheck = (int)e.Argument;
//Get a reference to the BackgroundWorker running this code
//for Progress Updates and Cancelation checking
BackgroundWorker thisWorker = (BackgroundWorker)sender;
//Create the list that stores the results and is returned by DoWork
List<int> Primes = new List<int>();
//Check all uneven numbers between 1 and whatever the user choose as upper limit
for(int PrimeCandidate=1; PrimeCandidate < highestToCheck; PrimeCandidate+=2)
{
//Report progress
thisWorker.ReportProgress(PrimeCandidate);
bool isNoPrime = false;
//Check if the Cancelation was requested during the last loop
if (thisWorker.CancellationPending)
{
//Tell the Backgroundworker you are canceling and exit the for-loop
e.Cancel = true;
break;
}
//Determin if this is a Prime Number
for (int j = 3; j < PrimeCandidate && !isNoPrime; j += 2)
{
if (PrimeCandidate % j == 0)
isNoPrime = true;
}
if (!isNoPrime)
Primes.Add(PrimeCandidate);
}
//Tell the progress bar you are finished
thisWorker.ReportProgress(highestToCheck);
//Save Return Value
e.Result = Primes.ToArray();
}
private void bgwPrim_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbPrim.Value = e.ProgressPercentage;
}
private void bgwPrim_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
pgbPrim.Value = pgbPrim.Maximum;
this.Refresh();
if (!e.Cancelled && e.Error == null)
{
//Show the Result
int[] Primes = (int[])e.Result;
StringBuilder sbOutput = new StringBuilder();
foreach (int Prim in Primes)
{
sbOutput.Append(Prim.ToString() + Environment.NewLine);
}
tbPrim.Text = sbOutput.ToString();
}
else
{
tbPrim.Text = "Operation canceled by user or Exception";
}
}
#endregion
I read both BackgroundWorker still freezes UI and UI still freezes using backgroundWorker but neither have helped me solve my issue. I asked a question a few days ago about Datatable not displaying information after SQL query fill in backgroundworker, which I eventually solved by using Invoke and a delegate.
My current code (shown below) gives me the results I want, except for two things: it doesn't actually display the progress bar (at least that I can see) and it freezes the UI while running:
DataTable tab1table = new DataTable();
public Form1()
{
InitializeComponent();
Instantiatesearch1Thread();
}
private void Instantiatesearch1Thread()
{
search1Thread.WorkerReportsProgress = true;
search1Thread.WorkerSupportsCancellation = true;
search1Thread.ProgressChanged += search1Thread_ProgressChanged;
search1Thread.DoWork += search1Thread_Dowrk;
search1Thread.RunWorkerCompleted += search1Thread_RunWorkerCompleted;
}
private void sbutton1_Click(object sender, EventArgs e)
{
search1Thread.RunWorkerAsync();
}
void search1Thread_Dowrk(object sender, DoWorkEventArgs e)
{
int percentprogress = 0;
var worker = sender as BackgroundWorker;
// Ensure process is thread-safe by using Invoke method
Invoke(new MethodInvoker(delegate
{
// Search1 button event handler
using (SqlConnection conn = new SqlConnection(Connection_Info))
{
conn.Open();
using (SqlDataAdapter cmd = new SqlDataAdapter(comboBox1SQL, conn))
{
//MessageBox.Show("Working");
if (comboBox1.Text.Contains("ID"))
{
long para = long.Parse(search1.Text);
cmd.SelectCommand.Parameters.Add(new SqlParameter
{
ParameterName = "#combo1Par",
Value = para,
SqlDbType = SqlDbType.BigInt
});
}
// Clear datatable if it contains any information and then fill it
if (tab1table != null)
tab1table.Clear();
cmd.Fill(tab1table);
// Set Progress Bar info
search1Progress.Maximum = tab1table.Rows.Count;
groupBox2.Controls.Add(search1Progress);
search1Progress.BringToFront();
}
}
// This is a long calculation where I am reporting the
// progress every iteration (which is probably not helping,
// but didn't change anything when commented out).
if(!String.IsNullOrEmpty(search1.Text) && tab1Combo1.SelectedIndex > -1 || tab1Combo2.SelectedIndex > -1)
{
for (int i = tab1table.Rows.Count - 1; i >= 0; i--)
{
DataRow dr = tab1table.Rows[i];
if (!dr.ItemArray.Contains(search1.Text.ToString()))
{
dr.Delete();
tab1table.AcceptChanges();
}
percentprogress++;
worker.ReportProgress(percentprogress);
}
}
}));
}
void search1Thread_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
search1Progress.Value = e.ProgressPercentage;
}
void search1Thread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
tab1datatable.DataSource = null;
tab1datatable.DataSource = tab1table;
tab1datatable.Update();
groupBox2.Controls.Remove(search1Progress);
MessageBox.Show(" Your search returned " + tab1table.Rows.Count.ToString() + " results");
}
Can anyone point out what I am doing wrong here?
My issue may have something to do with Invoke as I know it's not the best method with BackgroundWorker, but I couldn't get it to update the UI using any other method.
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();
}
I'm doing work on some database code. At some point I do need to move these calls off of the UI thread and on to a Background thread. I've got my code posted below but I'm curious as to some examples for doing this or if someone could show how to do this. I've done Async Calls in Java just trying to wrap my head around doing this C# with Visual Studio 2013. Any help would be appreciated.
Database Code :
static public Project.Project QueryProject(string projDatabaseName)
{
Project.Project proj = new Project.Project();
string connStr = "server=localhost;database=" + projDatabaseName + ";user=******;port=3306;password=*****;";
string queryStr = "SELECT * FROM " + projDatabaseName + ".project";
MySqlConnection myConnection = new MySqlConnection(connStr);
MySqlCommand myCommand = new MySqlCommand(queryStr, myConnection);
myConnection.Open();
try
{
MySqlDataReader myReader = myCommand.ExecuteReader();
while (myReader.Read())
{
proj.ProjectID = int.Parse(myReader["ProjectID"].ToString());
proj.ProjectName = myReader["ProjectName"].ToString();
proj.ProjectStartDate = Convert.ToDateTime(myReader["ProjectStartDate"]);
proj.ProjectEndDate = Convert.ToDateTime(myReader["ProjectEndDate"]);
proj.ProjectNotes = myReader["ProjectNotes"].ToString();
}
myReader.Close();
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
return null;
}
finally
{
myConnection.Close();
}
return proj;
}
Call to database code:
savedProj = ProjectDbInteraction.QueryProject(currentProjDb);
You should do some research into BackgroundWorker https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker%28v=vs.110%29.aspx and Threading in general with C#.
I would try using a background worker that would look something like this:
//setting up your background worker.
var worker = new BackgroundWorker();
worker.DoWork += bgw_DoWork;
worker.RunWorkerCompleted += bgw_WorkCompleted;
worker.ProgressChanged += bgw_ProgressChanged;
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = ProjectDbInteraction.QueryProject(currentProjDb);
}
private void bgw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//Here you can inspect the worker and update UI if needed.
}
private void bgw_WorkCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//check for errors, and other unexpected results first...
//assuming that savedProj is some private member variable of your class,
//just assign the variable to the Result here.
savedProj = (Project.Project)e.Result;
}
i am working on a winform application in which i have a timer.it is being used for showing stopwatch on the form. When i fire a button than my timer is interrupted. i want my timer to be uninterrupted while button is clicked. My code is as followed:-
private void button1_Click(object sender, EventArgs e)
{
if (!_timerRunning)
{
// Set the start time to Now
_startTime = DateTime.Now;
// Store the total elapsed time so far
_totalElapsedTime = _currentElapsedTime;
_timer.Start();
_timerRunning = true;
}
SqlConnection Con = new SqlConnection("Data Source=69.162.83.242,1232;Initial Catalog=test1;Uid=test;pwd=1234#Test;MultipleActiveResultSets=true;Connect TimeOut=60000;");
//SqlConnection Con = new SqlConnection("Data Source=ADMIN-PC\\YASH;Initial Catalog=test;Integrated Security=True; Connect TimeOut=600");
Con.Open();
string messageMask = "{0} # {1} : {2}";
string message = string.Format(messageMask, label6.Text, DateTime.Now.ToShortTimeString(), richTextBox2.Text);
richTextBox1.AppendText(Environment.NewLine + message);
SqlCommand cmd, cmd1;
cmd = new SqlCommand("Update Chat set UserInitial=#message,Updated=1 where ExpertName ='" + label6.Text + "'", Con);
cmd.Parameters.AddWithValue("#message", message);
cmd.ExecuteNonQuery();
Con.Close();
richTextBox2.Text = String.Empty;
richTextBox1.ScrollToCaret();
}
private void timer1_Tick(object sender, EventArgs e)
{
richTextBox1.ScrollToCaret();
count = count + 1;
count1 = count1 + 1;
timerSinceStartTime = new TimeSpan(timerSinceStartTime.Hours, timerSinceStartTime.Minutes, timerSinceStartTime.Seconds + 1);
// The current elapsed time is the time since the start button was
// clicked, plus the total time elapsed since the last reset
_currentElapsedTime = timerSinceStartTime + _totalElapsedTime;
// These are just two Label controls which display the current
// elapsed time and total elapsed time
if (count1 == 180)
{
MessageBox.Show("You are Automaticlly hired by User");
if (label7.Visible == true)
{
label7.Visible = false;
count = 0;
timerSinceStartTime = new TimeSpan(00, 00, 00);
label3.Visible = true;
}
}
label3.Text = timerSinceStartTime.ToString();
// If we're running on the UI thread, we'll get here, and can safely update
// the label's text.
richTextBox1.ScrollToCaret();
}
how to solve it??
thanks in Advance
Your problem stems from the fact you're using System.Windows.Forms.Timer which is not threaded and relies on the message pump. While your program is busy in the UI thread the message pump won't be processed.
You can improve this by moving to a timer that supports threads. I prefer System.Timers.Timer but there's also System.Threading.Timer.
With System.Timers.Timer the tick event is raised on a background thread if you don't pass any sync object in so that any code within the event handler will be handled in a separate thread.
Of course, to update the form we have to marshal back to the UI thread so we'll also need to use Control.Invoke().
This is very rough, but something like this:
System.Timers.Timer timer = new System.Timers.Timer();
timer.Elapsed += new ElapsedEventHandler(OnTimer);
timer.Interval = 1000;
timer.AutoReset = false;
timer.Enabled = true;
public void OnTimer(object sender, ElapsedEventArgs e)
{
// Do something busy like dancing
// Update form
Invoke((MethodInvoker)delegate() {
UpdateForm();
});
// Restart timer
((System.Timers.Timer)sender).Start();
}
public void UpdateForm()
{
// Code to update the form
}
Note I use AutoReset = false so that if the tick event takes longer than the timer interval you won't get overlap. You may or may not want this it entirely depends on what you're doing.
You can try this,
private bool bStopTimer = true;
private void StartTimer()
{
System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ThreadStart(this.ThreadTimer));
thread.IsBackground = true;
thread.Start();
}
private void ThreadTimer()
{
while (bStopTimer)
{
System.Threading.Thread.Sleep(1000); //interval in millisecond
lock(lblLabel.Text)
{
lblLable.Text = System.DateTime.Now.ToString("HH:mm:ss");
}
}
}
Update
place this line before Application.Run(new frmForm);
System.Windows.Forms.Control.CheckForIllegalCrossThreadCalls = false;