Winform - Continue from a ShowDialog without hiding the window - c#

In a multithreading environment.
I'm using ShowDialog in a progress form to block all user activities on other forms.
After finish part of the process I hide the progress form, I do some tasks and I show (ShowDialog) it again.
Everything's works fine but the progress form flickers.
Is there a way to continue from a ShowDialog without hiding the window or, probably better, is there a way to transform a Show to a ShowDialog and back again?
EDIT
My code is similar to this
class frmProgress : Form
{
// Standard stuff
public void DoSomeWorks()
{
async Task.Run(() => RunWork1());
ShowDialog();
if (_iLikeTheResultOfWork1)
{
async Task.Run(() => RunWork2());
ShowDialog();
}
}
void RunWork1()
{
// Do a lot of things including update UI
Hide();
}
void RunWork2()
{
// Do a lot of things including update UI
Hide();
}
}
EDIT 2
Thanks to everyone for the answers and the suggestions.
At the end I adopted the solution to Group some small works in one bigger work that I use in UI while in unit tests I still test the small works.
Actually it was not the solution I was looking for, I hoped to find a solution based on Handling the message pump or doing something similar (see System.Windows.Forms.Application source code starting from RunDialog(.) and all the called methods where I got lost before posting this question).

You can't do that. At least there is not an easy way that I am aware of. One way to tackle that is to change your code as follows:
//C#-ish pseudocode based on OPs example; doesn't compile
class frmProgress : Form
{
// Standard stuff
public void DoSomeWorks()
{
async Task.Run(() => RunWork1());
ShowDialog();
}
void RunWork1()
{
// Do a lot of things including update UI
if (_iLikeTheResultOfWork1)
{
async Task.Run(() => RunWork2());
}
else
{
Hide();
}
}
void RunWork2()
{
// Do a lot of things including update UI
Hide();
}
}
EDIT For those complaining the code doesn't compile, they are right. But this is the best the OP will get with that code sample and that's the reason I guess he is mercilessly downvoted.
But to make the answer more relevant to others, my suggestion is don't hide the progress form between the the 2 tasks, hide it when you are sure that the tasks have ended. All these by respecting the threading model of the context you are working on, something the OP's code doesn't do. Manipulating the UI in Winforms from methods that will eventually run in other threads won't work.

I suppose you have some methods like following:
void Task1()
{
Debug.WriteLine("Task1 Started");
System.Threading.Thread.Sleep(5000);
Debug.WriteLine("Task1 Finished");
}
void Task2()
{
Debug.WriteLine("Task2 Started");
System.Threading.Thread.Sleep(3000);
Debug.WriteLine("Task2 Finished");
}
If you are going to run them in parallel:
private async void button1_Click(object sender, EventArgs e)
{
this.Enabled = false;
//Show a loading image
await Task.WhenAll(new Task[] {
Task.Run(()=>Task1()),
Task.Run(()=>Task2()),
});
//Hide the loading image
this.Enabled = true;
}
If you are going to run them one by one:
private async void button1_Click(object sender, EventArgs e)
{
this.Enabled = false;
//Show a loading image
await Task.Run(()=>Task1());
await Task.Run(()=>Task2());
//Hide the loading image
this.Enabled = true;
}

Referencing Async/Await - Best Practices in Asynchronous Programming
I advise you take advantage of async event handlers and tasks to allow for non blocking calls while the form is in modal via show dialog
class frmProgress : Windows.Form {
// Standard stuff
public void DoSomeWorks() {
Work -= OnWork;
Work += OnWork;
Work(this, EventArgs.Empty);//raise the event and do work on other thread
ShowDialog();
}
private CancellationTokenSource cancelSource;
private event EventHandler Work = delegate { };
private async void OnWork(object sender, EventArgs e) {
Work -= OnWork; //unsubscribe
cancelSource = new CancellationTokenSource(); //to allow cancellation.
var _iLikeTheResultOfWork1 = await RunWork1Async(cancelSource.Token);
if (_iLikeTheResultOfWork1) {
await RunWork2Async(cancelSource.Token);
}
DialogResult = DialogResult.OK; //just an example
}
Task<bool> RunWork1Async(CancellationToken cancelToken) {
// Do a lot of things including update UI
//if while working cancel called
if (cancelToken.IsCancellationRequested) {
return Task.FromResult(false);
}
//Do more things
//return true if successful
return Task.FromResult(true);
}
Task<bool> RunWork2Async(CancellationToken cancelToken) {
// Do a lot of things including update UI
//if while working cancel called
if (cancelToken.IsCancellationRequested) {
return Task.FromResult(false);
}
//Do more things
//return true if successful
return Task.FromResult(true);
}
}
Note the use of cancellation token to allow tasks to be cancelled as needed.
The UI is now unblocked and the async functions can continue the work. No flickering as the form remain shown without any interruptions.

I could be missunderstood, here what I did, handleDialog with wndProc. I created an extra form and used Show(); method. After I showed form, async tasks are started. After I showed the form, I handled that form by wndProc.
protected override void WndProc(ref Message m) {
if((f2.IsDisposed || !f2.Visible)) {
foreach(var control in controlList) {
control.Enabled = true; // enable all controls or other logic
}
}
if(m.Msg == 0x18 && !f2.IsDisposed) { // notify dialog opens and double checks dialog's situation
foreach(var control in controlList.Where(ctrl => ctrl.Name != "button1")) {
control.Enabled = false; // disable except cancel button
}
}
base.WndProc(ref m);
}
Hope helps,

From the code you posted, you are calling Hide() at the end of your RunWork() methods, and then ShowDialog() right afterwards. If I understand correctly, you want to call Hide() first inside your RunWork() methods, which makes the main UI window accessable while the UI updates are occurring. After everything finishes, then the ShowDialog() method occurs and blocks the main UI thread again.
class frmProgress : Form
{
public bool _iLikeTheResultOfWork1 = true;
// Note: changed to async method and now awaiting the task
public async void DoSomeWorks()
{
await Task.Run(() => RunWork1());
ShowDialog();
if (_iLikeTheResultOfWork1)
{
await Task.Run(() => RunWork2());
ShowDialog();
}
}
// Hide window and wait 5 seconds. Note that the main window is active during this 5 seconds.
// ShowDialog() is called again right after this, so the dialog becomes modal again.
void RunWork1()
{
Hide();
Task.Delay(5000).Wait();
// Do a lot of things including update UI
}
void RunWork2()
{
Hide();
Task.Delay(5000).Wait();
// Do a lot of things including update UI
}
}
My code above will call RunWork1(), which hides the dialog for 5 seconds. During this time the main UI window will be active. Afterwards, the method returns and ShowDialog() is called. Close that window to call RunWork2() and start the process over again.
Is this what you are attempting to achieve? I just tested this code and it works with no visual "flickers". If this is what you want, does the "flickering" occur with this method?

"the form will always be closed when it leaves its modal message loop, the one that was started by ShowDialog().  The best you can do is simulate modality, display the form with Show() and disable all other windows."
https://social.msdn.microsoft.com/Forums/sharepoint/en-US/3f6c57a1-92fd-49c5-9f46-9454df80788c/possible-to-change-modality-of-a-dialog-box-at-runtime?forum=winforms
So you could enumerate all your app's Windows and enable/disable them when needed, plus set your dialog window to be always on top when simulating modality. I think always on top means it comes above all windows in the OS though, so you may need a timer instead to bring your window to the front (among your app windows) if it is set to simulate modality

Related

.NET Async in shutdown methods?

I have an application that connects to a REST API using async methods. I have this set up using async/await pretty much everywhere that connects to the API, however I have a question and some strange behavior that I don't completely understand. What I want to do is simply return a license in certain scenarios when the program shuts down. This is initiated by a window closing event; the event handler is as follows:
async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
...other synchronous code...
//Check for floating licensing
if (KMApplication.License != null && KMApplication.License.Scope != Enums.LicenseScope.Standalone)
{
for (int i = 0; i < 3; i++)
{
try
{
await KMApplication.License.ShutDown(KMApplication.Settings == null
? Enums.LicenseReturnModes.PromptOnShutdown
: KMApplication.Settings.LicenseReturnMode)
.ConfigureAwait(false);
break;
}
catch (Exception ex)
{
_logger.Warn("Exception in license release, attempt " + i, ex);
}
}
}
await KMApplication.ApiService.Disconnect().ConfigureAwait(false);
_logger.Info("Shutdown Complete");
Application.Current?.Shutdown();
}
When this runs I can step through in the debugger and it gets to the first license shutdown call which is the first async awaited call. Then when I press F10 to step to the next line of code it just shuts down and is gone. I verified that the license release that is supposed to be happening in that line is in face happening so it appears to run to completion of that line but then shuts down or crashes or something. I also looked at the logs and it never gets to the Shutdown Complete line and I don't believe it's getting to the ApiService.Disconnect either.
I also tried running this as a sync method using Task.Run(() => ...the method...).GetAwaiter().GetResult() but that just deadlocks on the first call.
How do I handle this and have it run the async release, wait for it to be done, then shut down?
The fundamental problem in what you're trying to do is that async/await assumes the main application thread continues running. This assumption directly conflicts with the shutdown action, whose job is to terminate all running tasks.
If you examine the documentation on Window_Closing, it states the following (and only the following):
Occurs directly after Close() is called, and can be handled to cancel window closure.
This is important. The only thing this is supposed to do is allow you to programmatically cancel the window closure, thus prompting some additional user action.
Your expectations are befuddled because of how async/await works. Async/await appears to run in a linear fashion; however, what actually happens is that control is passed back to the caller at the first await. The framework assumes at that point that you do not wish to cancel the form close, and the program is allowed to terminate, taking all other actions with it.
Fundamentally, all C-style programs have a main entry point, which runs a loop. It's been that way since the early days of C, and continues that way in WPF. However, in WPF, Microsoft got a bit clever, and decided to hide this from the programmer. There are a couple of options to deal with things that need to happen after main window closing:
Re-hijack the main loop from your program, and put the code there. The details on how to do this may be found here.
Set an explicit shutdown mode, and kick off the task to initiate that. Call Application.Shutdown() as the very last line of code you need to execute.
Here is an async version of the FormClosing event. It delays the closing of the form until the completion of the supplied Task. The user is prevented from closing the form before the completion of the task.
The OnFormClosingAsync event passes an enhanced version of the FormClosingEventArgs class to the handling code, with two additional properties: bool HideForm and int Timeout. These properties are read/write, much like the existing Cancel property. Setting HideForm to true has the effect of hiding the form while the async operation is in progress, to avoid frustrating the user. Setting Timeout to a value > 0 has the effect of abandoning the async operation after the specified duration in msec, and closing the form. Otherwise it is possible that the application could be left running indefinitely with a hidden UI, which could certainly be a problem. The Cancel property is still usable, and can be set to true by the handler of the event, to prevent the form from closing.
static class WindowsFormsAsyncExtensions
{
public static IDisposable OnFormClosingAsync(this Form form,
Func<object, FormClosingAsyncEventArgs, Task> handler)
{
Task compositeTask = null;
form.FormClosing += OnFormClosing; // Subscribe to the event
return new Disposer(() => form.FormClosing -= OnFormClosing);
async void OnFormClosing(object sender, FormClosingEventArgs e)
{
if (compositeTask != null)
{
// Prevent the form from closing before the task is completed
if (!compositeTask.IsCompleted) { e.Cancel = true; return; }
// In case of success allow the form to close
if (compositeTask.Status == TaskStatus.RanToCompletion) return;
// Otherwise retry calling the handler
}
e.Cancel = true; // Cancel the normal closing of the form
var asyncArgs = new FormClosingAsyncEventArgs(e.CloseReason);
var handlerTask = await Task.Factory.StartNew(
() => handler(sender, asyncArgs),
CancellationToken.None, TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default); // Start in a thread-pool thread
var hideForm = asyncArgs.HideForm;
var timeout = asyncArgs.Timeout;
if (hideForm) form.Visible = false;
compositeTask = Task.WhenAny(handlerTask, Task.Delay(timeout)).Unwrap();
try
{
await compositeTask; // Await and then continue in the UI thread
}
catch (OperationCanceledException) // Treat this as Cancel = true
{
if (hideForm) form.Visible = true;
return;
}
catch // On error don't leave the form hidden
{
if (hideForm) form.Visible = true;
throw;
}
if (asyncArgs.Cancel) // The caller requested to cancel the form close
{
compositeTask = null; // Forget the completed task
if (hideForm) form.Visible = true;
return;
}
await Task.Yield(); // Ensure that form.Close will run asynchronously
form.Close(); // Finally close the form
}
}
private struct Disposer : IDisposable
{
private readonly Action _action;
public Disposer(Action disposeAction) => _action = disposeAction;
void IDisposable.Dispose() => _action?.Invoke();
}
}
public class FormClosingAsyncEventArgs : EventArgs
{
public CloseReason CloseReason { get; }
private volatile bool _cancel;
public bool Cancel { get => _cancel; set => _cancel = value; }
private volatile bool _hideForm;
public bool HideForm { get => _hideForm; set => _hideForm = value; }
private volatile int _timeout;
public int Timeout { get => _timeout; set => _timeout = value; }
public FormClosingAsyncEventArgs(CloseReason closeReason) : base()
{
this.CloseReason = closeReason;
this.Timeout = System.Threading.Timeout.Infinite;
}
}
Since OnFormClosingAsync is an extension method and not a real event, it can only have a single handler.
Usage example:
public Form1()
{
InitializeComponent();
this.OnFormClosingAsync(Window_FormClosingAsync);
}
async Task Window_FormClosingAsync(object sender, FormClosingAsyncEventArgs e)
{
e.HideForm = true; // Optional
e.Timeout = 5000; // Optional
await KMApplication.License.ShutDown();
//e.Cancel = true; // Optional
}
The Window_FormClosingAsync handler will run in a thread-pool thread, so it should not include any UI manipulation code.
Unsubscribing from the event is possible, by keeping a reference of the IDisposable return value, and disposing it.
Update: After reading this answer, I realized that it is possible to add a real event FormClosingAsync in the form, without creating a class that inherits from the form. This can be achieved by adding the event, and then running an initialization method that hooks the event to the native FormClosing event. Something like this:
public event Func<object, FormClosingAsyncEventArgs, Task> FormClosingAsync;
public Form1()
{
InitializeComponent();
this.InitFormClosingAsync(() => FormClosingAsync);
this.FormClosingAsync += Window_FormClosingAsync_A;
this.FormClosingAsync += Window_FormClosingAsync_B;
}
Inside the initializer, in the internal handler of the native FormClosing event, all the subscribers of the event can be retrieved
using the GetInvocationList method:
var eventDelegate = handlerGetter();
if (eventDelegate == null) return;
var invocationList = eventDelegate.GetInvocationList()
.Cast<Func<object, FormClosingAsyncEventArgs, Task>>().ToArray();
...and then invoked appropriately. All this adds complexity, while the usefulness of allowing multiple handlers is debated. So I would probably stick with the original single-handler design.
Update: It is still possible to have multiple handlers using the original method OnFormClosingAsync. It is quite easy actually. The Func<T>
class inherits from Delegate, so it has invocation list like a real event:
Func<object, FormClosingAsyncEventArgs, Task> aggregator = null;
aggregator += Window_FormClosingAsync_A;
aggregator += Window_FormClosingAsync_B;
this.OnFormClosingAsync(aggregator);
No modification in the OnFormClosingAsync method is required.
Ok here is what I ended up doing. Basically the window closing kicks off a task that will wait for the release to happen and then invoke the shutdown. This is what I was trying to do before but it didn't seem to work in async void method but it seems to be when done this way. Here is the new handler:
void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
...other sync code...
Task.Run(async () =>
{
await InvokeKmShutdown();
(Dispatcher ?? Dispatcher.CurrentDispatcher).InvokeShutdown();
});
}
And the shutdown method looks like this:
async Task InvokeKmShutdown()
{
...other sync code...
await KMApplication.ApiService.Disconnect();
//Check for floating licensing
if (KMApplication.License != null && KMApplication.License.Scope != License.Core.Enums.LicenseScope.Standalone)
{
for (int i = 0; i < 3; i++)
{
try
{
await KMApplication.License.ShutDown(KMApplication.Settings == null
? Enums.LicenseReturnModes.PromptOnShutdown
: KMApplication.Settings.LicenseReturnMode);
break;
}
catch (Exception ex)
{
_logger.Warn("Exception in license release, attempt " + i, ex);
}
}
}
}
Hope it helps someone.
EDIT
Note that this is with an WPF app set to ShutdownMode="OnExplicitShutdown" in App.xaml so it won't shut down the actual app until I call the shutdown. If you are using WinForms or WPF is set to shut down on last window or main window close (main window close is the default I believe) you will end up with the race condition described in the comments below and may get the threads shut down before things run to completion.

C# Invoke button control on separate thread

I have seen a lot of questions about how to edit controls on c# form from a different thread but none make much sense to me. I understand that you can not change any UI from another thread than it's main. To make this work you have to use invoke and from there safely edit the control?
I have a button that starts writing in a file and the moment you press the button the button itself gets disabled so you can not start multiple threads that do exactly the same. When the writing is done I want the button to be available again but I can not get it working on this other thread.
I have this as the Generate_Click event from the form.
private void Generate_Click(object sender, EventArgs e)
{
Generate.Enabled = false;
int x = 512;
int y = 512;
MBrot mbrot = new MBrot(x, y);
PB_Update lb = new PB_Update(0, y, Generator_PB, Generate, mbrot, this);
lb.Start();
}
And this is in PB_Update.cs the ThreadWork() function, when the while loop is done the writing to the file is done and so is the thread so its ended and given a messagebox with "finished" now as last the button needs to be enabled again.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.Enabled = true;
}
For WinForms you can execute directly on the thread which the control was created on through the Control.BeginInvoke method, you can use Control.Invoke as well but, Control.BeginInvoke is preferred for UI operations.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.BeginInvoke((Action)delegate()
{
Generate_.Enabled = true;
});
}
Somehow, get a reference to the form that hosts the generate_ button (let's call it myform). Then, at the bottom of your ThreadWork:
myform.Invoke(new Action(() => {
myform.SetGenerateEnabled();
}));
And then inside your form create that method that enables the button appropriately. (I used a method rather than just updating the button directly so that you don't publicly expose the button.)
This executes the commands inside the { ... } on myform's thread, which is a UI thread, because it is UI. At least, that's what I understand. This is how I do all of my UI updating from other threads.
Here's a simple example of a way to kick off an async task that disables a button for 5 seconds and then enables it again. Meanwhile, the rest of the UI is functional.
Note that this async method exists in the same class as your Generate_Click event, and runs on the UI thread. This means that it can enable and disable the button. But the long running task executes on a separate thread, so it doesn't lock the UI.
Hopefully this sample provides you a base to modify for your own code:
private void Generate_Click(object sender, EventArgs e)
{
DisableButton(sender as Button, 5);
}
private async void DisableButton(Button sender, int secondsToDisable)
{
sender.Enabled = false;
// In your code, you would kick off your long-running process here as a task
await Task.Run(()=>Thread.Sleep(TimeSpan.FromSeconds(secondsToDisable)));
sender.Enabled = true;
}

Form design messed up when using Show()

I'm trying to make a loading screen window. I use Show() instead of ShowDialog() because I have some code to execute after showing it. When using ShowDialog() form is fine but when using Show() form is messed up. What is causing this and what is the solution? Here is how I did it:
bool closeLoadingWindow = false;
void ShowLoadingWindow()
{
LoadingWindow loadingWindow = new LoadingWindow();
loadingWindow.Show();
while (!closeLoadingWindow);
loadingWindow.Close();
return;
}
public MainWindow()
{
Thread loadingWindowThread = new Thread(ShowLoadingWindow);
loadingWindowThread.Start();
InitializeComponent();
// ...
closeLoadingWindow = true;
}
When using ShowDialog():
When using Show():
The reason ShowDialog is working is because your while loop won't be executing, once the runtime hits that line of code it will stop processing until the form is dimissed.
Your code doesn't make sense, the point of using a thread here is to keep the "busy" code (your while loop) out of the main UI thread so it doesn't block. However, you are trying to create/show your form on the same thread, and a non-UI thread at that.
You don't necessarily need to use Show here, you can use ShowDialog but it is a little bit trickier in terms of dimissing the form etc. However, to solve the problem you have at the minute I would recommend you do:
LoadingWindow _loadingWindow;
void ShowLoadingWindow()
{
if (_loadingWindow == null)
_loadingWindow = new LoadingWindow();
_loadingWindow.Show();
}
void HideLoadingWindow()
{
if (_loadingWindow != null)
{
_loadingWindow.Close();
_loadingWindow.Dispose();
}
}
void LoadSomething()
{
while (...)
{
// busy code goes here
}
// after code is finished, close the form
MethodInvoker closeForm = delegate { HideLoadingWindow(); };
_loadingWindow.Invoke(closeForm);
}
public MainWindow()
{
ShowLoadingWindow();
new Thread(LoadSomething).Start();
}
}
FYI - Depending on the nature of exactly what your trying to do in the thread it might be a better approach to use the Task Parallel Library rather than creating a dedicated thread, various benefits like continuation / cancellation support.

How to continue executing code after calling ShowDialog()

the Form.ShowDialog() method causes the code to be halted until the newly called form is closed. I need the code to continue running after the ShowDialog() method is called. I googled and read about using backgroundworker? But that is the first time i have heard of that and never used it before.
Form2 form2this = new Form2();
form2this.ShowDialog();
MessageBox.Show("Something");
This code gets executed after clicking a button, how can i still call ShowDialog to prevent the user from interacting with the main form but still allowing the main form to continue with its work?
Sorry if its been discussed but everything i found seems extremely difficult to perform such a simple task. I am actually surprised its not included in the SHowDialog method. for instance ShowDialog().Continue would be cool.
If you just want the code to continue on instead of blocking until the popup is closed consider using Show instead of ShowDialog.
If you have some action that you want to have the parent form doing while the child form is up, then yes, it could be appropriate to use a BackgroundWorker (or just manually starting a new Thread/Task). It would be helpful to know more about what that task is though. If you need to interact with the main form, or the child form, then that seems like trouble to me; if you just need to do some background task with no UI interaction then this is the right line of thought.
Another possibility is that what you want to do really just should be something done in the child form, rather than the parent form.
As long as you do asynchronous operations during the time that the modal dialog is opened, you can do it as simply as shown below, assuming button1_Click() is the event handler for a button.
private async void button1_Click(object sender, EventArgs e)
{
// create and display modal form
Form2 modalForm = new Form2();
BeginInvoke((Action)(() => modalForm.ShowDialog()));
// do your async background operation
await DoSomethingAsync();
// close the modal form
modalForm.Close();
}
private async Task DoSomethingAsync()
{
// example of some async operation....could be anything
await Task.Delay(10000);
}
I found that when I used the solution that suggested to use Show(), I could end up in cases where the dialog I wanted to be modal would end up behind the main form, after switching back and forth between apps. That never happens when I use the solution above.
This is my way, so ugly but i have no better idea.
private void AppUiMain_Shown(object sender, EventArgs e)
{
var loading = new AppUiLoading();
loading.Shown += (o, args) =>
{
bool isLoading = true;
loading.Top = (int)(loading.Top * 1.16);
Application.DoEvents();//refresh ui
EventHandler ehr = null;
EventHandler ehe = null;
ehr = (ss, ee) =>
{
App.Instance.Ready -= ehr;
App.Instance.Error -= ehe;
isLoading = false;
};
ehe = (ss, ee) =>
{
loading.Text = "Error";
loading.ShowAbortButton("Error occur");
};
App.Instance.Error += ehe;
App.Instance.Ready += ehr;
InitApp();
//HACK: find a better way to `refresh' main form
Application.DoEvents();
this.Height++;
this.Height--;
//HACK: find a better way to keep message looping on ShowDialog
while (isLoading)
Application.DoEvents();
loading.Close();
};
loading.ShowDialog(this);
}
To continue code execution without closing modal dialog WindowsFormsSynchronizationContext.Current.Post(-=> {"Your code"}, null); can be used. Here you can find more detail -
http://newapputil.blogspot.in/2015/05/continue-executing-code-after-calling.html
Run an async call to show modal. Here an example in wpf:
private Window waitView;
/// <summary>
/// Closes a displayed WaitView from code.
/// </summary>
public void CloseWaitView()
{
if(waitView != null)
{
// Work on the gui Thread of waitView.
waitView.Dispatcher.Invoke(new Action(() => close()));
}
}
/// <summary>
/// Closes a displayed WaitView and releases waitView-Instance.
/// </summary>
private void close()
{
waitView.Close();
waitView = null;
}
/// <summary>
/// Showes a modal WaitView (Window).
/// </summary>
public void ShowWaitView()
{
// instance a new WaitViewWindow --> your Window extends Window-Class
waitView = new WaitViewWindow();
// prepare a operation to call it async --> your ShowDialog-call
var asyncCall = new Action(() => waitView.Dispatcher.Invoke(
new Action(() => waitView.ShowDialog())
));
// call the operation async
// Argument 1 ar:
// ar means IAsyncResult (what should be done, when come back from ShowDialog -->
// remove view memory with set waitView to null or ... dispose
// the second argument is an custom parameter you can set to use in ar.AsyncState
asyncCall.BeginInvoke(ar => waitView = null, null);
// all from here is done during ShowDialog ...
}
I suppose next solution for async ShowDialog:
public bool DialogResultAsync
{
get;
private set;
}
public async Task<bool> ShowDialogAsync()
{
var cts = new CancellationTokenSource();
// Attach token cancellation on form closing.
Closed += (object sender, EventArgs e) =>
{
cts.Cancel();
};
Show(); // Show message without GUI freezing.
try
{
// await for user button click.
await Task.Delay(Timeout.Infinite, cts.Token);
}
catch (TaskCanceledException)
{ }
}
public void ButtonOkClick()
{
DialogResultAsync = true;
Close();
}
public void ButtonCancelClick()
{
DialogResultAsync = false;
Close();
}
And in main form you must use this code:
public async void ShowDialogAsyncSample()
{
var msg = new Message();
if (await msg.ShowDialogAsync())
{
// Now you can use DialogResultAsync as you need.
System.Diagnostics.Debug.Write(msg.DialogResultAsync);
}
}

Updating a dialog from another form

I know there are a lot of similar questions out there and I have read through a lot of them. Unfortunately, I still couldn't solve my problem after reading through them - however I am relatively new to C#. According to docs the problem of not being thread safe results in an InvalidOperationException when using the debugger. This is not the case in my problem.
I've recreated the problem with a simple raw test class to concentrate on my problem.
The main form is supposed to show a kind of a progress dialog.
public partial class ImportStatusDialog : Form
{
public ImportStatusDialog()
{
InitializeComponent();
}
public void updateFileStatus(string path)
{
t_filename.Text = path;
}
public void updatePrintStatus()
{
t_printed.Text = "sent to printer";
}
public void updateImportStatus(string clientName)
{
t_client.Text = clientName;
}
public void updateArchiveStatus()
{
t_archived.Text = "archived";
}
}
When that code is called without any Invoke() from the main form:
private void button1_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
nDialog.updateFileStatus("test");
Thread.Sleep(1000);
nDialog.updateImportStatus("TestClient");
Thread.Sleep(1000);
nDialog.updatePrintStatus();
Thread.Sleep(1000);
nDialog.updateArchiveStatus();
Thread.Sleep(1000);
nDialog.Close();
}
And even when I call it like this:
private void button3_Click(object sender, EventArgs e)
{
ImportStatusDialog nDialog = new ImportStatusDialog();
nDialog.Show();
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateFileStatus("Test");
});
}
else
{
nDialog.updateFileStatus("Test");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updatePrintStatus();
});
}
else
{
nDialog.updatePrintStatus();
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateImportStatus("cName");
});
}
else
{
nDialog.updateImportStatus("cName");
}
Thread.Sleep(1000);
if (this.InvokeRequired)
{
this.Invoke((MethodInvoker)delegate
{
nDialog.updateArchiveStatus();
});
}
else
{
nDialog.updateArchiveStatus();
}
Thread.Sleep(1000);
nDialog.Close();
}
the dialog which looks like this in the designer (in my example)
will be displayed like that:
When I use ShowDialog() instead of Show() the dialog displays correnctly, but as the API Doc points out
You can use this method to display a modal dialog box in your
application. When this method is called, the code following it is not
executed until after the dialog box is closed
which is not what I want, especially as it would mean that the dialog updating would only happen after it has been closed again.
What am I doing wrong here? This seems to be a trivial problem and yet the solution evades me. Please keep in mind that I am new to C# GUI programming.
Also, I would like to ask what would be the right place for using the Invoke()? Do Would you use it in the main form when calling the dialog methods or rather in the dialog update methods itself?
It doesn't look like you're using multiple threads here, so the invoke stuff is not required. Invoke is only needed for cross-thread calls - you are creating multiple forms, but all your code is running in the same thread.
If you're doing your work in the main UI thread (as implied by the code being in a button click event), then just call Application.DoEvents() periodically to allow the progress form to refresh.
A better solution would be to use a BackgroundWorker for your work, and have it report its progress periodically.
Then you can update the progress form in the BackgroundWorker's ProgressChanged event (which will be executed in the main thread, so you still don't have to invoke anything).

Categories

Resources