I'm trying to create GUI program that generates HTML invoices, and sends them for printing. I have this working. However, now I want to introduce threading.
I have a form with a BackgroundWorker. The Background worker runs this code:
#region BackGroundWorker
private void bg_htmlGeneration_DoWork(object sender, DoWorkEventArgs e)
{
//SOME MORE CODE..
foreach (XElement ele in Lib.GetInvoiceElement(inv, ico.Supplier))
{
PrintDocument(Lib.CreateHTMLFile());
}
}
#endregion
public void PrintDocument(string fileName)
{
var th = new Thread(() =>
{
WebBrowser webBrowserForPrinting = new WebBrowser();
webBrowserForPrinting.DocumentCompleted += new WebBrowserDocumentCompletedEventHandler(PrintDocumentHandler);
webBrowserForPrinting.Url = new Uri(fileName);
Application.Run();
});
th.SetApartmentState(ApartmentState.STA);
th.Start();
}
public void PrintDocumentHandler(object sender, WebBrowserDocumentCompletedEventArgs e)
{
((WebBrowser)sender).Print();
((WebBrowser)sender).Dispose();
Application.ExitThread();
}
Everything runs through fine. However, the WebBrowser object refuses to print. There are no errors (that are obvious), the program finishes off with nothing sent to the printer. When I take away the threading, the program works again.
My knowledge of threading is weak, and I'm pretty much teaching myself - so presumably I'm misunderstanding how threading priority is set.
Here's How it should work:
User selects Invoice(s) on Main Form, chooses to print.
Background thread goes away and prints them while user continues on the program.
Any ideas would be appreciated.
Main problem with your code is WebBrowser wrong using.
WebBrowser supposed to be used for interactive web-browsing, during it user do some things in the internet. But in your case you are using WebBrowser just for the printing after downloading the html. This is wrong by two reasons:
Your code creates whole Windows Forms Control and not using even half of its functionality.
Your code tries to use the WinForms Control in the background thread, which leads to the unexpected behaviour.
BackgroundWorker class supposed to be used for
execute a time-consuming operation (like downloads and database transactions) in the background.
Much more:
You must be careful not to manipulate any user-interface objects in your DoWork event handler. Instead, communicate to the user interface through the ProgressChanged and RunWorkerCompleted events.
Your code will fail in the background thread, because WinForms control is a user-interface object.
Just for the record, WebBrowser.Print method invokes native windows API, so you have no chance that this will work in background. From the disassembly code:
this.AxIWebBrowser2.ExecWB(NativeMethods.OLECMDID.OLECMDID_PRINT,
NativeMethods.OLECMDEXECOPT.OLECMDEXECOPT_DONTPROMPTUSER,
ref obj,
IntPtr.Zero);
So, my suggestion for your code is:
Remove usage of the WebBrowser class in the background. Use HttpWebRequest instead for downloading the web content.
Choose other way for the printing your downloaded content. Options are:
PrintDocument implementation (example for it is here).
Use MS Office classes for opening and printing your html-files (first parameter for the Print method is a Boolean Background, I think this can help you). As far as I know, even in 2010 this approach works well.
Check other questions about printing (here is a discussion of printing the images, but this is the same thing, I think).
PS: in the comments you've said that you may need the PDF from your html. I did this by C# by two ways:
Batching using the wkhtmltopdf
Using Microsoft Office Add-in: Microsoft Save as PDF or XPS.
This Add-in should be installed on the server, after that you can easily use MS Office classes for saving the output in the PDF format.
Some update here:
As we have an async/await and TPL options for the time-consuming operations, I don't recommend you to use the BackgroundWorker class anymore.
Related
I am trying to develop a simple application that must have GUI components. It will be a service in the taskbar tray but must query the database every few minutes to check for changes, then post to a web server the results. It will run 24/7.
This is my first application, and so have been getting some help along the way from SO. When I first used the FluentScheduler, I had trouble (C# FluentScheduler Job not Repeating) but got it working as a simple proof of concept with a console app.
As I tried taking what I had learned and implementing it with my Windows Forms solution, I couldn't get it working at all because once it ran the Application.Run(ThisForm); command, the scheduler did nothing. Eventually while troubleshooting, I stumbled across this:
https://github.com/fluentscheduler/FluentScheduler/issues/169
I see that you're using the library from something like a Windows Forms/WPF application. Starting threads/tasks from a GUI application is a pain in the ***, maybe that's what's biting you. Fingers crossed to be something else, diving up on STA threads, dispatchers, synchronization contexts and alikes is no fun.
So now I am left wondering what I am supposed to do? Am I supposed to develop the scheduled tasks as a console app leaving an API for a WPF application to communicate with, or am I supposed to work through the pain he is describing and make it work within WPF?
As this is my first C# project, it seems pretty complicated to separate the two components, but am willing to learn if that is the right choice. I am still very early in the project just doing proof of concepts of each needed feature and so can easily switch to WPF, UWP, or whatever else is most appropriate. It will have minimal GUI, just a few forms to fill out username / password type stuff and options to sync.
Even though this FluentScheduler has about a quarter million downloads, maybe there is a better one that doesn't suffer from the same limitations you could recommend.
Based on the earlier post you linked to, I see a few problems with your code:
Your call to JobManager.Initialize is unreachable because it occurs after Application.Run, which blocks until the application shuts down (e.g., when the last window is closed).
The FluentScheduler will schedule your job to run on an arbitrary worker thread, but your action accesses or manipulates UI elements. In both WPF and Windows Forms, you can only touch UI elements from the main thread. If your job needs to touch the UI, it must first marshal itself back onto the UI thread.
The scheduled action in your original post does not make sense:
Action someMethod = new Action(() =>
{
Form1 ThisForm = new Form1();
ThisForm.Text ="HELLO";
});
Specifically, you are creating a new window that is never shown, rather than modifying one that already exists.
Here is a simple example project that you should be able to use as a starting point. It displays the current time, updating once per second. I used WPF, as I haven't used Windows Forms in years, and there's no compelling reason to use it these days.
SchedulerText.xaml:
<Window x:Class="WpfTest.SchedulerTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<TextBlock x:Name="_textBlock"
FontSize="18pt"
TextAlignment="Center"
VerticalAlignment="Center" />
</Grid>
</Window>
SchedulerTest.xaml.cs:
using System;
using FluentScheduler;
namespace WpfTest
{
public partial class SchedulerTest
{
public SchedulerTest()
{
InitializeComponent();
JobManager.AddJob(
this.DoScheduledWork,
schedule => schedule.ToRunNow().AndEvery(1).Seconds());
}
private void DoScheduledWork()
{
// Go query your database, or do whatever your main job is.
// You don't want to do this on the UI thread, because it
// will block the thread and prevent user interaction.
DoPrimaryWorkOffUIThread();
// If you need to communicate some sort of result to the user,
// do it on the UI thread.
Dispatcher.Invoke(new Action(ShowResultsOnUIThread));
}
private DateTime _currentResult;
private void DoPrimaryWorkOffUIThread()
{
_currentResult = DateTime.Now;
}
private void ShowResultsOnUIThread()
{
_textBlock.Text = $"{_currentResult:h:mm:ss}";
}
}
}
Note that you don't have to initialize the job in the windows's constructor, but that would be the easiest place to do it.
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.
We are using Microsoft's UIAutomation framework to develop a client that monitors events of a specific application and responds to them in different ways. We've started with the managed version of the framework, but due to delay issues, moved to the native version wrapped in UIACOMWrapper. After more issues with performance inside our (massive) WPF application, we decided to move it to a separate terminal application (transfer the events to our WPF app through UDP) which seemed to fix all the performance issues. The only problem is that it seems that every several minutes, the events for TabSelection, StructureChanged, WindowOpened and WindowClosed stop being captured for a few minutes. Surprisingly PropertyChanged events are still received and handled while this happens. I will post the relevant code of our event monitor, but this is probably irrelevant as we have seen similar behavior when using Microsoft's own AccEvent utility. I can't post the code of the monitored application as it is proprietary and confidential as well, I can say that it is a WinForms application that hosts WPF windows and also quite massive.
Has anyone seen this sort of behavior while working with the UI Automation framework?
Thank you for your time.
Here's the monitor code (I know the event handling is on the UI Automation threads here but moving it to a dedicated thread did not change anything):
public void registerHandlers()
{
//Register on structure changed and window opened events
System.Windows.Automation.Automation.AddStructureChangedEventHandler(
this.getMsAutomationElement(), System.Windows.Automation.TreeScope.Subtree, this.handleStructureChanged);
System.Windows.Automation.Automation.AddAutomationEventHandler(
System.Windows.Automation.WindowPattern.WindowOpenedEvent,
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree,
this.handleWindowOpened);
System.Windows.Automation.Automation.AddAutomationEventHandler(
System.Windows.Automation.WindowPattern.WindowClosedEvent,
System.Windows.Automation.AutomationElement.RootElement,
System.Windows.Automation.TreeScope.Subtree,
this.handleWindowClosed);
this.registerValueChanged();
this.registerTextNameChange();
this.registerTabSelected();
this.registerRangeValueChanged();
}
private void registerRangeValueChanged()
{
if (this.getMsAutomationElement() != null)
{
System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange,
System.Windows.Automation.RangeValuePattern.ValueProperty);
}
}
private void unregisterRangeValueChanged()
{
System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
this.handlePropertyChange);
}
private void registerValueChanged()
{
if (this.getMsAutomationElement() != null)
{
System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange,
System.Windows.Automation.ValuePattern.ValueProperty);
}
}
private void unregisterValueChanged()
{
System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
this.handlePropertyChange);
}
private void registerTextNameChange()
{
if (this.getMsAutomationElement() != null)
{
System.Windows.Automation.Automation.AddAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
System.Windows.Automation.TreeScope.Subtree, this.handlePropertyChange,
System.Windows.Automation.AutomationElement.NameProperty);
}
}
private void unregisterTextNameChange()
{
System.Windows.Automation.Automation.RemoveAutomationPropertyChangedEventHandler(
this.getMsAutomationElement(),
this.handlePropertyChange);
}
private void handleWindowOpened(object src, System.Windows.Automation.AutomationEventArgs e)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(DateTime.Now.ToShortTimeString() + " " + "Window opened:" + " " +
(src as System.Windows.Automation.AutomationElement).Current.Name);
System.Windows.Automation.AutomationElement element = src as System.Windows.Automation.AutomationElement;
//this.sendEventToPluginQueue(src, e, element.GetRuntimeId(), this.getAutomationParent(element).GetRuntimeId());
//Fill out the fields of the control added message
int[] parentId = this.getAutomationParent(element).GetRuntimeId();
this.copyToIcdArray(parentId,
this.protocol.getMessageSet().outgoing.ControlAddedMessage.Data.controlAdded.parentRuntimeId);
this.copyToIcdArray(element.GetRuntimeId(),
this.protocol.getMessageSet().outgoing.ControlAddedMessage.Data.controlAdded.runtimeId);
//Send the message using the protocol
this.protocol.send(this.protocol.getMessageSet().outgoing.ControlAddedMessage);
}
private void copyToIcdArray(int[] runtimeId, ICD.UI_AUTOMATION.RuntimeId icdRuntimeId)
{
icdRuntimeId.runtimeIdNumberOfItems.setVal((byte)runtimeId.Count());
for (int i = 0; i < runtimeId.Count(); i++)
{
icdRuntimeId.runtimeIdArray.getElement(i).setVal(runtimeId[i]);
}
}
private void handleWindowClosed(object src, System.Windows.Automation.AutomationEventArgs e)
{
if (src != null)
{
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine(DateTime.Now.ToShortTimeString() + " " + "Window closed:" + " " +
(src as System.Windows.Automation.AutomationElement).GetRuntimeId().ToString());
System.Windows.Automation.AutomationElement element = src as System.Windows.Automation.AutomationElement;
this.copyToIcdArray(element.GetRuntimeId(),
this.protocol.getMessageSet().outgoing.ControlRemovedMessage.Data.controlRemoved.runtimeId);
//Send the message using the protocol
this.protocol.send(this.protocol.getMessageSet().outgoing.ControlRemovedMessage);
//this.sendEventToPluginQueue(src, e, element.GetRuntimeId());
}
}
EDIT:
I forgot to mention that I strongly suspect that the issue is that one of the UI-Automation event handler threads gets stuck somehow. The reason I believe this, is that when the problem occurred in my monitor, I started an instance of AccEvent and it received all the missing events that my monitor was not getting. This means that the events are being fired but not passed to my monitor.
EDIT2:
I forgot to mention that this happens running in Windows 8 with the specific target application, I have not seen this phenomenon on my own Windows 7 machine with other applications. Another interesting thing is that it seems to happen periodically more or less, but regardless of when I subscribe to events, i.e. it can happen almost immediately after subscribing but then it takes several minutes to reoccur.
I'm afraid I don't know the cause of the delays that you're seeing, but here are some thoughts on this...
Everything I say below relates to the native UIA API in Windows, not the managed .NET UIA API. All improvements to UIA in recent years have been made to the Windows UIA API. So whenever I write UIA client C# code, I call UIA through a managed wrapper that I generate with the tlbimp.exe SDK tool.
That is, I first generate the wrapper with a command like...
"C:\Program Files (x86)\Microsoft SDKs\Windows\v8.1A\bin\NETFX 4.5.1 Tools\x64\tlbimp.exe" c:\windows\system32\uiautomationcore.dll /out:Interop.UIAutomationCore.dll
Then I include a reference to the Interop.UIAutomationCore.dll in my C# project, add "using Interop.UIAutomationCore;" to my C# file, and then I can do things like...
IUIAutomation uiAutomation = new CUIAutomation8();
IUIAutomationElement rootElement = uiAutomation.GetRootElement();
uiAutomation.AddAutomationEventHandler(
20016, // UIA_Window_WindowOpenedEventId
rootElement,
TreeScope.TreeScope_Descendants,
null,
this);
...
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
{
// Got a window opened event...
}
In Windows 7, there were some important constraints around UIA event handlers. It was easy to write event handlers which didn't account for those constraints, and that could lead to long delays when interacting with UIA. For example, it was important to not add or remove a UIA event handler from inside an event handler. So at the time, I intentionally made no UIA calls at all from inside my event handlers. Instead, I'd post myself a message or add some action to a queue, allow my event handler to return, and take whatever action I wanted to in response to the event shortly afterwards on another thread. This required some more work on my part, but I didn't want to risk hitting delays. And any threads I created would be running in an MTA.
An example of the action described above is in my old focus tracking sample up at https://code.msdn.microsoft.com/windowsapps/Windows-7-UI-Automation-6390614a/sourcecode?fileId=21469&pathId=715901329. The file FocusEventHandler.cs creates the MTA thread and queues messages to avoid making UIA calls inside the event hander.
Since Window 7, I know the constraints in UIA relating to threading and delays have been relaxed, and the likelihood of encountering delays has been reduced. More recently, there were some improvements between Windows 8.1 and Windows 10 in this area, so if it'd be practical to run your code on Windows 10, it would be interesting to see if the delays still repro there.
I know this is time consuming, but you might be interested in removing the interaction with UIA inside your event handlers and seeing if the delays go away. If they do, it'd be a case of determining which action seems to trigger the problem, and seeing if there's an alternative way of achieving your goals without performing the UIA interaction in the event handlers.
For example, in your event handler, you call...
this.getAutomationParent(element).GetRuntimeId();
I expect this will lead to two calls back into the provider app which generated the event. The first call is to get the parent of the source element, and the second call is to get the RuntimeId of that parent. So while UIA is waiting for your event handler to return, you've called twice back into UIA. While I don't know that that's a problem, I'd avoid it.
Sometimes you can avoid a cross-proc call back to the provider process by having some data of interest cached with the event itself. For example, say I know I'm going to want the RuntimeId of an element that raised a WindowOpened event. I can ask UIA to cache that data with the events I receive, when I register for the events.
int propertyRuntimeId = 30000; // UIA_RuntimeIdPropertyId
...
IUIAutomationCacheRequest cacheRequestRuntimeId = uiAutomation.CreateCacheRequest();
cacheRequestRuntimeId.AddProperty(propertyRuntimeId);
uiAutomation.AddAutomationEventHandler(
20016, // UIA_Window_WindowOpenedEventId
rootElement,
TreeScope.TreeScope_Descendants,
cacheRequestRuntimeId,
this);
...
public void HandleAutomationEvent(IUIAutomationElement sender, int eventId)
{
// Got a window opened event...
// Get the RuntimeId from the source element. Because that data is cached with the
// event, we don't have to call back through UIA into the provider process here.
int[] runtimeId = sender.GetCachedPropertyValue(propertyRuntimeId);
}
On a side note, when practical, I always cache data when dealing with events or accessing elements through UIA, (by using calls such as FindFirstBuildCache(),) as I want to avoid as many cross-proc calls as possible.
So my advice would be:
Use the native Windows UIA API with a managed wrapper generated by tlbimp.exe.
Cache as much data as possible with the events, to avoid having to call back into the provider process unnecessarily later.
Avoid calls back into UIA from inside a UIA event handler.
Thanks,
Guy
I have seen this behavior in my project. The solution was unsubscribes and resubscribe to the events using a timer.
In addition, I set off any action following the events in a new task (running in an STA thread pool).
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!
I'm facing problem in showing FolderBrowserDialog instance created and called from a non-UI thread. It doesn't get renders properly.
Being more specific, it doesn't shows the folder tree but displays only the Make New Folder OK and Cancel
All the shell dialogs, including FolderBrowserDialog, require the COM apartment for the thread to be set to STA. You are probably missing the Thread.SetApartmentState() call:
private void button1_Click(object sender, EventArgs e) {
var t = new Thread(() => new FolderBrowserDialog().ShowDialog());
t.IsBackground = true;
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
Beware that you cannot set the owner of the dialog, it easily gets lost behind a window of another application. Which makes showing forms or dialogs on a worker thread less than a good idea.
I am not sure why you would want to do this. On a worker-thread all neccessary values for your calculation should be available. There should be no need for user-interaction to get more input.
Maybe a redesign would be more helpful in your case. Think about providing the selected folder to your worker-thread before starting it.
EDIT (reply to the comment):
If you want to do some logging my answer still applies. Your worker-thread should know where to log exceptions and not start to ask the user.
Do you use a logging framework? If not, have a look at log4net for instance. Here you normally pre-configure your logging (the log-level, path, format, ...) in a xml-file. There is no user interaction needed. Though the user could change the logging path (in the xml file).