I have a WPF view with grid that have two elements in it, RichText and Progressbar.
When I'm loading a lot of text to RichText I want to show a loading process (just an animation) to the user.
The main idea to hide Richtext control, show Progressbar, start load text, when it finish show RichText again.
The problem is that while I'm updating RichText control I'm blocking UI and Progressbar is freezed.
Is there a way to update Progressbar from another thread, maybe some proxyhost?
Thank you.
Is there a way to update Progressbar from another thread
Short answer: No. A control can only be updated on the thread on which it was originally created.
What you can do is to display the ProgressBar in another window that runs on another thread and then close this window when the RichTextBox on the original thread has been updated.
You can add this to your usings:
using System.Windows.Threading;
For .NET 5/6, that is enough. For .NET framework you must also add a reference to System.Windows.Presentation.dll.
And this type of code will work fine:
private void Button_Click(object sender, RoutedEventArgs e)
{
// here we're in UI thread
var max = 100;
pg.Maximum = max; // pg is a progress bar
Task.Run(() =>
{
// here we're in another thread
for (var i = 0; i < max; i++)
{
Thread.Sleep(100);
// this needs the System.Windows.Threading using to support lambda expressions
Dispatcher.BeginInvoke(() =>
{
// this will run in UI thread
pg.Value = i;
});
}
});
}
Create a method on your form to update the element you want to update and use invoke to run it from your thread
like that:
private void Form1_Load(object sender, EventArgs e)
{
Thread _thread = new Thread(() =>
{
//do some work
upDateUiElements();//pass any parameters you want
});
_thread.Start();
}
public void upDateUiElements()//input any parameters you need
{
BeginInvoke(new MethodInvoker(() =>
{
//update ui elements
}));
}
If you need to invoke it from a different class you can pass your form as an object and then access the method through that object
Like that:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
OtherClass _class = new OtherClass(this);
_class.runthread();
}
public void upDateUiElements()//input any parameters you need
{
BeginInvoke(new MethodInvoker(() =>
{
//update ui elements
}));
}
}
class OtherClass
{
private Form1 _accessForm1;
public OtherClass(Form1 accessform1)
{
_accessForm1 = accessform1;
}
public void runthread()
{
Thread _thread = new Thread(() =>
{
//do some work
_accessForm1.upDateUiElements();
});
_thread.Start();
}
}
Related
I am new to C# and am having trouble figuring out how to pass an event from a thread up to the GUI form thread. Any help would be appreciated. All of the examples I find are WAY too complicated. I just want to start with one event from the treat up to the GUI and have the GUI do something (right now, anything).
namespace testEvents
{
public delegate void StuffHappenedDel( MessageEventArgs e);
public partial class Form1 : Form
{
workerThread thread;
int j = 0;
public Form1()
{
InitializeComponent();
thread = new workerThread();
thread.Start();
}
private void button1_Click(object sender, EventArgs e)
{
thread.Stop();
}
private void StuffHappenedDel(Object seder, EventArgs e)
{
j++;
}
}
public class workerThread
{
Thread worker;
private bool _quit = false;
/* I don't think this next line is correct*/
public event StuffHappenedDel StuffHappened;
protected virtual void OnStuffHappened(MessageEventArgs e)
{
if (StuffHappened != null)
StuffHappened( e);
}
public void Start()
{
ThreadStart start = new ThreadStart(Run);
worker = new Thread(start);
worker.Start();
}
private void Run()
{
int i = 0;
while (!_quit)
{
Thread.Sleep(1000);
i++;
OnStuffHappened(new MessageEventArgs(false, "it worked!"));
Console.WriteLine(string.Format("Slept {0} seconds.",i));
}
Console.WriteLine("Thread exiting");
}
}
public class MessageEventArgs : EventArgs
{
public MessageEventArgs(bool Error, string message)
{
IsError = Error;
Message = message;
}
public bool IsError { get; set; }
public string Message { get; set; }
}
}
You need to register Form1 as a listener for the event. First, add a method like the following to Form1:
private void thread_SuffHappened(MessageEventArgs e)
{
MessageBox.Show("Stuff happened!");
}
And in Form1's constructor, register as a listener like so:
public Form1()
{
InitializeComponent();
thread = new workerThread();
thread.StuffHappened += new StuffHappenedDel(thread_StuffHappened);
thread.Start();
}
Do you have to use this custom threading system, or are you able to use BackgroundWorkers? BackgroundWorkers haven an event ProgressChanged which fires on the thread that created the BackgroundWorker.
Alternatively, if you attach a handler to a background event from the UI thread, the work is still done on the background thread.
thread.StuffHappenedDel += new EventHandler<MessageEventArgs>(StuffHappenedDel);
Therefore, you need to marshall the data to the UI thread. One way is by using BeingInvoke.
private void StuffHappenedDel(object sender, MessageEventArgs e)
{
this.myControl.BeginInvoke( new Action(
() =>
{
//UI thread work (likely anything that affects UI. Heavy
//processing can continue on the bg thread outside this code block
}));
}
Also, you can use if (myControl.InvokeRequired) to check if you need to marshall data when changing a particular control.
if (this.InvokeRequired)
{
this.Invoke((Action)(() =>
{
//UI thread stuff
}
));
}
Edit to clarify
Your thread object that you've created needs to attach an event handler to the StuffHappenedDel event. To do this, you use something like this
thread.StuffHappenedDel += new EventHandler<MessageEventArgs>(StuffHappenedDel);
before you call thread.Start(). Now, this handler is called
private void StuffHappenedDel(Object seder, MessageEventArgs e)
{
j++;
}
whenever your event is fired.
If you want to make changes to any UI elements, you need to use the Invoke method described above.
Look into the Background Worker Class. Also, you can always fire an event that is handled by your GUI Class (though not on the GUI Thread) and then call Invoke
I am pretty new to C# and having a little issue with something. I believe threading may be the answer, but thats just a Buzz-Word I have picked up when looking for solutions.
namespace Test
{
public partial class Form1 : Form
{
private Form2 form2;
public Form1()
{
InitializeComponent();
form2 = new Form2();
}
private void runCheck(object source, System.Timers.ElapsedEventArgs e)
{
form2.ShowDialog();
form2.TopMost = true;
}
private void runCheckFalse()
{
form2.Hide();
}
}
This is only a quick code snippet of my stripped out application, however when trying this I get an error: Cross-thread operation not valid: Control 'Form2' accessed from a thread other than the thread it was created on.
Also as a side note I am using form2.TopMost = true; to attempt to open the window on top of everything else, but this often ends up at the back behind Visual Studio etc
You need to use Invoke in order to work with the form from a different thread.
Here is a nice article explaining how to work with Windows Forms controls from multiple threads: How to: Make Thread-Safe Calls to Windows Forms Controls
Try this:
namespace Test
{
public partial class Form1 : Form
{
private Form2 form2;
public Form1()
{
InitializeComponent();
form2 = new Form2();
}
private void runCheck(object source, System.Timers.ElapsedEventArgs e)
{
if (form2.InvokeRequired)
{
form2.Invoke(new EventHandler(delegate { form2.ShowDialog(); form2.TopMost = true; }));
}
else
{
form2.ShowDialog();
form2.TopMost = true;
}
}
private void runCheckFalse()
{
if(form2.InvokeRequired)
{
form2.Invoke(new EventHandler(delegate { form2.Hide(); }));
}
else
{
form2.Hide();
}
}
}
}
You can modify your runCheckFalse method in the following way - this is a fairly standard pattern for Windows Forms:
private void runCheckFalse()
{
if(InvokeRequired)
{
BeginInvoke(new MethodInvoker(runCheckFalse));
return;
}
form2.Hide();
}
Effectively, what this does is check to see if we are running on the GUI thread (" if InvokeRequired"). If we aren't, we call ourselves on the GUI thread and immediately return. If we are running on the GUI thread, then we don't need to do anything and just continue with the method as normal.
If you need to use parameters:
private void runCheckFalse(bool someParameter)
{
if(InvokeRequired)
{
BeginInvoke(new MethodInvoker(() => { runCheckFalse(someParameter);}));
return;
}
form2.Hide();
}
WinForm controls can only be updated from the UI thread. Take a look at this blog post, it gives a number of approaches to making sure that the update occurs on the UI thread. It is a long post, but worth the read. The quick and dirty approach is the first one if you don't have time to read it.
Erick
I want to get a control's property from a BackgroundWorker in my form:
foreach (ListViewItem i in ListView.CheckedItems) { //error: Cross-thread operation not valid: Control 'ListView' accessed from a thread other than the thread it was created on.
//do something with i
}
Can anyone suggest the simplest and easiest method to do this?
Let me take another stab at this...
1.) Drag a ListView onto the Form
2.) Drag a BackgroundWorker onto the Form
3.) Create a method do iterate through the ListViewItem collection
private void LoopThroughListItems()
{
foreach (ListViewItem i in listView1.CheckedItems)
DoSomething();
}
4.) Add code to call LoopThroughListItems() inside the BackgroundWorker's DoWork Event
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
LoopThroughListItems();
}
5.) In your Form Load - execute the code on the main thread (it works) then on the backgroundWorkder thread (it fails)
private void Form1_Load(object sender, EventArgs e)
{
// Try it on the UI Thread - It works
LoopThroughListItems();
// Try it on a Background Thread - It fails
backgroundWorker1.RunWorkerAsync();
}
6.) Modify your code to use IsInvokeRequired/Invoke
private void LoopThroughListItems()
{
// InvokeRequired == True when executed by non-UI thread
if (listView1.InvokeRequired)
{
// This will re-call LoopThroughListItems - on the UI Thread
listView1.Invoke(new Action(LoopThroughListItems));
return;
}
foreach (ListViewItem i in listView1.CheckedItems)
DoSomething();
}
7.) Run the app again - now it works on the UI thread and the non-UI thread.
That solve the problem. The checking IsInvokeRequired/Invoking is a common pattern you'll get used to a lot (which is why it's included on all Controls). If you are doing it all over the place, you can do something clever and wrap it all up - as described here: Automating the InvokeRequired code pattern
Try something like this:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void OnClick(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
private void OnDoWork(object sender, DoWorkEventArgs e)
{
foreach (ListViewItem i in GetItems(listView1))
{
DoSomething(i);
}
}
private IEnumerable<ListViewItem> GetItems(ListView listView)
{
if (InvokeRequired)
{
var func = new Func<ListView, IEnumerable<ListViewItem>>(GetItems);
return (IEnumerable<ListViewItem>)Invoke(func, new[] { listView });
}
// Create a defensive copy to avoid iterating outsite UI thread
return listView.CheckedItems.OfType<ListViewItem>().ToList();
}
private void DoSomething(ListViewItem item)
{
if (InvokeRequired)
{
var action = new Action<ListViewItem>(DoSomething);
Invoke(action, new[] { item });
return;
}
// Do whatever you want with i
item.Checked = false;
}
}
}
However, your question was really general. Maybe there would be a easier or better solution if you shared more details.
I am creating an application and I would like to implement a progress window that appears when a lengthy process is taking place.
I've created a standard windows form project to which I've created my app using the default form. I've also created a new form to use as a progress window.
The problem arises when i open the progress window (in a function) using:
ProgressWindow.ShowDialog();
When this command is encountered, the focus is on the progress window and I assume it's now the window who's mainloop is being processed for events. The downside is it blocks the execution of my lengthy operation in the main form.
If I open the progress window using:
ProgressWindow.Show();
Then the window opens correctly and now doesn't block the execution of the main form but it doesn't act as a child (modal) window should, i.e. allows the main form to be selected, is not centered on the parent, etc..
Any ideas how I can open a new window but continue processing in the main form?
You probably start your lengthy operation in a separate worker thread (e.g. using a background worker). Then show your form using ShowDialog() and on completion of the thread close the dialog you are showing.
Here is a sample - in this I assume that you have two forms (Form1 and Form2). On Form1 I pulled a BackgroundWorker from the Toolbox. Then I connected the RunWorkerComplete event of the BackgroundWorker to an event handler in my form. Here is the code that handles the events and shows the dialog:
public partial class Form1 : Form
{
public Form1() {
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e) {
Thread.Sleep(5000);
e.Result = e.Argument;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
var dlg = e.Result as Form2;
if (dlg != null) {
dlg.Close();
}
}
private void button1_Click(object sender, EventArgs e) {
var dlg = new Form2();
this.backgroundWorker1.RunWorkerAsync(dlg);
dlg.ShowDialog();
}
}
I implemented something very similar to this for another project. This form allows you to popup a modal dialog from within a worker thread:
public partial class NotificationForm : Form
{
public static SynchronizationContext SyncContext { get; set; }
public string Message
{
get { return lblNotification.Text; }
set { lblNotification.Text = value; }
}
public bool CloseOnClick { get; set; }
public NotificationForm()
{
InitializeComponent();
}
public static NotificationForm AsyncShowDialog(string message, bool closeOnClick)
{
if (SyncContext == null)
throw new ArgumentNullException("SyncContext",
"NotificationForm requires a SyncContext in order to execute AsyncShowDialog");
NotificationForm form = null;
//Create the form synchronously on the SyncContext thread
SyncContext.Send(s => form = CreateForm(message, closeOnClick), null);
//Call ShowDialog on the SyncContext thread and return immediately to calling thread
SyncContext.Post(s => form.ShowDialog(), null);
return form;
}
public static void ShowDialog(string message)
{
//Perform a blocking ShowDialog call in the calling thread
var form = CreateForm(message, true);
form.ShowDialog();
}
private static NotificationForm CreateForm(string message, bool closeOnClick)
{
NotificationForm form = new NotificationForm();
form.Message = message;
form.CloseOnClick = closeOnClick;
return form;
}
public void AsyncClose()
{
SyncContext.Post(s => Close(), null);
}
private void NotificationForm_Load(object sender, EventArgs e)
{
}
private void lblNotification_Click(object sender, EventArgs e)
{
if (CloseOnClick)
Close();
}
}
To use, you'll need to set the SyncContext from somewhere in your GUI thread:
NotificationForm.SyncContext = SynchronizationContext.Current;
Another option:
Use ProgressWindow.Show() & implement the modal-window behavior yourself. parentForm.Enabled = false, position the form yourself, etc.
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();
}
}
}