I need to be able to start up a window on a second UI thread and shut it down again at will.
This is my current code:
/// <summary>Show or hide the simulation status window on its own thread.</summary>
private void toggleSimulationStatusWindow(bool show)
{
if (show)
{
if (statusMonitorThread != null) return;
statusMonitorThread = new System.Threading.Thread(delegate()
{
Application.Run(new AnalysisStatusWindow(ExcelApi.analyisStatusMonitor));
});
statusMonitorThread.Start();
}
else
{
if (statusMonitorThread != null)
statusMonitorThread.Abort();
statusMonitorThread = null;
}
}
AnalysisStatusWindow is a fairly basic System.Windows.Forms.Form
The above code is successfully creating the new UI thread, but my request to Abort the thread is ignored. The result is that toggling the above function multiple times simply results in new windows opening up - all of which are on their own thread and fully functional.
Is there any way I can pass a message to this thread to shut down nicely? Failing that, is there any way to make sure Abort() really kills my second UI thread?
I've tried using new Form().Show() and .ShowDialog() instead of Application.Run(new Form()), but they aren't any easier to shut down.
If anyone is questioning the need for a separate UI thread, this code exists in an Excel Add-in, and I cannot control the fact that the Excel UI blocks while calculations for a given cell are underway. For that reason, when a long running custom formula executes, I require this second UI thread to display progress updates.
Thanks to Hans for his comment. I solved my problem using the following code:
/// <summary>Show or hide the simulation status window on its own thread.</summary>
private void toggleSimulationStatusWindow(bool show)
{
if (show)
{
if (statusMonitorThread != null) return;
statusMonitorWindow = new AnalysisStatusWindow(ExcelApi.analyisStatusMonitor);
statusMonitorThread = new System.Threading.Thread(delegate()
{
Application.Run(statusMonitorWindow);
});
statusMonitorThread.Start();
}
else if (statusMonitorThread != null)
{
statusMonitorWindow.BeginInvoke((MethodInvoker)delegate { statusMonitorWindow.Close(); });
statusMonitorThread.Join();
statusMonitorThread = null;
statusMonitorWindow = null;
}
}
Related
I have a problem and don't know how to solve that. I'm starting a new thread:
private void button_Click(object sender, EventArgs e)
{
Thread thrd = new Thread(new ThreadStart(loadingScreenStart));
thrd.Start();
//setting some variables, entering some methods etc...
thrd.Abort();
}
public void loadingScreenStart()
{
splashScreen splashObj = splashScreen.GetInstance();
Application.Run(splashScreen.GetInstance());
}
In another form I have:
private static splashScreen m_instance = null;
private static object m_instanceLock = new object();
public static splashScreen GetInstance()
{
lock (m_instanceLock)
{
if (m_instance == null)
{
m_instance = new splashScreen();
}
}
return m_instance;
}
That works fine but when I hit the button a second time then I get an exception that there is no access to the discarded object. Why and how to solve that? I mean after the thread gets aborted I create a new one when hitting the button again.
It's not the thread that is discarded, but the splashScreen instance. You should just create a new one, not try to reuse the old one.
Thread thrd = new Thread(new ThreadStart(loadingScreenStart));
thrd.Start();
//setting some variables, entering some methods etc...
thrd.Abort();
Why do you call thrd.Abort()? Did u know thread finish yet?
You must wait thread finished.
And use double checking here
public static splashScreen GetInstance()
{
if (m_instance == null)
{
lock (m_instanceLock)
{
if (m_instance == null)
{
m_instance = new splashScreen();
}
}
}
return m_instance;
}
And maybe when you call Run splashscreen, it was disposed. Try create and capture it to a field and pass it to your method. Try this.
Task.Run(() =>
var splashObj = splashScreen.GetInstance();
Application.Run(splashObj);
}));
Ok I solved it somehow. Don't think that this is a good solution but it works. Instead of aborting the thread I just hide the Form of the SplashScreen and check if the thread is already running. If yes then I just Show the Form. If not I create a new instance.
This is overly complicated and unsafe. If you really want to handle this using a "splash screen", why not try something like this?
using (var splashScreenForm = SplashScreen.ShowSplashScreen())
{
// Do your work
}
Where SplashScreen has methods like this:
public static SplashScreen ShowSplashScreen()
{
var form = new SplashScreen();
new Thread(() => Application.Run(form)).Start();
return form;
}
public override void Dispose(bool disposing)
{
if (disposing)
{
if (InvokeRequired)
{
Invoke(() => base.Dispose(true));
return;
}
base.Dispose(true);
}
else base.Dispose(disposing);
}
After taking another look at the whole thing I realized there's another problem here, and this is the entirely wrong direction to approach it from.
In your button_Click event you're obviously doing a lot of complicated stuff that takes a lot of time. Otherwise you wouldn't need to show a splash screen. However doing this in an event handler is a bad idea in itself. You totally lock up the UI thread and Windows will soon consider that window to be "hanged". It cannot even repaint itself!
So you should approach this from the completely opposite direction. Instead of trying to show the splash screen from another thread while using the UI thread for heavy work, just move the heavy work to the other thread! Then the splash screen won't need anything exotic like calling Application.Start() in another thread. Just a simple .ShowModal() will be enough. And when the other thread is done with its work, it can just call .Close() on your splash screen.
In a well designed application there is almost never a need for a second UI thread. It's the heavy work that needs to be moved to other threads, not UI.
Just be aware that if you want to manipulate the UI from another thread, you'll need to do some Invoke() stuff. Read more here: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired(v=vs.110).aspx
I'm trying to make a loading screen window. I use Show() instead of ShowDialog() because I have some code to execute after showing it. When using ShowDialog() form is fine but when using Show() form is messed up. What is causing this and what is the solution? Here is how I did it:
bool closeLoadingWindow = false;
void ShowLoadingWindow()
{
LoadingWindow loadingWindow = new LoadingWindow();
loadingWindow.Show();
while (!closeLoadingWindow);
loadingWindow.Close();
return;
}
public MainWindow()
{
Thread loadingWindowThread = new Thread(ShowLoadingWindow);
loadingWindowThread.Start();
InitializeComponent();
// ...
closeLoadingWindow = true;
}
When using ShowDialog():
When using Show():
The reason ShowDialog is working is because your while loop won't be executing, once the runtime hits that line of code it will stop processing until the form is dimissed.
Your code doesn't make sense, the point of using a thread here is to keep the "busy" code (your while loop) out of the main UI thread so it doesn't block. However, you are trying to create/show your form on the same thread, and a non-UI thread at that.
You don't necessarily need to use Show here, you can use ShowDialog but it is a little bit trickier in terms of dimissing the form etc. However, to solve the problem you have at the minute I would recommend you do:
LoadingWindow _loadingWindow;
void ShowLoadingWindow()
{
if (_loadingWindow == null)
_loadingWindow = new LoadingWindow();
_loadingWindow.Show();
}
void HideLoadingWindow()
{
if (_loadingWindow != null)
{
_loadingWindow.Close();
_loadingWindow.Dispose();
}
}
void LoadSomething()
{
while (...)
{
// busy code goes here
}
// after code is finished, close the form
MethodInvoker closeForm = delegate { HideLoadingWindow(); };
_loadingWindow.Invoke(closeForm);
}
public MainWindow()
{
ShowLoadingWindow();
new Thread(LoadSomething).Start();
}
}
FYI - Depending on the nature of exactly what your trying to do in the thread it might be a better approach to use the Task Parallel Library rather than creating a dedicated thread, various benefits like continuation / cancellation support.
I use a control that is a popup window. I have a methodes that show and hide it and I need to implement a functionality that will prevent show and hide my popup in different threads. Can I show my popup in not UI thread?
Update
The main goal of my question is:
It's not important from what thread the method Show starts, the Method Hide should be in the same thread. How to implement this?
public void Show()
{
IsShown = true;
if (this.ChildWindowPopup == null)
{
this.ChildWindowPopup = new Popup();
try
{
this.ChildWindowPopup.Child = this;
}
catch (ArgumentException)
{
throw new InvalidOperationException("The control is already shown.");
}
}
if (this.ChildWindowPopup != null && Application.Current.RootVisual != null)
{
// Configure accordingly to the type
InitializeProgressType();
// Show popup
this.ChildWindowPopup.IsOpen = true;
}
}
public void Hide()
{
IsShown = false;
// Restore system tray
SystemTray.IsVisible = currentSystemTrayState;
this.progressBar.IsIndeterminate = false;
this.ChildWindowPopup.IsOpen = false;
}
Generally working on UI components can only be done in UI thread. If you want to block your specific popup from being started from UI threads you would need to provide an interface to launch it that internally checks the thread it has been executed from - perhaps using SynchronizationContext or Thread classes, but in the end - it will need to show the Popup by invoking a method on UI thread.
When my application is loading. I display a progress bar using the code below. The problem is if someone clicks on the toolbar context menu (the way to exit) it will be blocked until this the progress bar is closed. Does anyone know a better way of achieving this?
The reason I'm using ShowDialog is that when I used Show the progress bar wouldn't animate - I'm using the MarqueeStyle.
Thanks
public partial class PopUpProgessBar : Form
{
public PopUpProgessBar()
{
InitializeComponent();
}
Thread t;
private void StartAnmiation()
{
this.Update();
this.ShowDialog();
}
public void Stop()
{
if (t != null)
{
t.Abort();
t.Join();
}
}
public void Start()
{
if (t == null)
{
t = new Thread(new ThreadStart(this.StartAnmiation));
t.Start();
}
}
This code doesn't look quite right. Are you sure it doesn't throw cross-thread violations? In general, your whole metaphor here is wrong. You need to keep the GUI on the GUI thread. Load your application on the background thread and have it send progress updates to the GUI thread.
Your PopupProgressBar form shouldn't be responsible for loading itself in a new thread, that should be done in presumably your main window.
I would get rid of all the thread stuff in PopupProgressBar and make it simply start updating it's marquee. Then, in your main window (OnLoad) you tell it to do it's thing:
bool done = false;
PopupProgressBar splashForm = null;
ThreadPool.QueueUserWorkItem((x) =>
{
using (splashForm = new PopupProgressBar ())
{
splashForm.Show();
while (!done)
Application.DoEvents();
splashForm.Close();
}
});
// do all your initialization work here
// also, during each step of your initialization you could send call a function
// in splashForm to update
done = true;
In my application I have a Button. If the button is clicked as select against a database is executed and the result is shown in a ListView.
As the select is quite complex, it takes some time to retrieve the data.
When I click the Button, the Application-Window should be disabled until the data is loaded.
But when I set the IsEnabled-Property of the Window to false, the window gets disabled after the data is loaded.
I tried to disable the Window in an other thread with a BackgroundWorker. But then I get an exception that the window is already in use by another thread.
How can I disable the Window bevore it retrieves the data?
You did the wrong thing in a background thread. You have to affect the UI from the UI thread, and your data loading should occur in a background thread.
The simplest approach is to use a BackgroundWorker to load your data, store that data in a class-level variable, and when your background work is complete, the UI re-enables and loads the data from the class-level variable.
I'd think you'd move the database activity to the background thread to leave your UI responsive (even if it's only to disable it) rather than the other way around.
try this:
BackgroundWorkerHelper.DoWork<Type of object you want to retrieve>(
() =>
{
//Load your data here
//Like
using (MarketingService svc = new MarketingService())
{
return svc.GetEmployeeLookupTable();
}
},
(args) =>
{
this.IsEnable = true;
if (args.Error == null)
{
Your Target Datasource= args.Result;
}
});
this.IsEnable = false;
I will suggest "BusyDialog" window in addition to background thread.
Yous busy dialog can be an animation displaying it is doing something, and modally blocking any user input as well.
public partial class BusyDialog : Window
{
public BusyDialog()
{
InitializeComponent();
}
public static T Execute<T>(DependencyObject parent, Func<T> action)
{
Window parentWindow = null;
if (parent is Window)
{
parentWindow = parent as Window;
}
else
{
parentWindow = Window.GetWindow(parent);
}
T val = default(T);
Exception le = null;
BusyDialog bd = new BusyDialog();
bd.Owner = parentWindow;
ThreadPool.QueueUserWorkItem((o) =>
{
try
{
val = action();
}
catch (Exception ex)
{
le = ex;
}
bd.EndDialog();
});
bd.ShowDialog();
if (le != null)
{
Trace.WriteLine(le.ToString());
throw new Exception("Execute Exception", le);
}
return val;
}
private void EndDialog()
{
Dispatcher.Invoke((Action)delegate() {
this.DialogResult = true;
});
}
}
Now you can use following way to call your method asynchronously,
List<Result> results = BusyDialog.Execute( this ,
()=>{
return MyLongDatabaseLoadingCall();
});
This is what happens,
BusyDialog is displayed modally, blocking any user input as well as displaying busy animation
A call to your method MyLongDatabaseLoadingCall is executed in ThreadPool.QueueUserItem, which asynchronously calls your method in different thread (Same as background threading functionality suggested by others here).
Animation continues till the call is executing
When your method ends, BusyDialog is ended and everything is back to how it was.