Closing a window owned by a different thread - c#

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();
}
}
}

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)(() => ...);

Avoiding to freeze UI while loading

I've this class, it works fine
public partial class Home : UserControl
{
public ObservableCollection<Activity> DataGridRows { get; set; }// = new ObservableCollection<Activity>();
public Home()
{
InitializeComponent();
DataContext = this;
this.Init();
}
private void Init()
{
DataGridRows = new ObservableCollection<Activity>();
refreshGrid(null, null);
}
private void refreshGrid(object sender, RoutedEventArgs e)
{
Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
startRefresh(); //<-- very long operation!
}));
}
}
My problem is that while calling startRefresh() the whole program is freezed, i can't click on other buttons or perform other operations until startRefresh is finished. However i want to run it on background.
Note that i can't use the Task object with the TaskScheduler.FromCurrentSynchronizationContext() method because startRefresh performs edit operations on DataGridRows and i get this exception:
System.NotSupportedException : This type of CollectionView does not support changes to its SourceCollection from a thread different from the Dispatcher thread.
You need to move the heavy data fetching and processing off the UI thread. When the data is ready, update the UI from the UI thread. If you are using .NET 4.0 or later, the Task Parallel Library makes this sort of operation MUCH easier.
NOTE: I am making the assumption that startRefresh() both fetches data and updates the UI. You will make like much easier on yourself if the data retrieval and UI update are in separate methods.
See this answer for more detail:
Avoiding the window (WPF) to freeze while using TPL
I think you can use awaitable delegate command
public ICommand MyCommand { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
MyCommand = new AwaitableDelegateCommand(refreshGrid);
}
private async Task refreshGrid()
{
await Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() =>
{
Thread.Sleep(10000);
}));
}
You can have a look at http://jake.ginnivan.net/awaitable-delegatecommand/ for awaitable delegate command

Communication between threads via delegates?

I am looking for a solution for interthread communication.
Thread A is the main thread of a windows app. I starts a Thread B that is working independant of thread a, they do not share code. But thread A has to get some feedback about status of thread b. I try to solve this with a delegate.
I am very sorry, I forgot to add that I have to work on .net 3.5, c#, WEC7
It is important that the code
public void OnMyEvent(string foo)
{
MessageBox.Show(foo);
}
is executed in context of thread a, how can I achieve this
public partial class Form1 : Form
{
//...
public void StartThread(Object obj)
{
new ClassForSecondThread(obj as Parameters);
}
private void button1_Click(object sender, EventArgs e)
{
//ParameterizedThreadStart threadstart = new ParameterizedThreadStart(startThread);
ParameterizedThreadStart threadstart = new ParameterizedThreadStart(StartThread);
Thread thread = new Thread(threadstart);
Parameters parameters = new Parameters(){MyEventHandler = OnMyEvent};
thread.Start(parameters);
}
public void OnMyEvent(string foo)
{
MessageBox.Show(foo);
}
}
//This code is executed in Thread B
public class ClassForSecondThread
{
public ClassForSecondThread(Parameters parameters)
{
if (parameters == null)
return;
MyEventhandler += parameters.MyEventHandler;
DoWork();
}
private void DoWork()
{
//DoSomething
if (MyEventhandler != null)
MyEventhandler.DynamicInvoke("Hello World");// I think this should be executed async, in Thread A
Thread.Sleep(10000);
if (MyEventhandler != null)
MyEventhandler.DynamicInvoke("Hello World again"); // I think this should be executed async, in Thread A
}
public event MyEventHandler MyEventhandler;
}
public class Parameters
{
public MyEventHandler MyEventHandler;
}
public delegate void MyEventHandler(string foo);
As you want to call the MessageBox on the main UI thread, you can achieve what you want using Control.Invoke.
Invoke((MethodInvoker)(() => MessageBox.Show(foo)));
The Invoke method can be called directly on the Form and you won't be in the context of Thread B within the delegate - the code will run on the same thread as the Form.
EDIT:
OP question: if I understood Control.Invoke correctly, it always acts in the context of a control?
Although the Invoke method uses a Control (in this case the form) to get a handle to the UI thread it is running on, the code within the delegate is not specific to the UI. If you want to add more statements and expand it to include more stuff, just do this:
string t = "hello"; //declared in the form
//Thread B context - Invoke called
Invoke((MethodInvoker)(() =>
{
//Back to the UI thread of the Form here == thread A
MessageBox.Show(foo);
t = "dd";
}));
Also, if you are updating things in a multi threaded environment where the data is accessible to more than one thread, then you will need to investigate sychronization - applying locks to data etc.
For what it is worth you can simplify your code considerably by using the new async and await keywords in C# 5.0.
public class Form1 : Form
{
private async void button1_Click(object sender, EventArgs args)
{
OnMyEvent("Hello World");
await Task.Run(
() =>
{
// This stuff runs on a worker thread.
Thread.Sleep(10000);
});
OnMyEvent("Hello World again");
}
private void OnMyEvent(string foo)
{
Message.Show(foo);
}
}
In the code above OnMyEvent is executed on the UI thread in both cases. The first call be executed before the task starts and the second call will be executed after the task completes.

Show form in main thread from another thread

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();

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.

Categories