How do I use Threading to Change some Cell in Gridview? I have a query from database and it uses a lot of time for its query. So It's very slow and I would like to use Threading to load data faster. Also, when the thread has finished it's job can change data in Grid view?
using System.Threading;
using System.Threading.Tasks;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
dataGridView1.DataSource = new List<Test>() { new Test { Name = "Original Value" } };
}
// Start the a new Task to avoid blocking the UI Thread
private void button1_Click(object sender, EventArgs e)
{
Task.Factory.StartNew(this.UpdateGridView);
}
// Blocks the UI
private void button2_Click(object sender, EventArgs e)
{
UpdateGridView();
}
private void UpdateGridView()
{
//Simulate long running operation
Thread.Sleep(3000);
Action del = () =>
{
dataGridView1.Rows[0].Cells[0].Value = "Updated value";
};
// If the caller is on a different thread than the one the control was created on
// http://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired%28v=vs.110%29.aspx
if (dataGridView1.InvokeRequired)
{
dataGridView1.Invoke(del);
}
else
{
del();
}
}
}
Related
I have a gui form that displays a treeview. I display an alert form that uses a marquee progress bar while a backgroundworker builds the treeview. The issue I have is that the marquee does not display and the progresschanged routine does not fire until control returns to the gui, not while the dowork is running.
Can anyone please assist as to whether I have coded this incorrectly or have misunderstood how it should work? Thanks.
setup:
alert = new AlertForm();
alert.Message = string.Format("Building folder tree ({0}), please wait.", Global.RootDir);
// event handler for the Cancel button in AlertForm
alert.Cancelled += new EventHandler<EventArgs>(btnExit_Click);
alert.Show();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += backgroundWorker1_DoWork;
backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
backgroundWorker1.RunWorkerAsync();
background worker code (debug steps onto .ReportProgress but not into backgroundWorker1_ProgressChanged) :
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(1);
Invoke(new MethodInvoker(() => CreateTreeView(treeView1)));
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
alert.Refresh();
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
alert.Close();
if (treeView1.Nodes.Count > 0)
{
treeView1.ExpandAll();
treeView1.TopNode = treeView1.Nodes[0];
}
}
what is displayed:
worker running - no marquee in gui
worker complete - marquee displays in gui
========================================================================
Based on the comments below I have ended up with creating a new treeview object then cloning that into the gui object.
TreeView treeView = new TreeView();
CreateTreeView(treeView);
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
if (Global.Error)
{
_alert.Close();
string errorMessage = Global.ErrorMessage;
MessageBox.Show(errorMessage, #"Error occurred", MessageBoxButtons.OK);
Global.Error = false;
}
}
else
{
TreeView treeView = (TreeView)e.Result;
CopyTreeNodes(treeView, treeView1);
if (treeView1.Nodes.Count > 0)
{
treeView1.ExpandAll();
treeView1.TopNode = treeView1.Nodes[0];
}
_alert.Close();
treeView = null;
}
}
public void CopyTreeNodes(TreeView treeViewSource, TreeView treeViewTarget)
{
foreach (TreeNode node in treeViewSource.Nodes)
{
TreeNode treeNode = new TreeNode(node.Text, node.ImageIndex, node.SelectedImageIndex);
CopyNodeChildren(treeNode, node);
treeViewTarget.Nodes.Add((TreeNode)node.Clone());
}
}
public void CopyNodeChildren(TreeNode parent, TreeNode original)
{
foreach (TreeNode tn in original.Nodes)
{
TreeNode treeNode = new TreeNode(tn.Text, tn.ImageIndex, tn.SelectedImageIndex);
parent.Nodes.Add((TreeNode)tn.Clone());
CopyNodeChildren(treeNode, tn);
}
}
I am making assumptions regarding the version of c# you are using, but if you are using 4.5 or later then you don't need a BackgroundWorker if you can work async/await patterns into your code. The downside is that working async/await into an existing code base can sometimes be challenging.
Form1 kicks things off by showing the alert form. The alert form has a CancellationTokenSource that can be used to cancel the operation.
Then CallTreeView is run asynchronously. It will abort if you click the cancel button. The progress bar is simply set to marquee style, you don't need to increment it or do anything with it, it will simply pulse the entire time the alert form is open.
Form1:
using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
AlertForm af = new AlertForm();
af.Show();
//I assume CallTreeView is not implemented in your form's code behind,
//if it is you do not need to pass it as a parameter
await Task.Run(async () => await CallTreeView(treeView1, af.Cts.Token));
af.Close();
}
private async Task CallTreeView(TreeView tv, CancellationToken token)
{
for (int i = 0; i < 10; i++)
{
if (token.IsCancellationRequested)
{
//clean up whatever you need to
return;
}
else
{
await Task.Delay(500); //just simulate doing something
//add nodes...
}
}
}
}
}
Alert Form:
using System;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class AlertForm : Form
{
public CancellationTokenSource Cts { get; set; }
public AlertForm()
{
InitializeComponent();
Cts = new CancellationTokenSource();
}
private void Cancel_Click(object sender, EventArgs e)
{
Cts.Cancel();
}
}
}
While using timers, stopwatches and threads is the standard way, I was wondering if there was a way to create a Winform Application in c# which had a label with initial value as 0 and which automatically kept on incrementing once a button is clicked and when the same button is clicked again it should pause. Personally, I feel that the trick is to use multicast delegates. But I am stuck as to how to proceed.
NOTE: Possible use of method callback and InvokeRequired().
this code dose not use timer or stopwatch.
i have wrote a simple class for you, forgive me if its not so standard because im so lazy for now :)
public partial class Form1 : Form
{
CancellationTokenSource src;
CancellationToken t;
public Form1()
{
InitializeComponent();
}
//start incrementing
private async void button1_Click(object sender, EventArgs e)
{
this.Start.Enabled = false;
this.Cancel.Enabled = true;
this.src = new CancellationTokenSource();
this.t = this.src.Token;
try
{
while (true)
{
var tsk = Task.Factory.StartNew<int>(() =>
{
Task.Delay(500);
var txt = int.Parse(this.Display.Text) + 1;
return (txt);
}, this.t);
var result = await tsk;
this.Display.Text = result.ToString();
}
}
catch (Exception ex)
{
return;
}
}
// Stop incrementing
private void button1_Click_1(object sender, EventArgs e)
{
this.src.Cancel();
this.Cancel.Enabled = true;
this.Start.Enabled = true;
}
}
Really not sure why you think this can be done with your restrictions in place. If you want a delay in-between your "events", then you need to use some kind of Timer, or some kind of thread (classic Thread or some kind of Task) that has a delay within it...no way around that.
Here's another approach that'll probably violate your restrictions:
public partial class Form1 : Form
{
private Int64 value = -1;
private bool Paused = true;
private int IntervalInMilliseconds = 100;
private System.Threading.ManualResetEvent mre = new System.Threading.ManualResetEvent(false);
public Form1()
{
InitializeComponent();
this.Shown += Form1_Shown;
}
private async void Form1_Shown(object sender, EventArgs e)
{
await Task.Run(delegate ()
{
while (true)
{
value++;
label1.Invoke((MethodInvoker)delegate ()
{
label1.Text = value.ToString();
});
System.Threading.Thread.Sleep(IntervalInMilliseconds);
mre.WaitOne();
}
});
}
private void button1_Click(object sender, EventArgs e)
{
if (Paused)
{
mre.Set();
}
else
{
mre.Reset();
}
Paused = !Paused;
}
}
USE an EVENT.
If you can not use timers or threads, then how about creating a do while loop that executes an event.
Some PSEUDO code is below - it should give you the idea..
bool IWantEvents = false;
public event EventHandler<myHandler> myNonTimerEvent ;
FormStart()
{
this.myNonTimerEvent += new MyNonTimerEventHandler();
IWantEvents = true;
Do
{
.. do some weird stuff - set IWantEvents False on condition ..
}
while(IWantEvents)
}
MyNonTimerEventHandler()
{
.. Do what I would do if I was using a timer event.
}
I have a listbox with filenames. When the selected index is changed I load the file.
I want something like jQuery's HoverIntent that delays the action of loading the file for a short time so the user can use the down arrow and quickly cycle through the items in the list without the application trying to load each one. Thread.Sleep pauses the whole app so a user can't select another list item until the sleep completes, this is obviously not what I want.
This will work if your using WinForms, make a call to the InitTimer method in the Form constructor.
Load the file in the _timer_Tick event handler. To change the delay set the Interval property in InitTimer to another value.
private System.Windows.Forms.Timer _timer;
private void InitTimer()
{
_timer = new Timer { Interval = 500 };
_timer.Tick += _timer_Tick;
}
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
_timer.Stop();
_timer.Start();
}
private void _timer_Tick(object sender, EventArgs e)
{
_timer.Stop();
// TODO: Load file here
}
Use Threading to separate the loading from your GUI.
This should get you started:
public partial class MainWindow : Window
{
CancellationTokenSource cts;
bool loading;
private void SelectedIndexChanged(int index)
{
if (loading)
cts.Cancel();
cts = new CancellationTokenSource();
var loader = new Task.Delay(1000);
loader.ContinueWith(() => LoadFile(index))
.ContinueWith((x) => DisplayResult(x));
loader.Start();
}
private void DisplayResult(Task t)
{
// TODO: Invoke this Method to MainThread
if (!cts.IsCancellationRequested)
{
// Actually display this file
}
}
Could not test, as I'm still on .net 4 whereas Task.Delay() is .net 4.5
You may need to add another field in the form for the file content transfer from the tasks to the GUI.
Winforms:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private CancellationTokenSource _cancel;
private object _loadLock = new object();
private void listBox1_SelectedIndexChanged(object sender, EventArgs e)
{
lock (_loadLock)
{
handleCancellation();
var loader = new Task((chosenFileItemInListbox) =>
{
Thread.Sleep(1000);
LoadFile(chosenFileItemInListbox);
}, listBox1.SelectedItem, _cancel.Token);
}
}
private bool handleCancellation()
{
bool cancelled = false;
lock (_loadLock)
{
if (_cancel != null)
{
if (!_cancel.IsCancellationRequested)
{
_cancel.Cancel();
cancelled = true;
}
_cancel = null;
}
}
return cancelled;
}
private void LoadFile(object chosenFileItemInListbox)
{
if (handleCancellation())
{
return;
}
}
}
The code above could also be applied to WPF, but WPF contains some built in magic for handling delays and cancellation of previous updates.
<ListBox SelectedItem="{Binding Path=SelectedFile, Delay=1000}" />
I am trying to get a ProgressBar with the progress of a dataset being converted to Excel using the BackgroundWorker. The problem is that the work is being done in a different class than the ProgressBar and I am having difficulty calling worker.ReportProgress(...) from within my loop. I am sorry if this is a easy thing but I am new to C# and have been trying this the whole day and just can't seem to get it right. Your help would be HIGHLY appreciated.
namespace CLT
{
public partial class GenBulkReceipts : UserControl
{
private void btnOpen_Click(object sender, EventArgs e)
{
Cursor.Current = Cursors.WaitCursor;
try
{
OpenFile();
}
Cursor.Current = Cursors.Default;
}
private void OpenFile()
{
if (dsEx1.Tables[0].Rows.Count > 0)
{
backgroundWorker1.RunWorkerAsync(dsEx1);
}
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DataSet ImportDataSet = e.Argument as DataSet;
AccountsToBeImported = new BLLService().Get_AccountsToBeReceipted(ImportDataSet);
}
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
// ...
}
}
namespace BLL
{
class GenBulkReceiptsBLL
{
public DataSet Get_AccountsToBeReceipted(DataSet dsImport)
{
DataSet dsReturn = AccountsDAL.QGenReceiptAccounts(0,0,"");//Kry Skoon DataSet wat ge-populate moet word
CLT.GenBulkReceipts pb = new CLT.GenBulkReceipts();
int TotalRows = dsImport.Tables[0].Rows.Count;
//pb.LoadProgressBar(TotalRows);
int calc = 1;
int ProgressPercentage;
foreach (DataRow dr in dsImport.Tables[0].Rows)
{
ProgressPercentage = (calc / TotalRows) * 100;
//This is the problem as I need to let my Progressbar progress here but I am not sure how
//pb.worker.ReportProgress(ProgressPercentage);
}
return dsReturn;
}
// ...
}
}
You'll need to pass your worker to the Get_AccountsToBeReceipted method - it can then call BackgroundWorker.ReportProgress:
// In GenBulkReceipts
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
DataSet importDataSet = e.Argument as DataSet;
AccountsToBeImported =
new BLLService().Get_AccountsToBeReceipted(importDataSet, worker);
}
// In GenBulkReceiptsBLL
public DataSet Get_AccountsToBeReceipted(DataSet dsImport,
BackgroundWorker worker)
{
...
worker.ReportProgress(...);
}
Alternatively, you could make GenBulkReceiptsBLL have its own Progress event, and subscribe to that from GenBulkReceipts - but that would be more complicated.
Your class GenBulkReceiptsBLL needs some reference to the BackgroundWorker instance. You can achieve this in a variety of ways. One such suggestion would be to pass the reference to the class when instantiating it.
For example, since GenBulkReceipts is the class that instantiates GenBulkReceiptsBLL, then in the constructor for GenBulkReceiptsBLL, you could pass the instance of the BackgroundWorker that is currently being used in GenBulkReceipts. This would allow for you to call ReportProcess(...) directly. Alternately, you could pass the reference directly into the Get_AccountsToBeReceipted(...) method.
my aim is that in the function "Dummy" i can change the controls like labels etc of the form from which the thread is initiating..how to do it..please don't suggest completely different strategies or making a worker class etc...modify this if you can
Thread pt= new Thread(new ParameterizedThreadStart(Dummy2));
private void button1_Click(object sender, EventArgs e)
{
pt = new Thread(new ParameterizedThreadStart(Dummy2));
pt.IsBackground = true;
pt.Start( this );
}
public static void Dummy(........)
{
/*
what i want to do here is to access the controls on my form form where the
tread was initiated and change them directly
*/
}
private void button2_Click(object sender, EventArgs e)
{
if (t.IsAlive)
label1.Text = "Running";
else
label1.Text = "Dead";
}
private void button3_Click(object sender, EventArgs e)
{
pt.Abort();
}
}
}
what i plan is that i could do this in the "Dummy" function
Dummy( object p)
{
p.label1.Text = " New Text " ;
}
You could do this, supposing you're passing an instance of the form to the thread method using the t.Start(...) method:
private void Form_Shown(object sender)
{
Thread t = new Thread(new ParameterizedThreadStart(Dummy));
t.Start(this);
}
....
private static void Dummy(object state)
{
MyForm f = (MyForm)state;
f.Invoke((MethodInvoker)delegate()
{
f.label1.Text = " New Text ";
});
}
EDIT
Added thread start code for clarity.
You can't do this. You can only access a UI control on the same thread that created it.
See the System.Windows.Forms.Control.Invoke Method and the Control.InvokeRequired property.
Can use something like this:
private void UpdateText(string text)
{
// Check for cross thread violation, and deal with it if necessary
if (InvokeRequired)
{
Invoke(new Action<string>(UpdateText), new[] {text});
return;
}
// What the update of the UI
label.Text = text;
}
public static void Dummy(........)
{
UpdateText("New text");
}