c# winform System.InvalidOperationException' exception in System.Windows.Forms.dll on an event invoker - c#

I basically have two winform opened (LoadForm and MainForm).
After I click to a button in LoadForm I need it to close LoadForm and do some operation on the MainForm (updating its element) without blocking the whole UI.
To update MainForm's element from LoadForm I'm using an EventHandler like this:
load loadForm = new load();
loadForm.AccessDone += UpdateList;
loadForm.ShowDialog();
UpdateList is the function inside MainForm
public void UpdateList(object sender, EventArgs e)
{
Console.WriteLine("Updating the anime on the application.");
tier1.Controls.Clear();
tier2.Controls.Clear();
tier3.Controls.Clear();
tier4.Controls.Clear();
tier5.Controls.Clear();
tier6.Controls.Clear();
tier7.Controls.Clear();
tier8.Controls.Clear();
tier9.Controls.Clear();
tier10.Controls.Clear();
notier.Controls.Clear();
//progressBar1.Maximum = Global.Mal.AnimeList.Count;
//progressBar1.Show();
foreach (Anime anime in Global.Mal.AnimeList)
{
DragDropControl userControl = new DragDropControl(anime.Title, anime.MainPicture.Large, anime.MyListStatus.Score, anime.Id);
//progressBar1.PerformStep();
switch (anime.MyListStatus.Score)
{
case 1:
tier1.Controls.Add(userControl);
break;
case 2:
tier2.Controls.Add(userControl);
break;
case 3:
tier3.Controls.Add(userControl);
break;
case 4:
tier4.Controls.Add(userControl);
break;
case 5:
tier5.Controls.Add(userControl);
break;
case 6:
tier6.Controls.Add(userControl);
break;
case 7:
tier7.Controls.Add(userControl);
break;
case 8:
tier8.Controls.Add(userControl);
break;
case 9:
tier9.Controls.Add(userControl);
break;
case 10:
tier10.Controls.Add(userControl);
break;
default:
notier.Controls.Add(userControl);
break;
}
}
//progressBar1.Hide();
//progressBar1.Step = 0;
Debug.WriteLine("Updating terminated.");
}
Process starts when a button in LoadForm is clicked.
//Start everything from here after the click
private async void button1_Click(object sender, EventArgs e)
{
this.Close(); //Closing LoadForm
Task.Run(() => LoadAnime()); //Run the task
}
//Task to run
private async Task LoadAnime()
{
await Global.Mal.GetAccessToken(textBox1.Text);
await Global.Mal.GetAnimeList();
//Invoke MainForm method
AccessDone?.Invoke(this, EventArgs.Empty);
}
I tried to comment everything in UpdateList that does something on the MainForm's element and it does work.
EDIT:
By adding this in UpdateList I don't get the error, but the MainForm is still blocked, so I can't do nothing until it ends.
if (InvokeRequired)
{
Invoke((Action)(() => UpdateList(sender, e)));
return;
}
I checked the details of the error:
Cross-thread operation not valid: Control 'notier' accessed from a thread other than the thread it was created on winform
Is there a better way to handle these things that you would have done (asking as a newbie programmer)?
EDIT 2:
I tried what #Jimi suggested and came to this conclusion:
Instead of using invoke I call LoadForm and wait it to ends like this
private void button1_Click(object sender, EventArgs e)
{
using (var f = new load())
{
if (f.ShowDialog() == DialogResult.OK)
{
UpdateList();
}
}
}
LoadForm changes DialogResult if everything went ok.
private async void button1_Click(object sender, EventArgs e)
{
try
{
//Do something
//Returns OK
this.DialogResult = DialogResult.OK;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error" , MessageBoxButtons.OK, MessageBoxIcon.Error);
}
//Closing the form.
this.Close();
}
The problem is that the MainForm is still locked. I tried by running a Task or Thread but then I still get Cross-thread operation not valid: Control 'notier' accessed from a thread other than the thread it was created on winform

Related

C# Multi-threading and writing data to text files. UI becomes unresponsive [duplicate]

This question already has answers here:
WinForm Application UI Hangs during Long-Running Operation
(3 answers)
Closed 5 years ago.
My program is suppose to perform tests on 8 electronic products of the same model simultaneously. The previous programmer has implemented some form of multi-threading in the program to accomplish this. However, when 5 slots or more are being tested, the UI becomes unresponsive and the results being written to a text file may get corrupted.
Below I will insert a pseudo-code on what's going on in the program.
private void button1_Click(object sender, EventArgs e)
{
//create_thread_1 <= mainFunction 1
//start thread 1
}
private void button2_Click(object sender, EventArgs e)
{
//create_thread_2 <= mainFunction 2
//start thread 2
}
private void button3_Click(object sender, EventArgs e)
{
//create_thread_3 <= mainFunction 3
//start thread 3
}
private void mainFunction1
{
//perform test A
//write test A result to textFile1 //calls writeToTextFile1
//perform test B
//write test B result to textFile1 //calls writeToTextFile1
//continues on and finishes all tests
//aborts thread1
//end
}
private void mainFunction2
{
//perform test A
//write test A result to textFile2 //calls writeToTextFile2
//perform test B
//write test B result to textFile2 //calls writeToTextFile2
//continues on and finishes all tests
//aborts thread2
//end
}
private void mainFunction3
{
//perform test A
//write test A result to textFile3 //calls writeToTextFile3
//perform test B
//write test B result to textFile3 //calls writeToTextFile3
//continues on and finishes all tests
//aborts thread3
//end
}
private void writeToTextFile1
{
//creates and saves results into textFile1
}
private void writeToTextFile2
{
//creates and saves results into textFile2
}
private void writeToTextFile3
{
//creates and saves results into textFile3
}
My theory is that only a single thread can open and write data into a text file at a single time, so when another thread have to write data, that thread has to wait and causes the UI to become unresponsive. Am I right here? If I'm not, any advice is greatly appreciated.
One of the solutions that I have read online is to declare the WriteToTextFile function as a new Thread so that other main threads can wait for each other without slowing down the UI. Is this the correct approach?
EDIT: added the important parts of the coding for better understanding..This code runs for one slot only but the other 9 slots basically uses the same code here
private void button1_Click(object sender, EventArgs e)
{
if (this.button1.Text == "START")
{
this.txtSerial1.ReadOnly = false;
this.txtSerial1.Select();
MessageBox.Show("SLOT 1: Scan the serial number and press ENTER", "3458A
Heat Rack", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
}
else if (System.Windows.Forms.DialogResult.OK == MessageBox.Show("SLOT 1: Are
you sure about stopping?", "3458A Heat Rack",
MessageBoxButtons.OKCancel, MessageBoxIcon.Exclamation))
{
this.call_main1.Abort();
this.sentry1.Close();
this.sentry1.Dispose();
MessageBox.Show("SLOT 1: Unit can be safely removed now", "3458A Heat
Rack", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
this.txtSerial1.Clear();
this.txtStart1.Clear();
this.txtStatus1.Clear();
this.info1.Clear();
this.button1.Text = "START";
this.button1.BackColor = this.startColour;
this.txtStatus1.BackColor = Control.DefaultBackColor;
}
}
private void textBox1_KeyPress(object sender, KeyPressEventArgs e)
{
int num;
int test_num = default(int);
double resultrelay = default(double);
if (e.KeyChar == '\r')
{
if (this.txtSerial1.Text.Length == 0)
{
this.txtSerial1.ReadOnly = true;
}
else if (this.txtSerial1.Text.Length >= 10)
{
try
{
this.sentry1 = new DirectIO(string.Concat("GPIB",
this.busNumber_Slot1, "::22::INSTR"));
this.terminal1 = new DirectIO(string.Concat("GPIB0::14::INSTR"));
num = 1;
}
catch (Exception exception)
{
num = 0;
}
if (num != 1)
{
MessageBox.Show("SLOT 1: DUT Not Present !!", "3458A Heat Rack",
MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
this.txtSerial1.Clear();
this.txtSerial1.Select();
this.txtSerial1.ReadOnly = true;
}
else
{
this.button1.Select();
this.button1.Text = "RUNNING";
this.button1.BackColor = this.runningColour;
this.txtSerial1.Text = this.txtSerial1.Text.ToUpper();
this.txtStart1.Text = DateTime.Now.ToString();
this.txtSerial1.ReadOnly = true;
string txtBox1_serial = this.txtSerial1.Text;
this.call_main1 = new Thread(() => this.main_Program_slot1(sentry1,
terminal1, txtBox1_serial, 1, test_num,
resultrelay));
this.call_main1.Start();
}
}
else
{
MessageBox.Show("SLOT 1: Unit Serial Number Is Incorrect!!", "3458A
Heat Rack", MessageBoxButtons.OK,
MessageBoxIcon.Asterisk);
this.txtSerial1.Clear();
this.txtSerial1.Select();
}
}
}
public void slot1(string test) //function to update GUI
{
if (!base.InvokeRequired)
{
this.info1.Text = test;
}
else
{
Form1.test1 updateTestType = new Form1.test1(this.slot1);
object[] objArray = new object[] { test };
base.Invoke(updateTestType, objArray);
}
}
private void write_TestResultDetails1(string serialnumber, double resultLatest1)
{
//update results into textfile
}
private void main_Program_slot1(DirectIO sentry1, DirectIO terminal1, string sernum, int slot_Number, int test_num, double resultrelay)
{
for (int i = 1; i <= loop_Count; i++)
{
slot1("TEST A");
//performs testA
//calls write_TestResultDetails1
slot1("TEST B");
//performs testB
//calls write_TestResultDetails1
}
}
Hope this coding can help you guys to understand my problem better..
PS: seems like changing to using BackGroundWorker instead of making my own threads will be a better choice for this kind of program.
Windows forms programming has a few gotchas and keeping a UI responsive is tough. To help you debug this issue, I recommend you name your thread in form load so you can easily find it in the debugger (double click your form in the designer to get form load then call System.Threading.Thread.CurrentThread.Name = "My UI Thread". Launch your application and then when the UI hangs, break in the debugger. You can then observe the stacktrace of the UI Thread to find out where it is working (and where you need to use a thread to keep the UI responsive.
My hunch is that you have either used a synchronisation primitive incorrectly waiting for an answer on a thread, or you have accidentally launched some work without a thread which is hanging the UI.
There is a control called a BackgroundWorker which can be used to do work on a thread easily and then report the progress back with an event safely. This cuts down on the synchronisation work you need to do which might be helpful.
Totally agree with the previous comments btw, please post your actual code, just redact the method names etc. as the most likely issue is that your psuedo code and your actual code don't match.
I/O intensive tasks are perfect for asynchronous actions, i.e. writeToTextFile1(), writeToTextFile2(), and writeToTextFile3() can all be executed on different threads.
in your solution, the error might be caused from the fact that you wrapped two/three I/O method calls inside one thread.
I suggest you adopt the following pattern.
take writeToTextFile1() for example, I would use async/await pattern to define this method:
private async Task writeToTextFile1Async(string resultValue)
{
await Task.Run(() => {
//create and saves results into textFile1
});
}
rewrite mainFunction1() as follows:
private async Task mainFunction1Async()
{
string resultA = "***";
await writeToTextFile1Async(resultA);
string resultB = "***";
await writeToTextFile1Async(resultB);
//perform other things
}
Call this function inside Button1 click event handler:
private async void button1_Click(object sender, EventArgs e)
{
await mainFunction1Async();
}

What's the easiest way to play audio in background in UWP?

I am working on my Project (a soundcloud client) and the app can play tracks just fine, but not when the app is minimized. I use the MediaElement-Object for playing the mp3 from the url. How can i force the music to continue playing the music, when the app is in the background. Or whats the easiest way/best explained tutorial to implement this. I searched alot for a good answer, but the ones, i found, was too good for me :D What means, that i didn't understand it.
To play audio in the background you will have to do a Declaration in Package.appxmanifest for a Background Tasks, enable audio and add an entry point like TestUWP.MainPage page.
Also for the user to easily be able to manage the audio you can use SystemMediaTransportControls
Here is a basic setup with Play and Pause.
xaml
<MediaElement x:Name="mediaElement" Height="100" Width="100" AreTransportControlsEnabled="True"/>
C#
public MainPage()
{
this.InitializeComponent();
systemControls = SystemMediaTransportControls.GetForCurrentView();
// Register to handle the following system transpot control buttons.
systemControls.ButtonPressed += SystemControls_ButtonPressed;
mediaElement.CurrentStateChanged += MediaElement_CurrentStateChanged;
systemControls.IsPlayEnabled = true;
systemControls.IsPauseEnabled = true;
}
private void MediaElement_CurrentStateChanged(object sender, RoutedEventArgs e)
{
switch (mediaElement.CurrentState)
{
case MediaElementState.Playing:
systemControls.PlaybackStatus = MediaPlaybackStatus.Playing;
break;
case MediaElementState.Paused:
systemControls.PlaybackStatus = MediaPlaybackStatus.Paused;
break;
case MediaElementState.Stopped:
systemControls.PlaybackStatus = MediaPlaybackStatus.Stopped;
break;
case MediaElementState.Closed:
systemControls.PlaybackStatus = MediaPlaybackStatus.Closed;
break;
default:
break;
}
}
void SystemControls_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
{
switch (args.Button)
{
case SystemMediaTransportControlsButton.Play:
PlayMedia();
break;
case SystemMediaTransportControlsButton.Pause:
PauseMedia();
break;
case SystemMediaTransportControlsButton.Stop:
StopMedia();
break;
default:
break;
}
}
private async void StopMedia()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement.Stop();
});
}
async void PlayMedia()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
if (mediaElement.CurrentState == MediaElementState.Playing)
mediaElement.Pause();
else
mediaElement.Play();
});
}
async void PauseMedia()
{
await Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal, () =>
{
mediaElement.Pause();
});
}
Output
If you like to enable more controls you can do using the available properties for ex.
systemControls.IsNextEnabled = true;
and you have to add the case in the button switch.
case SystemMediaTransportControlsButton.Next:
//handle next song
break;

Not to execute any further code until the async method is completed its execution

Not to execute any further code until the async method is completed its execution. Please let me know how to achieve it.
Following is sample code :
// Parent Form code
private void btnOpenForm1_Click(object sender, EventArgs e)
{
Form1 form1 = new Form1();
var result = form1.ShowDialog();
if (result == System.Windows.Forms.DialogResult.OK)
{
// do something
}
}
// Child form code
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
DialogResult result = MessageBox.Show(string.Format("Do you want to save changes?", "Confirmation", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question);
if (result == System.Windows.Forms.DialogResult.Cancel)
{
e.Cancel = true;
}
else
{
if (result == DialogResult.Yes)
{
e.Cancel = true;
this.DialogResult = System.Windows.Forms.DialogResult.OK;
// HERE I NEED TO WAIT COMPULSARILY TILL THE OPERATION IS COMPLETED, NO NEXT STATEMENT SHOULD BE EXECUTED (NEITHER IN PARENT FORM)
var isSaveCompleted = await HandleSave().ConfigureAwait(false);
if(isSaveCompleted == true)
{
// dispose some objects
}
}
else // if No is clicked
{
this.DialogResult = System.Windows.Forms.DialogResult.Cancel;
// dispose some objects
}
}
}
public async Task<bool> HandleSave()
{
await doWork();
//
// some code here
//
}
public doWork()
{
//
// some code for I/O operation
//
}
In the above code, I don't want to execute the any of the next statements (not even in the Parent Form) until the HandleSave() method is completed its execution.
There's an XY problem here. Trying to force an asynchronous method to run synchronously in order to block closing the form is the wrong solution.
Instead of fighting window lifetimes, you need to figure out how to work with them. One solution for this scenario is to hide the form rather than closing it; and then actually close it when the asynchronous save completes. You can enable your parent form to detect when the child form is actually closed by using a TaskCompletionSource<DialogResult>.

MessageBox.Show called from backgound worker with the handle of the main UI thread

I have this code:
public void Blah(IWin32Window _this)
{
for (int i = 0; i < item_quantity; i++)
{
try { File.Delete(item[0, i]); }
catch (Exception ex)
{
if (MessageBox.Show(_this, String.Format("Error while accessing {0}\n{1}"
, item[0, i], ex.Message), "Error", MessageBoxButtons.RetryCancel
, MessageBoxIcon.Error) == DialogResult.Retry)
{ i--; }
}
}
}
...and this code in the main UI thread:
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
AnotherClass.Blah(this);
}
When I execute this code, I get the unsafe cross-thread exception. What's the safe way to do this operation?
What's the safe way to do this operation?
There is no real safe way to do this. The message box pops out of nowhere, without any direct connection to a command that the user gave. One failure mode is that the user continues working with your UI, clicking the mouse or pressing the space bar. And your message box pops up a millisecond before he clicked the mouse or pressed a key. He'll never see the message.
So something was supposed to be done, it didn't get done and the user is completely unaware of it. Not a good thing. You'll need to doctor your UI so this condition can never occur. Clearly that will require that you do error reporting a different way than by using a temporary message box. Many possible alternatives of course, could be as simple as a Label that reports state. StatusStrip is good for this.
The actual exception is a bogus one. It is triggered by the built-in diagnostics that checks that code uses UI in a thread-safe way. The underlying winapi call is GetParent(), one of the very few user32 Windows functions that can safely be called, and used, from a worker thread. The only legitimate reason I know where using Control.CheckForIllegalCrossThreadCalls to work around the problem is okay. But fix the real problem instead.
I'm not condoning the design, but you can pass in the Form to Blah() and then Invoke() against the referenced form:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!backgroundWorker.IsBusy)
{
button1.Enabled = false;
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
SomeClass AnotherClass = new SomeClass();
AnotherClass.Blah(this);
}
private void backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
button1.Enabled = true;
MessageBox.Show("Done!");
}
}
public class SomeClass
{
public void Blah(Form frm)
{
int item_quantity = 5;
for (int i = 0; i < item_quantity; i++)
{
try
{
//File.Delete(item[0, i]);
Console.WriteLine("i = " + i.ToString());
throw new Exception("duh");
}
catch (Exception ex)
{
frm.Invoke(new Action(() =>
{
DialogResult result = MessageBox.Show(frm, String.Format("Error while accessing {0}\n{1}", "something", ex.Message), "Error", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error);
if (result == DialogResult.Retry)
{
i--;
}
}));
}
}
}
}
You are trying to do UI work on a background thread, hence the cross-thread exception. RunWorkerCompletedEventArgs has a property called Error that will hold any exception that gets thrown by the RunWorkerAsync delegate. Set up a handler for RunWorkerCompleted on your BackgroundWorker and check if the Error property has a value. If it does, prompt the MessageBox in the handler because you will be on the UI thread at that point. Call the BackgroundWorker's RunWorkerAsync method again on the DialogResult.Retry scenario.
(You will probably have to tweak your BackgroundWorker and AnotherClass.Blah to take in the value of i to prime your loop condition for that second call to your BackgroundWorker. The DoWorkEventArgs has a property called Argument that you can use to pass in that value.)
You need to execute UI code like this when calling it from another thread:
// must use invoke because the timer event is running on a separate thread
this.Invoke(new Action(() =>
{
MessageBox.Show("Message");
}));

Using FileSavePicker with MessageDialog's IUICommand event

Individually, all code works perfectly. The snippet for saving the file, the snippet for picking a directory to save it to and also the message dialog works great.
But when I tie it all together, I get an access denied. I am not using the DocumentsLibrary capability since it is not required of me to do so in this case, however, enabling this capability after running into issues confirmed that it is not the issue.
Scenario:
User wants to create a new document after entering text in the text box. A MessageDialog appears, asking them if they want to save changes to the existing file first - the user clicks Yes (save file).
Now, here is where you handle the event that was raised by the MessageDialog.
Inside the IUICommand command event handler, you test for which button was clicked, and act accordingly.
I did this with a switch statement:
switch(command.Label) {
case "Yes":
SaveFile(); // extension method containing save file code that works on its own
break;
case "No":
ClearDocument();
break;
default:
break;
}
Now, each case works great except for the Yes button. When you click yes, an e tension method is called which has code that saves to a file
It is when you click yes button that you get the ACCESS DENIED exception. Details of the exception didn't reveal anything.
I think that it has something to do with how I am using the MesaageDialog. But after searching for hours I have yet to find a sample on how to save a file with the FileSavePicker when a MesaageDialog button is pressed.
Any ideas in how this should be done?
Update w/ Code
When the user clicks the New document button on the AppBar, this method fires:
async private void New_Click(object sender, RoutedEventArgs e)
{
if (NoteHasChanged)
{
// Prompt to save changed before closing the file and creating a new one.
if (!HasEverBeenSaved)
{
MessageDialog dialog = new MessageDialog("Do you want to save this file before creating a new one?",
"Confirmation");
dialog.Commands.Add(new UICommand("Yes", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("No", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.Commands.Add(new UICommand("Cancel", new UICommandInvokedHandler(this.CommandInvokedHandler)));
dialog.DefaultCommandIndex = 0;
dialog.CancelCommandIndex = 2;
// Show it.
await dialog.ShowAsync();
}
else { }
}
else
{
// Discard changes and create a new file.
RESET();
}
}
And the FileSavePicker stuff:
private void CommandInvokedHandler(IUICommand command)
{
// Display message showing the label of the command that was invoked
switch (command.Label)
{
case "Yes":
MainPage rootPage = this;
if (rootPage.EnsureUnsnapped())
{
// Yes was chosen. Save the file.
SaveNewFileAs();
}
break;
case "No":
RESET(); // Done.
break;
default:
// Not sure what to do, here.
break;
}
}
async public void SaveNewFileAs()
{
try
{
FileSavePicker saver = new FileSavePicker();
saver.SuggestedStartLocation = PickerLocationId.Desktop;
saver.CommitButtonText = "Save";
saver.DefaultFileExtension = ".txt";
saver.FileTypeChoices.Add("Plain Text", new List<String>() { ".txt" });
saver.SuggestedFileName = noteTitle.Text;
StorageFile file = await saver.PickSaveFileAsync();
thisFile = file;
if (file != null)
{
CachedFileManager.DeferUpdates(thisFile);
await FileIO.WriteTextAsync(thisFile, theNote.Text);
FileUpdateStatus fus = await CachedFileManager.CompleteUpdatesAsync(thisFile);
//if (fus == FileUpdateStatus.Complete)
// value = true;
//else
// value = false;
}
else
{
// Operation cancelled.
}
}
catch (Exception exception)
{
Debug.WriteLine(exception.InnerException);
}
}
Any progress on this issue? I currently have the same problem. I have also found that the same problem occurs if a second MessageDialog is shown in the IUICommand event.
My solution is to cancel the first operation (that shows the first message dialog). Here some code I’m using (it’s accessible in a global object):
private IAsyncInfo mActiveDialogOperation = null;
private object mOperationMutex = new object();
private void ClearActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation == operation)
mActiveDialogOperation = null;
}
}
private void SetActiveOperation(IAsyncInfo operation)
{
lock (mOperationMutex)
{
if (mActiveDialogOperation != null)
{
mActiveDialogOperation.Cancel();
}
mActiveDialogOperation = operation;
}
}
public void StopActiveOperations()
{
SetActiveOperation(null);
}
public async void ShowDialog(MessageDialog dialog)
{
StopActiveOperations();
try
{
IAsyncOperation<IUICommand> newOperation = dialog.ShowAsync();
SetActiveOperation(newOperation);
await newOperation;
ClearActiveOperation(newOperation);
}
catch (System.Threading.Tasks.TaskCanceledException e)
{
System.Diagnostics.Debug.WriteLine(e.Message);
}
}
So every time I want to show a MessageDialog I call ShowDialog. This will cancel the current dialog if any (then a TaskCanceledException occurs).
In the case when I will use a FileSavePicker, I call StopActiveOperations before PickSaveFileAsync is called.
This works but I can’t say I like it. It feels like I’m doing something wrong.
OK, now I have figured it out :-). The documentation says explicit that you shouldn’t show new popups/file pickers in the UICommand:
http://msdn.microsoft.com/en-US/library/windows/apps/windows.ui.popups.messagedialog.showasync
This is an example of a bad way to do it:
private async void Button_Click(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog (the application will crash).");
dialog.Commands.Add(new UICommand("OK", new UICommandInvokedHandler(OnDialogOkTest1)));
dialog.Commands.Add(new UICommand("Cancel"));
await dialog.ShowAsync();
}
private async void OnDialogOkTest1(IUICommand command)
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
This is the correct way to do it:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
MessageDialog dialog = new MessageDialog("Press ok to show new dialog");
UICommand okCommand = new UICommand("OK");
UICommand cancelCommand = new UICommand("Cancel");
dialog.Commands.Add(okCommand);
dialog.Commands.Add(cancelCommand);
IUICommand response = await dialog.ShowAsync();
if( response == okCommand )
{
MessageDialog secondDialog = new MessageDialog("This is the second dialog");
secondDialog.Commands.Add(new UICommand("OK"));
await secondDialog.ShowAsync();
}
}
Quite simple actually, I should have get this earlier...

Categories