Show form in main thread from another thread - c#

I developing multithreading application with main form and another form in which progress is shown.
At first: I create ProgressForm in MainForm
Progress p=new Progress();
Second: I create new instance of class Model (whith all data in my app).
Model m = new Model();
And subscribe for event:
m.OperationStarted += new EventHandler(OnCopyStarted);
private void OnCopyStarted(object sender, EventArgs e)
{
p.Show();
}
Third: I run some operation in another thread where I change property in another Model
private bool isStarted;
public bool IsStarted
{
get{return isStarted;}
set
{
isStarted = value;
if (isStarted && OperationStarted != null)
{
OperationStarted(this, EventArgs.Empty);
}
}
}
My questoin is: Why Progress form is show not in Main Thread? How can I run it without lockups?

All UI operations must run on the main UI thread.
The OnCopyStarted method is being called on another thread, so it must switch to the UI thread before before showing the dialog.
You can use your form's BeginInvoke to switch to the UI thread. Such as:
void OnCopyStarted(object sender, EventArgs e)
{
p.BeginInvoke((Action) (() => p.Show()));
}

Try it :
var t = new Thread(() => {
Application.Run(new Progress ());
});
t.Start();

Related

How to invoke UI thread in Winform application without a form or control?

I have created a tray application for controlling some hardware components. How can I invoke the UI thread without a main form or control?
The tray app is started with Application.Run(new MyTrayApp()):
class MyTrayApp : ApplicationContext
{
private NotifyIcon trayIcon;
public MyTrayApp()
{
trayIcon = new NotifyIcon()
{
Icon = Resources.app_icon,
ContextMenu = new ContextMenu(new MenuItem[] {
new MenuItem("Exit", Exit)
}),
Visible = true
};
// context is still null here
var context = SynchronizationContext.Current;
// but I want to invoke UI thread in hardware events
MyHardWareController controller= new MyHardWareController(context);
}
void Exit(object sender, EventArgs e)
{
// context is accessible here because this is a UI event
// too late tho
var context = SynchronizationContext.Current;
trayIcon.Visible = false;
Application.Exit();
}
}
Control.Invoke() is not available as there are no controls
Searching suggests that SynchronizationContext.Current should be saved for later invoke but there is no ApplicationContext.Load() event...?
I've noticed that MainForm is null in the whole cycle. I wonder how does SynchronizationContext initialized in this case?
Edit:
Just to add some background info on why I would like to invoke UI thread. It is because System.Threading.ThreadStateException will be thrown when attempt to access Windows resources such as Clipboard or SendKeys in another thread:
HResult=0x80131520
Message=Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it.
Source=System.Windows.Forms
StackTrace:
...
It's another can of worms but just for information:
[STAThreadAttribute] is already set for Main function (no effect)
Creating a new STA thread would result in anti-virus deleting my application upon compile
Thus Form.Invoke() or the equivalent to invoke main thread should be the easiest.
Edit 2:
Add a gist for reproducing the error:
https://gist.github.com/jki21/eb950df7b88c06cc5c6d46f105335bbf
Solved it with Application.Idle as mentioned by Loathing! Thanks everyone for your advice!
TrayApp:
class MyTrayApp: ApplicationContext {
private MyHardwareController controller = null;
public MyTrayApp() {
Application.Idle += new EventHandler(this.OnApplicationIdle);
// ...
}
private void OnApplicationIdle(object sender, EventArgs e) {
// prevent duplicate initialization on each Idle event
if (controller == null) {
var context = TaskScheduler.FromCurrentSynchronizationContext();
controller = new MyHardwareController((f) => {
Task.Factory.StartNew(
() => {
f();
},
CancellationToken.None,
TaskCreationOptions.None,
context);
});
}
}
// ...
}
MyHardwareController:
class MyHardwareController {
private Action < Action > UIInvoke;
public MyHardwareController(Action < Action > UIInvokeRef) {
UIInvoke = UIInvokeRef;
}
void hardware_Event(object sender, EventArgs e) {
// Invoke UI thread
UIInvoke(() => Clipboard.SetText("I am in UI thread!"));
}
}
An alternative solution would be to create a dummy form (which will never be shown, but should be stored somewhere. You just have to access the Handle property of the Form to be able to invoke it from now on.
public static DummyForm Form { get; private set; }
static void Main(string[] args)
{
Form = new DummyForm();
_ = Form.Handle;
Application.Run();
}
Now it is possible to invoke into the UI thread:
Form.Invoke((Action)(() => ...);

Update progress bar in another form while task is running

**Ultimately I am going to have four tasks running concurrently and have another form that contains four progress bars. I would like for each progress bar to update as it's work task is completing.
Here's what I'm trying to do for starters.
I have a form that has some buttons on it. When I click one I'm creating a new task to do some work.
public partial class MyMainForm : Form
{
private void btn_doWork_Click(object sender, EventArgs e)
{
Task task = new Task(RunComparisons);
task.Start();
}
private void RunComparisons()
{
int progressBarValue = 0;
MyProgressBarForm pBar = new MyProgressBarForm(maxValue, "some text");
pBar.ShowDialog();
foreach(string s in nodeCollection)
{
//do some work here
progressBarValue++;
pBar.updateProgressBar(progressBarValue, "some new text");
}
pBar.BeginInvoke(new Action(() => pBar.Close()));
}
}
In another class that contains a form with a progress bar:
public partial class MyProgressBarForm : Form
{
public MyProgressBarForm(int maxValue, string textToDisplay)
{
InitializeComponent();
MyProgressBarControl.Maximum = maxValue;
myLabel.Text = textToDisplay;
}
public void updateProgressBar(int progress, string updatedTextToDisplay)
{
MyProgressBarForm.BeginInvoke(
new Action(() =>
{
MyProgressBarControl.Value = progress;
myLabel.Text = updatedTextToDisplay;
}));
}
When I click the doWork button the progress bar form displays but doesn't update. It just sits there and hangs. If I comment out the pBar.ShowDialog(); then the progress bar form doesn't display but the work to be done is run to completion perfectly.
I had this working perfectly when I was creating my own threads but I read about Tasks and now I'm trying to get this to run that way. Where did I go wrong?
The TPL adds the IProgress interface for updating the UI with the progress of a long running non-UI operation.
All you need to do is create a Progress instance in your UI with instructions on how to update it with progress, and then pass it to your worker which can report progress through it.
public partial class MyMainForm : System.Windows.Forms.Form
{
private async void btn_doWork_Click(object sender, EventArgs e)
{
MyProgressBarForm progressForm = new MyProgressBarForm();
progressForm.Show();
Progress<string> progress = new Progress<string>();
progress.ProgressChanged += (_, text) =>
progressForm.updateProgressBar(text);
await Task.Run(() => RunComparisons(progress));
progressForm.Close();
}
private void RunComparisons(IProgress<string> progress)
{
foreach (var s in nodeCollection)
{
Process(s);
progress.Report("hello world");
}
}
}
public partial class MyProgressBarForm : System.Windows.Forms.Form
{
public void updateProgressBar(string updatedTextToDisplay)
{
MyProgressBarControl.Value++;
myLabel.Text = updatedTextToDisplay;
}
}
This lets the Progress Form handle displaying progress to the UI, the working code to only handle doing the work, the main form to simply create the progress form, start the work, and close the form when done, and it leaves all of the work of keeping track of progress and marhsaling through the UI thread to Progress. It also avoids having multiple UI thread; your current approach of creating and manipulating UI components from non-UI threads creates a number of problems that complicates the code and makes it harder to maintain.
Create your progress bar form on the main UI thread of the parent form, then call the Show() method on the object in your button click event.
Here's an example with 2 bars:
//In parent form ...
private MyProgressBarForm progressBarForm = new MyProgressBarForm();
private void button1_Click(object sender, EventArgs e)
{
progressBarForm.Show();
Task task = new Task(RunComparisons);
task.Start();
}
private void RunComparisons()
{
for (int i = 1; i < 100; i++)
{
System.Threading.Thread.Sleep(50);
progressBarForm.UpdateProgressBar(1, i);
}
}
//In MyProgressBarForm ...
public void UpdateProgressBar(int index, int value)
{
this.Invoke((MethodInvoker) delegate{
if (index == 1)
{
progressBar1.Value = value;
}
else
{
progressBar2.Value = value;
}
});
}
.ShowDialog is a blocking call; execution won't continue until the dialog returns a result. You should probably look in to a BackgroundWorker to process the work on another thread and update the dialog.

Update UI On RunWorkerCompleted

I've used Background workers lots of times in the passed to use the DoWork to go and get the data and then the WorkerComplete which I believe runs on the UI thread to update the UI. But in the way I have it working at the minute I keep getting a cross-thread error?
Heres what I have:
public partial class Form1 : Form
{
Public Form1()
{
}
BackgroundWorker CheckPopUps;
DataSet Popups = new DataSet();
public void Rotate()
{
CheckPopUps = new BackgroundWorker();
CheckPopUps.DoWork += CheckPopUps_DoWork;
CheckPopUps.RunWorkerCompleted += CheckPopUps_RunWorkerCompleted;
CheckPopUps.RunWorkerAsync();
}
void CheckPopUps_DoWork(object sender, DoWorkEventArgs e)
{
DataTable Pops1 = SharedTools.DataProcedures.Popup_GetListToPop(2, ScreenName, "Interviews");
if (Pops1.Rows.Count > 1) { Popups.Tables.Add(Pops1.Copy()); }
}
void CheckPopUps_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
foreach (DataTable myTable in Popups.Tables)
{
foreach (DataRow myRow in myTable.Rows)
{
PopUp Pop = new PopUp();
Pop.SetDetails(myRow);
this.Controls.Add(Pop);
//Error occurs on this line ^
//Cross-thread operation not valid: Control 'Form1' accessed from a thread other than the thread it was created on.
}
}
}
}
When RunWorkerAsync it will look at the value of SynchronizationContext.Current and, if not null, use that as the mechanism to marshal the event handlers to the UI thread. It doesn't magically know how to run code in the UI thread; it needs to be given some means from somewhere.
This means that Rotate needs to be called from the UI thread, since that's where you call RunWorkerAsync. You're apparently calling it from a non-UI thread.
You should only get a cross-thread error if you call Rotate on a thread other than the UI thread. The RunWorkerCompleted handler will not necessarily run on the UI thread - it actually runs on the SynchornizationContext that is current when RunWorkerAsync is called. This means if you call Rotate on the UI thread, it should happen on the UI thread.
I have these extension methods defined which allow me to call UI elements from any thread
public delegate void EmptyHandler();
public delegate void ParamHandler(params object[] args);
public static void SafeCall(this System.Windows.Forms.Control control, ParamHandler method, params object[] args)
{
if (control.InvokeRequired)
{
control.Invoke(method, args);
}
else
{
method(args);
}
}
public static void SafeCall(this System.Windows.Forms.Control control, EmptyHandler method)
{
if (control.InvokeRequired)
{
control.Invoke(method);
}
else
{
method();
}
}
To be used for example as
progress.SafeCall(() => progress.Value=100);
In your case it would be
var pop = new PopUp();
...
this.SafeCall( ()=> this.Controls.Add(pop) );

Calling ShowDialog in BackgroundWorker

I have a WinForms application in which my background worker is doing a sync task, adding new files, removing old ones etc.
In my background worker code I want to show a custom form to user telling him what will be deleted and what will be added if he continues, with YES/NO buttons to get his feedback.
I was wondering if it is ok to do something like this in background worker's doWork method?
If not, how should I do it?
Please advise..
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
MyForm f = new MyForm();
f.FilesToAddDelete(..);
DialogResult result = f.ShowDialog();
if(No...)
return;
else
//keep working...
}
If you try this you will see for yourself that it will not work because the BackgroundWorker thread is not STA (it comes from the managed thread pool).
The essence of the matter is that you cannot show user interface from a worker thread¹, so you must work around it. You should pass a reference to a UI element of your application (the main form would be a good choice) and then use Invoke to marshal a request for user interaction to your UI thread. A barebones example:
class MainForm
{
// all other members here
public bool AskForConfirmation()
{
var confirmationForm = new ConfirmationForm();
return confirmationForm.ShowDialog() == DialogResult.Yes;
}
}
And the background worker would do this:
// I assume that mainForm has been passed somehow to BackgroundWorker
var result = (bool)mainForm.Invoke(mainForm.AskForConfirmation);
if (result) { ... }
¹ Technically, you cannot show user interface from a thread that is not STA. If you create a worker thread yourself you can choose to make it STA anyway, but if it comes from the thread pool there is no such possibility.
I usually create a method to execute a delegate on the UI thread:
private void DoOnUIThread(MethodInvoker d) {
if (this.InvokeRequired) { this.Invoke(d); } else { d(); }
}
With this, you can change your code to:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
DialogResult result = DialogResult.No;
DoOnUIThread(delegate() {
MyForm f = new MyForm();
f.FilesToAddDelete(..);
result = f.ShowDialog();
});
if(No...)
return;
else
//keep working...
}
IMO answers stating that you should launch a thread to handle this are misguided. What you need is to jump the window back to the main dispatcher thread.
In WPF
public ShellViewModel(
[NotNull] IWindowManager windows,
[NotNull] IWindsorContainer container)
{
if (windows == null) throw new ArgumentNullException("windows");
if (container == null) throw new ArgumentNullException("container");
_windows = windows;
_container = container;
UIDispatcher = Dispatcher.CurrentDispatcher; // not for WinForms
}
public Dispatcher UIDispatcher { get; private set; }
and then, when some event occurs on another thread (thread pool thread in this case):
public void Consume(ImageFound message)
{
var model = _container.Resolve<ChoiceViewModel>();
model.ForImage(message);
UIDispatcher.BeginInvoke(new Action(() => _windows.ShowWindow(model)));
}
WinForms equivalent
Don't set UIDispatcher to anything, then you can do have:
public void Consume(ImageFound message)
{
var model = _container.Resolve<ChoiceViewModel>();
model.ForImage(message);
this.Invoke( () => _windows.ShowWindow(model) );
}
DRYing it up for WPF:
Man, so much code...
public interface ThreadedViewModel
: IConsumer
{
/// <summary>
/// Gets the UI-thread dispatcher
/// </summary>
Dispatcher UIDispatcher { get; }
}
public static class ThreadedViewModelEx
{
public static void BeginInvoke([NotNull] this ThreadedViewModel viewModel, [NotNull] Action action)
{
if (viewModel == null) throw new ArgumentNullException("viewModel");
if (action == null) throw new ArgumentNullException("action");
if (viewModel.UIDispatcher.CheckAccess()) action();
else viewModel.UIDispatcher.BeginInvoke(action);
}
}
and in the view model:
public void Consume(ImageFound message)
{
var model = _container.Resolve<ChoiceViewModel>();
model.ForImage(message);
this.BeginInvoke(() => _windows.ShowWindow(model));
}
Hope it helps.
You should bring up the dialog before you run the backgroundworker. And in the progresschanged-event, you can update the dialog.

Closing a window owned by a different thread

I am new to threading. I am using background threads in my WPF Application to talk to the DB and message communication.
One of the view models should open a separate window. Since this should Run as a UI thread, I am doing:
private void OnSelection(SelectionType obj)
{
Thread thread = new Thread(ShowRegionWindow);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
private void ShowRegionWindow()
{
var rWindow = new RegionWindow();
rWindow .Show();
rWindow .Closed += (s, e) => System.Windows.Threading.Dispatcher.ExitAllFrames();
System.Windows.Threading.Dispatcher.Run();
}
Now I need to close this window on another message. How do I do that?
Before I go any further, you said you are new to threading and I want to stress that there is probably no good reason for your application to open windows on different threads. It is good that you are using MVVM, but you may not be doing it right. Ideally, all your views and view models would be on the main UI thread. Any worker threads in your model layer need to invoke the UI dispatcher before interacting with a view model. For instance, you might have an update event on a worker thread call a handler on the view model to update the UI. The UI dispatcher should either be invoked immediately before or after that event is invoked. (To be clear though, the model should not know about the view model.)
In fact, you seem to be creating a new Window in a UI event handler which means you should probably just do this:
private void OnSelection(SelectionType obj)
{
var rWindow = new RegionWindow();
rWindow.Show();
}
However, maybe you have a perfectly legitimate reason for doing it the way you are. If so, one way you could close that new window from the calling thread would be to pass in an event. You could do something like this:
private event Action CloseRegionWindows = delegate { }; // won't have to check for null
private void OnSelection(SelectionType obj)
{
Thread thread = new Thread(() => ShowRegionWindow(ref CloseRegionWindows));
...
}
private void ShowRegionWindow(ref Action CloseRegionWindows)
{
var rWindow = new RegionWindow();
rWindow.Show();
CloseRegionWindows += () => rWindow.Dispatcher.BeginInvoke(new ThreadStart(() => rWindow.Close()));
...
}
And then raise that event somewhere:
private void OnClick(object sender, RoutedEventArgs args)
{
CloseRegionWindows();
}
After reading some of your comments again, I think I have a better understanding of the scenario. Here's what you need to do.
First, be sure that one of your ViewModels has a reference to the Model that needs to open and close a window. One way to accomplish that is constructor dependency injection.
public ViewModel(Model model) // or IModel
{
...
Next, you'll need to capture the UI dispatcher in that ViewModel. The best place for this is probably also the ViewModel constructor.
private Dispatcher dispatcher;
public ViewModel(Model model)
{
dispatcher = Dispatcher.CurrentDispatcher;
...
Now create two events in your Model; one to open and one to close the window.
class Model
{
internal event Action OpenWindow = delegate { };
internal event Action CloseWindow = delegate { };
...
And subscribe to them in your ViewModel constructor.
public ViewModel(Model model)
{
dispatcher = Dispatcher.CurrentDispatcher;
model.OpenWindow += OnWindowOpen;
model.CloseWindow += OnWindowClose;
...
}
Now open and close your window with the UI Dispatcher in the ViewModel class;
private Window window;
private void OnWindowOpen()
{
// still on background thread here
dispatcher.BeginInvoke(new ThreadStart(() =>
{
// now we're on the UI thread
window = new Window();
window.Show();
}
}
private void OnWindowClose()
{
dispatcher.BeginInvoke(new ThreadStart(() =>
{
window.Close();
}
}
Finally, raise the OpenWindow and CloseWindow events from your background thread in your Model, just as you would raise any event. Your Model might look something like this:
class Model
{
private Thread worker;
internal event Action OpenWindow = delegate { };
internal event Action CloseWindow = delegate { };
public Model()
{
worker = new Thread(Work);
worker.Start();
}
private void Work()
{
while(true)
{
if (/*whatever*/) OpenWindow();
else if (/*whatever*/) CloseWindow();
}
}
}

Categories