I'm creating an application that uses .Net and Mono, it uses cross-threaded forms as I was having bad response from the child windows.
I created a test program with 2 forms: the first (form1) has a single button (button1) and the second (form2) is blank, code snippet below.
void openForm()
{
Form2 form2 = new Form2();
form2.ShowDialog();
}
private void button1_Click(object sender, EventArgs e)
{
Thread x = new Thread(openForm);
x.IsBackground = true;
x.Start();
}
This works fine in .Net, but with Mono, the first window will not gain focus when you click it (standard .ShowDialog() behaviour) rather than .Show() behaviour as .Net uses.
When I use .Show(), on .Net and Mono the window just flashes then disappears. If I put a 'MessageBox.Show()' after 'form2.Show()' it will stay open until you click OK.
Am I missing something in that code or does Mono just not support that? (I'm using Mono 2.8.1)
Thanks in advance, Adrian
EDIT: I realised I forgot 'x.IsBackground = true;' in the code above so child windows will close with the main window.
It's almost never the right thing to do in a Windows app to have more than one thread talk to one window or multiple windows which share the same message pump.
And it's rarely necessary to have more than one message pump.
The right way to do this is either to manually marshal everything back from your worker threads to your Window, using the 'Invoke' method, or use something like BackgroundWorker, which hides the details for you.
In summary:
Don't block the UI thread for time-consuming computation or I/O
Don't talk to the UI from more than one thread.
If you use Winforms controls, you shold "touch" the object always in main UI thread.
And at least - calling new Form.ShowDialog() in new thread does not make sense.
EDIT:
If you want easy work with Invoke/BeginInvoke you can use extension methods:
public static class ThreadingExtensions {
public static void SyncWithUI(this Control ctl, Action action) {
ctl.Invoke(action);
}
}
// usage:
void DoSomething( Form2 frm ) {
frm.SyncWithUI(()=>frm.Text = "Loading records ...");
// some time-consuming method
var records = GetDatabaseRecords();
frm.SyncWithUI(()=> {
foreach(var record in records) {
frm.AddRecord(record);
}
});
frm.SyncWithUI(()=>frm.Text = "Loading files ...");
// some other time-consuming method
var files = GetSomeFiles();
frm.SyncWithUI(()=>{
foreach(var file in files) {
frm.AddFile(file);
}
});
frm.SyncWithUI(()=>frm.Text = "Loading is complete.");
}
Related
using System.Windows.Forms;
public class App
{
[STAThread]
public static void Main()
{
string fname;
using (var d = new OpenFileDialog())
{
if (d.ShowDialog() != DialogResult.OK)
{
return;
}
fname = d.FileName;
}
//Application.ExitThread();
for (; ;)
;
}
}
The above code shows me a file dialog. Once I select a file and press open, the for loop is executed, but the (frozen) dialog remains.
Once I uncomment Application.ExitThread() the dialog disappears as expected.
Does that work as intended? Why doesn't using make the window disappear? Where can I find more info about this?
You have discovered the primary problem with single-threaded applications... long running operations freeze the user interface.
Your DoEvents() call essentially "pauses" your code and gives other operations, like the UI, a chance to run, then resumes. The problem is that your UI is now frozen again until you call DoEvents() again. Actually, DoEvents() is a very problematic approach (some call it evil). You really should not use it.
You have better options.
Putting your long running operation in another thread helps to ensure that the UI remains responsive and that your work is done as efficiently as possible. The processor is able to switch back and forth between the two threads to give the illusion of simultaneous execution without the difficulty of full-blown multi-processes.
One of the easier ways to accomplish this is to use a BackgroundWorker, though they have generally fallen out of favor (for reasons I'm not going to get into in this post: further reading). They are still part of .NET however and have a lower learning curve then other approaches, so I'd still suggest that new developers play around with them in hobby projects.
The best approach currently is .NET's Tasks library. If your long running operation is already in a thread (for example, it's a database query and you are just waiting for it to complete), and if the library supports it, then you could take advantage of Tasks using the async keyword and not have to think twice about it. Even if it's not already in a thread or in a supported library, you could still spin up a new Task and have it executed in a separate Thread via Task.Run(). .NET Tasks have the advantage of baked in language support and a lot more, like coordinating multiple Tasks and chaining Tasks together.
JDB already explained in his answer why (generally speaking) your code doesn't work as expected. Let me add a small bit to suggest a workaround (for your specific case and for when you just need to use a system dialog and then go on like it was a console application).
You're trying to use Application.DoEvents(), OK it seems to work and in your case you do not have re-entrant code. However are you sure that all relevant messages are correctly processed? How many times you should call Application.DoEvents()? Are you sure you correctly initialize everything (I'm talking about the ApplicationContext)? Second problem is more pragmatic, OpenFileDialog needs COM, COM (here) needs STAThread, STAThread needs a message pump. I can't tell you in which way it will fail but for sure it may fail.
First of all note that usually applications start main message loop using Application.Run(). You don't expect to see new MyWindow().ShowDialog(), right? Your example is not different, let Application.Run(Form) overload creates the ApplicationContext for you (and handle HandleDestroyed event when form closes which will finally call - surprise - Application.ExitThread()). Unfortunately OpenFileDialog does not inherit from Form then you have to host it inside a dummy form to use Application.Run().
You do not need to explicitly call dlg.Dispose() (let WinForms manage objects lifetime) if you add the dialog inside the form with the designer.
using System;
using System.Windows.Forms;
public class App
{
[STAThread]
public static void Main()
{
string fname = AskForFile();
if (fname == null)
return;
LongRunningProcess(fname);
}
private static string AskForFile()
{
string fileName = null;
var form = new Form() { Visible = false };
form.Load += (o, e) => {
using (var dlg = new OpenFileDialog())
{
if (dlg.ShowDialog() == DialogResult.OK)
fileName = dlg.FileName;
}
((Form)o).Close();
};
Application.Run(form);
return fileName;
}
}
No, you don't have to call Application.ExitThread().
Application.ExitThread() terminates the calling thread's message loop and forces the destruction of the frozen dialog. Although "that works", it's better to unfreeze the dialog if the cause of the freeze is known.
In this case pressing open seems to fire a close-event which doesn't have any chance to finish. Application.DoEvents() gives it that chance and makes the dialog disappear.
I use AllocConsole() to open a Console in a winform application.
How can I prevent the application from exiting when the Console is closed?
EDIT
The update of completionpercentage from time to time is what I want to show in console
void bkpDBFull_PercentComplete(object sender, PercentCompleteEventArgs e)
{
AllocConsole();
Console.Clear();
Console.WriteLine("Percent completed: {0}%.", e.Percent);
}
I tried the richtextBox as the alternative
s =(e.Percent.ToString());
richTextBox1.Clear();
richTextBox1.AppendText("Percent completed: " +s +"%");
But I can't see the completionpercentage update time to time. It only appears when it is 100% complete.
Any alternative?
I know this is a task that seldom pops up but I had something similar and decided to go with a couple hacks.
http://social.msdn.microsoft.com/Forums/vstudio/en-US/545f1768-8038-4f7a-9177-060913d6872f/disable-close-button-in-console-application-in-c
-Disable the "Close" button on a custom console application.
You're textbox solution should work as well. It sounds a lot like your calling a function from the main thread that is tying up the form which is also on the main thread and is causing you grief when updating your textbox. Consider creating a new thread and either an event handler to update your textbox or use the invoke methodinvoker from the new thread to update the textbox. Link below from an already answered question on how to complete this.
How to update textboxes in main thread from another thread?
public class MainForm : Form {
public MainForm() {
Test t = new Test();
Thread testThread = new Thread((ThreadStart)delegate { t.HelloWorld(this); });
testThread.IsBackground = true;
testThread.Start();
}
public void UpdateTextBox(string text) {
Invoke((MethodInvoker)delegate {
textBox1.AppendText(text + "\r\n");
});
}
}
public class Test {
public void HelloWorld(MainForm form) {
form.UpdateTextBox("Hello World");
}
}
Refer to the answers over here. As mentioned in the answers, there is no way to stop the application from getting closed.
But as a workaround, you can have your own text output solution described in one of the answers.
You can build first the console application that recieves arguments and writes it to the console. Place it, where the main application starts.
From main application you can first kill process and then reopen it with a new argument.
It's the altarnative way.
This question already has answers here:
Run one instance of program
(2 answers)
Closed 9 years ago.
There is an option in my application to hide the window - form.hide(), and to put an notifyicon in the system tray, and when you click the notifyicon there will be a form.show().
If someone will try to run two instances of the app, I want
a. not to run the new instance
b. to show the window of the first instance
I already have a loop to check if a process with the same name exists.
and I can tell the new app not to run ( return in the program.cs before Application.run(new form()))
but I yet have to tell the first app to show its main window.
I have the process (of the first instance) , i can get its handle its id etc.
the question
How to show the window using it's process?
For the first part of the question, here is what you can do. Add this in the Main before you show your form. The benefit of this is that you don't check by process name (which might not be unique), but you create a mutex which is somehow "global".
using (Mutex applicationMutex = new Mutex(true, "SomeRandomTextHere", out mutexCreated))
{
if (!mutexCreated)
{
// Application is already running. Aborting..
return;
}
// Application.Run(..) goes here, plus other interesting stuff
}
For the second part of your question I would suggest the following:
Create a named event and set it initially to false. Then create a worker thread in your application that monitors this event. When it is signaled, Invoke your Show method from your main form.
Another approach is to search for the window handle of the main process and bring it to front. This article can give you ideas.
Bear in mind that doing a loop through all processes is not as efficient as using a mutex. If you don't care about speed, clean code and you just want this app to work then use this loop.. To me code is poetry.
Rewrote the code just for you this will give you exactly what you want. It will check for duplicates and focus the screen when a duplicate is opened.
EventWaitHandle ProgramOpen = new EventWaitHandle(false, EventResetMode.ManualReset, "ProgramOpen198472");
EventWaitHandle FocusProgram = new EventWaitHandle(false, EventResetMode.ManualReset, "FocusMyProgram198472");
private delegate void focusConfirmed(); Thread FocusCheck;
private void focus() { FocusProgram.WaitOne(); this.Invoke(new focusConfirmed(()=>{this.Show(); this.BringToFront();}));}
private void Form1_Load(object sender, EventArgs e)
{
if (ProgramOpen.WaitOne(0))
{
FocusProgram.Set();
this.Close();
}
ProgramOpen.Set();
}
private void HideButton_Click(object sender, EventArgs e)
{
this.Hide();
FocusProgram.Reset();
FocusCheck = new Thread(focus);
FocusCheck.Start();
}
private void showToolStripMenuItem_Click(object sender, EventArgs e)
{
FocusProgram.Set();
}
I've got a form called MyForm, and I want to edit it's properties in the middle of the program's execution, from outside of the class itself.
Here's what I got:
static void Main()
{
MyForm main = new MyForm();
main.ShowDialog();
main.Text = "Hello";
}
However, it seems like ShowDialog() just freezes the program until I close the form, so main.Text = "Hello"; won't be executed until I close the form.
I've also tried using main.Show() but it just closes the form after I've executed all the code in my Main() function, so the text "Hello" will only flash quickly.
I would need it so that I can have the form open at all times and change it's controls in the middle of the program's execution, from outside of the class itself.
How can I achieve this? Should I run the form in a different thread or something?
There are multiple questions here, you should try to focus on one at a time, so let me highlight the questions.
Why does my program close when Main exits?
How can I avoid "freezing" the program when showing a form?
How can I access the contents of a form from elsewhere?
Answers:
That's the design of how the lifetime of a program is. When the main thread (running the Main method) terminates, the program closes. Any open forms are closed in the process. Solution is to not allow Main to exit, typically by using Application.Run(main); in your case, showing the form and waiting for it to close.
You use Show and not ShowDialog, but since you have no other form keeping the program open, your program closes. Show returns after showing the form, returning to whatever the program was doing. In your case, the program has nothing left to do, so it terminates.
You need to store a reference to your form somewhere the rest of your program can access it, you can use a static field/property somewhere, or you can pass the form around to the various parts that need it.
First of all, I don't know what is your specific scenario. Perhaps my solution is good for you, but if it isn't, please, try to tell us what exactly are you trying to achieve.
static void Main()
{
var main = new MyForm();
//Initialize a new thread with the `DoSomething()` method
//and pass the form as a parameter
var thread = new Thread(() => DoSomething(main)) {IsBackground = true};
thread.Start();
main.ShowDialog();
}
static void DoSomething(MyForm main) {
//Update the form title
main.Text = "Hello";
//Wait one second
Thread.Sleep(1000);
//Update the form title again
main.Text = "World";
}
Sorry for the long post, but I tried to explain the problem very detailed so that no confusion should arise. The last sentence contains the actual question.
I'm programming a multi-thread application with C#/.NET.
The application consists of a main window, which visualizes data, coming from a pressure sensor. The sensor data is acquired in an own thread.
The data is also logged in an instance of class ListView:
There is the possibility to save the logged data to file on disk via a "Save" button (should open an instance of .NET class SaveFileDialog).
This SaveFileDialog is also running in an own thread.
Now there's a problem when calling the method SaveFileDialog.ShowDialog():
System.InvalidOperationException was unhandled
Message="Cross-thread operation not valid: Control 'tlpMain' accessed from a thread other than the thread it was created on."
Source="System.Windows.Forms"
The problem arises because the owner (the main window) of the SaveFileDialog is running in another thread.
Here's the code, which creates the thread for the SaveFileDialog():
private void bSave_Click(object sender, EventArgs e)
{
Thread saveFileDialog = new Thread(OpenSaveFileDialog);
saveFileDialog.SetApartmentState(ApartmentState.STA);
saveFileDialog.Start();
}
Code for method OpenSaveFileDialog():
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
saveFileDialog.Filter = "Text Files (*.txt)|*.txt|CSV (*.csv)|*.csv|All Files (*.*)|*.*";
saveFileDialog.FilterIndex = 0;
/* Call "ShowDialog" with an owner ("this.Parent") to achieve, so that
* the parent window is blocked and "unclickable".
*
* Danger of an "InvalidOperationException" because "this.Parent" control
* is running (was created) in another thread.
* But "this.Parent" should not be modified by this method call.
*/
DialogResult pressedButton = saveFileDialog.ShowDialog(this.Parent);
...
The InvalidOperationException is only thrown/displayed when running the application with Visual Studio's debugger. It is no problem - so far - when running the application "normally".
But I would like to avoid this problem.
I tried to build a wrapper method (SaveFileDialog):
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
...
SaveFileDialog(saveFileDialog, this.Parent);
}
Wrapper method :
private void SaveFileDialog(SaveFileDialog saveFileDialog, Control owner)
{
if (owner.InvokeRequired)
BeginInvoke(new dSaveFileDialog(SaveFileDialog), new object[] { saveFileDialog, owner });
else
{
DialogResult pressedButton = saveFileDialog.ShowDialog(owner);
...
This leads to a TargetInvocationException although the Main() method is labeled with [STAThreadAttribute]:
InnerException: System.Threading.ThreadStateException
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. This exception is only raised if a debugger is attached to the process."
Source="System.Windows.Forms"
Does anybody have a clue how to open the SaveFileDialog in a way, so that the main window will be blocked ("unclickable") without having the (thread) trouble?
Thank you.
The cross-thread exception you get during debugging is a Managed Debugging Assistant. They are not normally active outside of the debugger. That explains why you do not see when you run the application outside of Visual Studio.
It looks like you have discovered on your own that you simply cannot do anything to a UI element from a thread other than the main UI thread. You use the ISynchronizeInvoke methods, namely Invoke or BeginInvoke, to marshal the execution of an operation onto the UI thread so that you can safely access UI elements.
I still see a problem with your code though. In the OpenSaveFileDialog method, which is running on the worker thread, you are calling the constructor for SaveFileDiaglog which, of course, is a UI element. You just cannot do this. It is worth repeating. You cannot do anything to a Form or Control from a worker thread. That includes calling the constructor.
Sorry for the late reply.
First of all thank you for your quick and helpful responses.
The tip that's not possible
do anything to a Form or Control from
a worker thread
helped me a lot.
I usually not doing GUI programming for Microsoft's Windows and so I'm not so familiar with it.
So I reconsidered the previous source code because I wanted to solve the actual problem
(not doing GUI things from a worker thread) and would like to have a clean and logical code structure.
Therefore I've read in the topics of Window's Component Object Model (COM) and the used threading model:
What is COM:
http://www.microsoft.com/com/default.mspx
Understanding and Using COM Threading Models:
http://msdn.microsoft.com/en-us/library/ms809971.aspx
Understanding The COM Single-Threaded Apartment Part 1:
http://www.codeproject.com/KB/COM/CCOMThread.aspx
Now the code looks like this:
The main window ("UI thread") is started in ApartmentState STA
...
ThreadStart threadStart = delegate { RunMainWindow(mainWindow); };
Thread mainWindowThread = new Thread(threadStart);
mainWindowThread.SetApartmentState(ApartmentState.STA);
mainWindowThread.Start();
...
"Save" button event handler (main window):
private void bSave_Click(object sender, EventArgs e)
{
OpenSaveFileDialog();
}
Method "OpenSaveFileDialog" (main window):
private void OpenSaveFileDialog()
{
SaveFileDialog saveFileDialog = new SaveFileDialog();
...
DialogResult pressedButton = saveFileDialog.ShowDialog();
...
}
There is still space for optimizations (for sure), but I'm comfortable with this - preliminary - result.
So thanks a lot for your help.
Follow this microsoft blogpost: http://blogs.msdn.com/b/smondal/archive/2011/05/11/10059279.aspx
Just two methods and you are done!