if I call form.show() on a WinForms object from another thread, the form will throw an exception. Is where any way I can add a new, visible form to the main app thread? Otherwise, how can I open the form without stopping my currently executing thread?
Here is my sample code. I am attempting to start a thread and then execute some work within that thread. As the work progresses, I will show the form.
public void Main()
{
new Thread(new ThreadStart(showForm)).Start();
// Rest of main thread goes here...
}
public void showForm()
{
// Do some work here.
myForm form = new myForm();
form.Text = "my text";
form.Show();
// Do some more work here
}
Try using an invoke call:
public static Form globalForm;
void Main()
{
globalForm = new Form();
globalForm.Show();
globalForm.Hide();
// Spawn threads here
}
void ThreadProc()
{
myForm form = new myForm();
globalForm.Invoke((MethodInvoker)delegate() {
form.Text = "my text";
form.Show();
});
}
The "invoke" call tells the form "Please execute this code in your thread rather than mine." You can then make changes to the WinForms UI from within the delegate.
More documentation about Invoke is here: http://msdn.microsoft.com/en-us/library/zyzhdc6b.aspx
EDIT: You will need to use a WinForms object that already exists in order to call invoke. I've shown here how you can create a global object; otherwise, if you have any other windows objects, those will work as well.
You should call Application.Run() after you call form.Show(). For example:
public void showForm()
{
// Do some work here.
myForm form = new myForm();
form.Text = "my text";
form.Show();
Application.Run();
// Do some more work here
}
As for the details behind why, this msdn post may help.
The best way by my experience:
var ac = (ReportPre)Application.OpenForms["ReportPre"];
Thread shower = new Thread(new ThreadStart(() =>
{
if (ac == null)
{
this.Invoke((MethodInvoker)delegate () {
ac = new ReportPre();
ac.Show();
});
}
else
{
this.Invoke((MethodInvoker)delegate
{
pictureBox1.Visible = true;
});
if (ac.InvokeRequired)
{
ac.Invoke(new MethodInvoker(delegate {
ac.Hide();
ac.Show();
}));
}
}
}));
shower.Start();
If your use case is to display a GUI while the Main GUI Thread is busy (like loading bar) you can do the following:
private void MethodWithLoadingBar()
{
Thread t1 = new Thread(ShowLoading);
t1.Start();
// Do the Main GUI Work Here
t1.Abort();
}
private void ShowLoading()
{
Thread.Sleep(1000); //Uncomment this if you want to delay the display
//(So Loading Bar only shows if the Method takes longer than 1 Second)
loadingGUI = new LoadingGUI();
loadingGUI.label1.Text = "Try to Connect...";
loadingGUI.ShowDialog();
}
My LoadingGUI is a simple Form with a public Label and a ProgressBar with Style set to Marquee
After searching the web and not finding a good solution, I came up with my own:
public async Task FunctionWhereIStartForm( Func<Delegate,object>invoke )
{
//this is on another thread
invoke( new MethodInvoker( ( ) =>
{
new formFoo().Show( );
} ) );
}
Then call like this:
await FunctionWhereIStartForm(Invoke);
Related
I am trying to start a winForm from a thread, but when i do so, the form show but none of the labels are loaded ( the background where they should be is white ) and the form is frozen.
I've tried it with some other winForms that i know that work just fine and it still doesn't seem to work ? Has anyone encountered this problem ?
I know the question is vague but there isn't really any specific code that I could give to help understand the problem.
That is because the Message Loop runs on UI thread only. And when a control or window is created in any other thread, it cannot access that message loop. And thus, cannot process user input.
To solve this, try creating a window from UI thread and create a thread from that window to do whatever you want to do in different thread.
UI thread is supposed to be one.
Then, I suggest you to open your form calling a method of your original form thread, like in the example below:
(To test it just create an empty form called MainForm and paste this code in it)
public delegate void OpenFormDelegate(string txt);
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
var button1 = new Button();
button1.Text = "Run for 5 secs and open new window";
button1.Dock = DockStyle.Top;
this.Controls.Add(button1);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
public void Run()
{
Thread.Sleep(5000); // sleep for 5 seconds
this.BeginInvoke(new OpenFormDelegate(OpenNewForm), "Hello World !");
}
public void OpenNewForm(string text)
{
Form f = new Form();
f.Text = text;
f.Show();
}
}
It is related to thread access, when the new form is created it will not be able to access the UI thread. Use the main thread to create the form and new thread to process the infomation.
I am in the process of creating a POS system using C# and WinForms.
I am using a form with some text and an image to indicate when a long running process is performed, like sales printing and DB update after the sale. But when I do that, only the AjaxLoader form is showing and it's not calling the update functions below it.
This is my code.
public void completeSale()//invoked on Sell button
{
loader = new AjaxLoader();//this is a form
loader.label1.Text = "Printing...";
ThreadStart threadStart = new ThreadStart(Execution);
Thread thread = new Thread(threadStart);
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
private void Execution()
{
this.Invoke((MethodInvoker)delegate { loader.ShowDialog(this); });
Application.DoEvents();
update_sale("Sold");//method not getting called at all
this.Invoke((MethodInvoker)delegate { loader.Dispose(); });
}
This is my Ajax loader form that I need to display, that is supposed to block my POS form. So upon finishing the printing (doing background task) I need to close the loader.
The problem is that the lines
Application.DoEvents();
update_sale("Sold");//method not getting called at all
is never reached.
What am I doing wrong?
The .ShowDialog() on a form is a blocking call, so your code will wait until the form that is shown as dialog is .Closed()
I would also recommend using using async Task as this makes working with Threads much much easier!
I've changed your code to show this.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
await completeSale();
}
AjaxLoader loader = null;
public async Task completeSale()//invoked on Sell button
{
//for info, this is how I set up AjaxLoader form properties in the designer.
loader = new AjaxLoader();
loader.label1.Text = "Printing...";
loader.TopMost = true;
loader.WindowState = FormWindowState.Normal;
loader.StartPosition = FormStartPosition.CenterParent;
loader.ShowInTaskbar = false;
loader.ControlBox = false;
loader.FormBorderStyle = FormBorderStyle.None;
//loader.PointToClient(this.DesktopLocation);
await Execution();
}
private async Task Execution()
{
if (loader.InvokeRequired)
this.Invoke((MethodInvoker)delegate { loader.Show(this); });
else
loader.Show(this);
//Application.DoEvents();
await update_sale("Sold");
if (loader.InvokeRequired)
this.Invoke((MethodInvoker)delegate { loader.Close(); });
else
loader.Close();
}
private async Task update_sale(string v)
{
//long running process like printing etc..
await Task.Delay(3000);
}
}
this will do something like this:
On the AjaxLoader form I added a progress bar that is set to style = Marquee
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;
I am writing a test application where I need to put Form on separate thread.
So if I create Form window from main thread and set its .Owner = this everything works.
If I spawn thread UIThread and set Owner from new thread I get exception.
Getting exception is understandable since you can't access forms directly.
My question is is there a message that I need to catch on main thread and do BeginInvoke to push it on it's message pump? Since UIForm ShowInTaskbar is set to false I need to click on main application in taskbar and restore with all its children windows.
private void UIThread() // New Thread call
{
UIForm form = new UIForm();
form.ShowInTaskbar = false;
form.Owner = this;
Application.Run(form); // Expected Exception
}
I am not sure, maybe Application.Run shall be called only once per application. See if this one will work for you
Application.Run(new Form1());
-----------------
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
var thread = new Thread(
() =>
{
var form2 = new Form {Owner = this};
});
thread.Start();
}
}
I am trying to start a winForm from a thread, but when i do so, the form show but none of the labels are loaded ( the background where they should be is white ) and the form is frozen.
I've tried it with some other winForms that i know that work just fine and it still doesn't seem to work ? Has anyone encountered this problem ?
I know the question is vague but there isn't really any specific code that I could give to help understand the problem.
That is because the Message Loop runs on UI thread only. And when a control or window is created in any other thread, it cannot access that message loop. And thus, cannot process user input.
To solve this, try creating a window from UI thread and create a thread from that window to do whatever you want to do in different thread.
UI thread is supposed to be one.
Then, I suggest you to open your form calling a method of your original form thread, like in the example below:
(To test it just create an empty form called MainForm and paste this code in it)
public delegate void OpenFormDelegate(string txt);
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
var button1 = new Button();
button1.Text = "Run for 5 secs and open new window";
button1.Dock = DockStyle.Top;
this.Controls.Add(button1);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
public void Run()
{
Thread.Sleep(5000); // sleep for 5 seconds
this.BeginInvoke(new OpenFormDelegate(OpenNewForm), "Hello World !");
}
public void OpenNewForm(string text)
{
Form f = new Form();
f.Text = text;
f.Show();
}
}
It is related to thread access, when the new form is created it will not be able to access the UI thread. Use the main thread to create the form and new thread to process the infomation.