Working on a C# project which I would like to implement a "waiting" (throbber) indicator in a separate form. After much research and trial and error it appears as the suggested method of doing this is to load a form using a separate thread from the one from the current form/thread.
The reason I went with this method was because initially using the Show() method on the throbber form produced a transparent form. I cannot use ShowDialog because I need to run some code after the throbber is displayed, after which that completes I would like to close the throbber form.
Anyway .. after trying many different methods to load the throbber form in a separate thread I still get an error about trying to access it from a thread which is different from the one it was created in. Here is a skelton version of the project code that should shed some light on my issue:
the example I was working off of for multithreading was this popular link for creating your own spashscreen in a separate thread ... http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C
public class Main
{
public void CheckData()
{
try
{
ProgressBar pb = new ProgressBar();
pb.ShowProgressBar();
//do data checking here
pb.CloseForm()
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static Thread ms_oThread = null;
public bool shouldStop = false;
static ProgressBar ms_ProgBar = null;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
public void ShowForm()
{
ms_ProgBar = new ProgressBar();
Application.Run(ms_ProgBar);
}
public void CloseForm()
{
ms_ProgBar.Close();
}
public void ShowProgressBar()
{
// Make sure it is only launched once.
if (ms_ProgBar != null)
return;
ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
while (ms_ProgBar == null || ms_ProgBar.IsHandleCreated == false)
{
System.Threading.Thread.Sleep(1000);
}
}
}
You are creating your ProgressBar twice. Once in your main function, and once in your new thread. You are also calling your CloseWindow method from your main function (and on the window that is never shown), rather than on your new thread window.
You only want to create ProgressBar and show it using your new thread. Make your static ProgressBar field public so you can call close on it directly from Main, but make sure to use Invoke to do it since it's not on that Window's GUI thread.
Also, ShowProgressBar should be static.
Here's a rewrite attempt:
public class Main
{
public void CheckData()
{
try
{
ProgressBar.ShowProgressBar();
//do data checking here
ProgressBar.CloseForm();
}
catch(Exception e)
{
}
}
}
public partial class ProgressBar : Form
{
static ProgressBar _progressBarInstance;
public ProgressBar()
{
InitializeComponent();
//DoWork();
}
static void ShowForm()
{
_progressBarInstance = new ProgressBar();
Application.Run(ms_ProgressBar);
}
public static void CloseForm()
{
_progressBarInstance.Invoke(new Action(_progressBarInstance.Close));
_progressBarInstance= null;
}
public static void ShowProgressBar()
{
// Make sure it is only launched once.
if (_progressBarInstance != null)
return;
var ms_oThread = new Thread(new ThreadStart(ShowForm));
ms_oThread.IsBackground = true;
ms_oThread.SetApartmentState(ApartmentState.STA);
ms_oThread.Start();
}
}
Related
The problem is that i have some kind of splash screen which shows loading animation.
I have special manager that show and hide it.
class Manager
{
private Form CurForm;
Thread curt;
private Manager()
{
curt= new Thread(start);
curt.ApartmentState = ApartmentState.STA;
curt.IsBackground = true;
curt.Start();
}
void start()
{
CurForm = new Animation();
Application.Run(CurForm);
}
public static readonly Manager Active = new Manager();
public static void Show()
{
if (Active.CurForm != null)
{
Active.CurForm.Invoke(new Action(() => { Active.CurForm.Show(); }));
}
}
public static void Hide()
{
if (Active.CurForm != null)
{
Active.CurForm.Invoke(new Action(() => { Active.CurForm.Hide(); }));
}
}
}
I open some modal form (ShowDialog). This modal form doesn't show in taskbar.
I easily can minimise it and after clicking on main form on task bar it show that modal form on top.
But when I show this loading animation while it's loading all necessary data.
some kind like that (of course it is just a sample to test it work, and in real app it tooks much time to load all data and form with lots of controls)
public modal()
{
Manager.Show();
InitializeComponent();
Thread.Sleep(5000);
Manager.Hide();
}
And when i'm trying to minimise and restore it like i said above it doesn't restore my modal form and just show my main not available form. And more than that it works in some cases but in some not.
Does anybody know why it is happens or how to fix it?
it is strange but when i modify like this, everything seems to work normal.
I just kill separate ui thread.
public class MyApplicationContext:ApplicationContext
{
public Form CurForm;
ManualResetEvent ready = new ManualResetEvent(false);
public MyApplicationContext()
{
CurForm=new Animation();
CurForm.Show();
}
}
class Manager
{
private MyApplicationContext CurContext;
Thread curt;
void start()
{
try
{
CurContext = new MyApplicationContext();
Application.Run(CurContext);
}
catch
{
CurContext.CurForm.Close();
}
}
private void Init()
{
curt = new Thread(start);
curt.SetApartmentState(ApartmentState.STA);
curt.IsBackground = true;
curt.Start();
}
public static Manager Active
{
get
{
if (active == null)
{
active = new Manager();
}
return active;
}
}
private static Manager active;
public static void Show()
{
Active.Init();
}
public static void Hide()
{
Active.curt.Abort();
}
Given the class below, to launch a splash screen on an alternate thread:
public partial class SplashForm : Form
{
private static Thread _splashThread;
private static SplashForm _splashForm;
public SplashForm()
{
InitializeComponent();
}
// Show the Splash Screen (Loading...)
public static void ShowSplash()
{
if (_splashThread == null)
{
// Show the form in a new thread.
_splashThread = new Thread(new ThreadStart(DoShowSplash));
_splashThread.IsBackground = true;
_splashThread.Start();
}
}
// Called by the thread.
private static void DoShowSplash()
{
if (_splashForm == null)
_splashForm = new SplashForm();
// Create a new message pump on this thread (started from ShowSplash).
Application.Run(_splashForm);
}
// Close the splash (Loading...) screen.
public static void CloseSplash()
{
// Need to call on the thread that launched this splash.
if (_splashForm.InvokeRequired)
_splashForm.Invoke(new MethodInvoker(CloseSplash));
else
Application.ExitThread();
}
}
This is called and closed with the following respective commands
SplashForm.ShowSplash();
SplashForm.CloseSplash();
Fine.
I am not exactly new to the TPL, of course we can show the form on another thread using something as simple as:
Task task = Task.Factory.StartNew(() =>
{
SomeForm someForm = new SomeForm();
someForm.ShowDialog();
};
My issue is closing this SomeForm down when you are ready. There must be a better way than creating a public static method in the SomeForm class like
private static SomeForm _someForm;
public static void CloseSomeForm()
{
if (_someForm.InvokeRequired)
_someForm.Invoke(new MethodInvoker(CloseSomeForm));
}
My question is, what is the best way to do the same thing as shown using the SplashForm class above using the Task Parrallel Library (TPL)? Specifically, the best way to close the form invoked on another thread from the UI.
Your question does not seem to be so much about a difference between Thread and Task because what you want is to get rid of the "dirty" static state. I suggest you encapsulate it into a class:
class SplashController
{
public void Run() {
_someForm = new SomeForm();
someForm.ShowDialog();
}
private SomeForm _someForm;
public void CloseSomeForm()
{
if (_someForm.InvokeRequired)
_someForm.Invoke(new MethodInvoker(CloseSomeForm));
}
}
You can call Run using whatever threading mechanism you like. CloseSomeForm does not use threading so it is independent of this problem.
You can now store a reference to an instance of SplashController wherever you like. In local variables or indeed in a static variable. The latter makes sense because there is exactly one splash screen.
Because the static state is now well encapsulated I don't see any problem with it being statically held.
You probably shouldn't do something like this
Task task = Task.Factory.StartNew(() =>
{
SomeForm someForm = new SomeForm();
someForm.ShowDialog();
};
because it would require a message loop to be present on the exact thread that creates the Form, which is a ThreadPool thread. But I haven't tested this.
You could try this:
public static Task<SplashForm> ShowSplash()
{
var tcs = new TaskCompletionSource<SplashForm>();
// Show the form in a new thread.
_splashThread = new Thread(() =>
{
var splashForm = new SplashForm();
tcs.SetResult(_splashForm);
// Create a new message pump on this thread (started from ShowSplash).
Application.Run(splashForm);
});
_splashThread.IsBackground = true;
_splashThread.Start();
}
this would allow you to remove the static modifier from CloseSplash:
// Close the splash (Loading...) screen.
public void CloseSplash()
{
// Need to call on the thread that launched this splash.
if (this.InvokeRequired)
this.Invoke(new MethodInvoker(CloseSplash));
else
Application.ExitThread();
}
May be used like this:
var form = await SplashForm.ShowSplash();
form.CloseSplash();
I have a FTP proccess that run without UI. and have a winform that use this ftp control. in that window I have a progressbar that show the ftp upload progress. The progress arrives to the window via interfase that is updated on the underliying presenter (I'm using MVP pattern).
My problem is when try to update the progress, it allways throw me this exception.
Through threads illegal operation: control 'prgProgresoSubido' is accessed from a thread other than that in which you created it.
That problem persists even if I use a BackGroundWorker in the Form.
// This is a delegated on presenter when a File finish to upload
void client_FileUploadCompletedHandler(object sender, FileUploadCompletedEventArgs e)
{
string log = string.Format("{0} Upload from {1} to {2} is completed. Length: {3}. ",
DateTime.Now, e.LocalFile.FullName, e.ServerPath, e.LocalFile.Length);
archivosSubidos += 1;
_Publicacion.ProgresoSubida = (int)((archivosSubidos / archivosXSubir) * 100);
//this.lstLog.Items.Add(log);
//this.lstLog.SelectedIndex = this.lstLog.Items.Count - 1;
}
// This is My interfase
public interface IPublicacion
{
...
int ProgresoSubida { set; }
}
/// And Here is the implementartion of the interfase on the form
public partial class PublicarForm : Form ,IPublicacion
{
//Credenciales para conectarse al servicio FTP
public FTPClientManager client = null;
public XmlDocument conf = new XmlDocument();
public string workingDir = null;
public webTalk wt = new webTalk();
private readonly PublicacionesWebBL _Publicador;
public PublicarForm()
{
InitializeComponent();
String[] laPath = { System.AppDomain.CurrentDomain.BaseDirectory};
String lcPath = System.IO.Path.Combine(laPath);
_Publicador = new PublicacionesWebBL(this, lcPath);
}
public int ProgresoSubida
{
set
{
// This is my prograss bar, here it throw the exception.
prgProgresoSubido.Value = value;
}
}
}
How can I do to avoid this problem ?
In general, all updates to the User Interface and Controls has to be done from the main thread (event dispatcher). If you attempt to modify the properties of a control from a different thread you will get an exception.
You must call Control.Invoke to invoke on the event dispatcher the method that updates your UI
Control.Invoke
Here, place a button and a label on a form, then try this
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(TestThread));
t.Start();
}
private void TestThread()
{
for (int i = 0; i < 10; i++)
{
UpdateCounter(i);
Thread.Sleep(1000);
}
}
private void UpdateCounter(int i)
{
if (label1.InvokeRequired)
{
label1.Invoke(new ThreadStart(delegate { UpdateCounter(i); }));
}
else
{
label1.Text = i.ToString();
}
}
}
Realize, that if you are firing an event from a thread, that the event will be on the same Thread. Therefore, if that thread is not the event dispatcher, you'll need to invoke.
Also, there may be mechanisms that BackgroundWorker gives you (As the commentator said) that simplify this for you, but I've never used it before so I'll leave that up to you to investigate.
As Alan has just pointed out, you must do all operations with UI controls in UI thread.
Just modify your property like this:
public int ProgresoSubida
{
set
{
MethodInvoker invoker = delegate
{
prgProgresoSubido.Value = value;
}
if (this.InvokeRequired)
{
Invoke(invoker);
}
else
{
invoker();
}
}
}
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.
I have two forms, the main form and one that pops up as a modal dialog. From a process spawned in the main form, I want to dynamically update the text on the modal dialog. Here's what I have:
In the main form, I do this:
// show the wait modal
var modal = new WaitDialog { Owner = this };
// thread the packaging
var thread = new Thread(() => Packager.PackageUpdates(clients, version, modal));
thread.Start();
// hopefully it worked ...
if (modal.ShowDialog() != DialogResult.OK)
{
throw new Exception("Something failed, miserably.");
}
The PackageUpdates method takes the modal dialog, and does this:
// quick update and sleep for a sec ...
modal.SetWaitLabelText("Downloading update package...");
Thread.Sleep(2000);
modal.SetWaitLabelText("Re-packaging update...");
To be thread safe, I do this in the modal dialog:
public void SetWaitLabelText(string text)
{
if (lblWaitMessage.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
lblWaitMessage.Text = text;
}
}
Everything works great ... most of the time. Every three or four times that the modal pops up, I get an exception on the lblWaitMessage.Text = text; and it's not invoking the command.
Am I missing something in this setup?
Like #Hans Passant pointed out, you should wait for the modal.Load-event. One good option is to use the ManualResetEvent to inform your thread to wait until that happens.
The WaitOne method will block the thread until the Set method is called. Here's a very simple setup which should do the trick.
public partial class Form1 : Form
{
ManualResetEvent m_ResetEvent;
public Form1()
{
InitializeComponent();
m_ResetEvent = new ManualResetEvent(false);
}
private void button1_Click(object sender, EventArgs e)
{
Dialog d = new Dialog { Owner = this, ResetEvent = m_ResetEvent };
var thread = new Thread(new ParameterizedThreadStart(DoSomething));
thread.Start(d);
if (d.ShowDialog() != System.Windows.Forms.DialogResult.OK)
{
throw new Exception("Something terrible happened");
}
}
private void DoSomething(object modal)
{
Dialog d = (Dialog)modal;
// Block the thread!
m_ResetEvent.WaitOne();
for (int i = 0; i < 1000; i++)
{
d.SetWaitLabelText(i.ToString());
Thread.Sleep(1000);
}
}
}
And here is the modal form
public partial class Dialog : Form
{
public Form Owner { get; set; }
public ManualResetEvent ResetEvent { get; set; }
public Dialog()
{
InitializeComponent();
}
public void SetWaitLabelText(string text)
{
if (label1.InvokeRequired)
{
Invoke(new Action<string>(SetWaitLabelText), text);
}
else
{
label1.Text = text;
}
}
private void Dialog_Load(object sender, EventArgs e)
{
// Set the event, thus unblocking the other thread
ResetEvent.Set();
}
}
I think you should rewrite the code to let thread.Start() isn't called before modal.ShowDialog().
As a workaround, you can try this:
public void SetWaitLabelText(string text) {
Invoke(new Action<string>(SetWaitLabelText2), text);
}
void SetWaitLabelText2(string text) {
lblWaitMessage.Text = text;
}
The first method always uses Invoke, regardless the value of InvokeRequired. The second method actually does the thing. This pattern is usable when you always call the function from another thread.