Rather than closing/respawning windows, it's often easier to simply hide and unhide.
Create the window whenever you create your main window and never close it. Add an onbeforeclose (or whatever that was called again...) event handler that cancels a user-initiated close and instead hides the window.
Now, you're already using a thread-safe dispatcher to modify the window contents: simply add one line to that event handler to unhide the window (this won't do anything if it's already visible), and you're good to go!
Incidentally, a useful thing to have in these scenario's is a custom TextWriter subclass. You can make one as follows:
public abstract class AbstractTextWriter : TextWriter {
protected abstract void WriteString(string value);
public override Encoding Encoding { get { return Encoding.Unicode; } }
public override void Write(char[] buffer, int index, int count) {
WriteString(new string(buffer, index, count));
}
public override void Write(char value) {
WriteString(value.ToString(FormatProvider));
}
public override void Write(string value) { WriteString(value); }
//subclasses might override Flush, Dispose and Encoding
}
public class DelegateTextWriter : AbstractTextWriter {
readonly Action<string> OnWrite;
readonly Action OnClose;
static void NullOp() { }
public DelegateTextWriter(Action<string> onWrite, Action onClose = null) {
OnWrite = onWrite;
OnClose = onClose ?? NullOp;
}
protected override void WriteString(string value) { OnWrite(value); }
protected override void Dispose(bool disposing) {
OnClose(); base.Dispose(disposing);
}
}
That way you can stick your form-updating logic into something like this...
var threadSafeLogWriter = new DelegateTextWriter(str => {
Action updateCmd = ()=>{
myControl...//append text to whatever control here
myControl.Show();
};
if(myControl.InvokeRequired) myControl.BeginInvoke(updateCmd);
else updateCmd();
});
... and use it everywhere, potentially even doing Console.SetOut
to catch output to Console.Write
. It's ok to call Write on this TextWriter on multiple threads since the implementation is self-synchronizing.
You could test it like this...
Console.SetOut(threadSafeLogWriter);
Parallel.For(0,100, i=>{
threadSafeLogWriter.Write("Hello({0}) ", i);
Thread.Yield();
Console.WriteLine("World({0})!",i);
});
If you do lots of logging of small messages, you may end up with a lot of BeginInvoke calls, and these are slow. As an optimization, you might instead enqueue all log messages to a ConcurrentQueue
or some other synchronized structure, and only BeginInvoke if the queue was empty before you enqueued to it. That way you usually do only one BeginInvoke between UI updates; and when the UI gets around to doing it's thing, it clears the flag and then appends all queued text at once. However, since all threads doing logging just dump their messages in whatever order they come into the logger, it can be very bad for readability to have lots of small log statements; best to log as large a string as possible at once to ensure it's not interrupted by another threads message; and if you do that then you won't have many BeginInvoke's anyhow and perf. will be less of an issue.