Every day, one of our end-users opens a screen and prints out all open support tickets for that day. When the tickets are printed, we want to make sure those tickets are marked as ‘printed’ on the server, so we have a button on the screen that lets her do just that:
//Screen has one 'state' boolean private bool isMarkedAsPrinted = false; //Normal code behind a button to mark tickets as printed partial void MarkAsPrinted_Execute() { if (!isMarkedAsPrinted) { foreach (var ticket in this.Tickets) ticket.IsPrinted = true; this.Save(); this.isMarkedAsPrinted = true; //Don't forget to change the screen's state } else this.ShowMessageBox("The tickets are already marked as printed."); }
Sometimes, she’ll forget to press that button, so when the screen closes we want to check if the tickets are printed. The screen has a ‘Write Code’ button for the _Closing event, and it is passed a bool called ‘cancel’. If you set this bool to ‘true’, the screen closing event will be cancelled.
//Excecuted when the screen is closed partial void PrintTicketScreen_Closing(ref bool cancel) { //Cancel all closing events unless we set the 'reallyClose' flag cancel = !(isMarkedAsPrinted); }
That works like a charm. Once she prints, the isMarkedAsPrinted boolean is set to true, and the screen can close. When she forgets to press the button, the screen will not close no matter how many times the ‘x’ is clicked.
From a UX perspective though, this is rather weird. The application will just feel like it doesn’t respond to her trying to close the screen. We could have the screen show a message box saying she needs to print first, or better yet: asking her if she wants to print:
//Screen has one 'state' boolean private bool isMarkedAsPrinted = false; //Normal code behind a button to mark tickets as printed partial void MarkAsPrinted_Execute() { if (!isMarkedAsPrinted) { foreach (var ticket in this.Tickets) ticket.IsPrinted = true; this.Save(); this.isMarkedAsPrinted = true; //Don't forget to change the screen's state } else this.ShowMessageBox("The tickets are already marked as printed."); } //Excecuted when the screen is closed partial void PrintTicketScreen_Closing(ref bool cancel) { //Cancel all closing events unless we set the 'isMarkedAsPrinted' flag cancel = !(isMarkedAsPrinted); if (cancel) { var answer = this.ShowMessageBox("Would you like to mark these tickets as printed before closing the screen?", "Mark tickets as printed?", MessageBoxOption.YesNoCancel); switch (answer) { case MessageBoxResult.Cancel: //Simply let the screen stay open in current state break; case MessageBoxResult.Yes: //Execute mark as printed, then close screen automatically this.MarkAsPrinted_Execute(); cancel = false; break; case MessageBoxResult.No: //User doesn't want to mark as printed, close screen cancel = false; break; default: throw new Exception("Unexpected result."); } } }
Something weird will happen when you run through the code though… When closing the screen, if the tickets haven’t been marked as printed, the dialog will be displayed to remind the user to save. However, half a second later, the screen will close anyways.
Here’s the rub: your screen’s _Closing event is given about one or two seconds to finish executing. When it does not finish executing within that timeframe, the LightSwitch framework will close every open dialog on the screen (including your ‘would you like to mark the tickets as printed’ one), and close the screen anyway.
The workaround is to make sure the _Closing event code returns immediately, but cancels the closing, on first run. Before we return from that method though, we’ll kick off a background worker. This background worker will ask the screen’s logical dispatcher to show the ‘would you like to mark the tickets as printed’ dialog. This request will be queued by the screen’s logical dispatcher and executed whenever it has time (read: whenever it is done not closing your screen).
Once the tickets are marked as printed, or the user refuses to mark the tickets as printed (perhaps the screen was opened by accident in the first place, or the printer ran out of paper), we’ll flip a boolean and close the screen programmatically. The second time the screen’s _Closing event code is executed, it’ll pick up this boolean (or notice the tickets have been marked as paid) and let the screen close this time:
//Screen has two 'state' booleans private bool isMarkedAsPrinted = false; private bool userDoesNotWantToMarkAsPrinted = false; //Normal code behind a button to mark tickets as printed partial void MarkAsPrinted_Execute() { if (!isMarkedAsPrinted) { foreach (var ticket in this.Tickets) ticket.IsPrinted = true; this.Save(); this.isMarkedAsPrinted = true; //Don't forget to change the screen's state } else this.ShowMessageBox("The tickets are already marked as printed."); } //Excecuted when the screen is closed partial void PrintTicketsScreen_Closing(ref bool cancel) { //Cancel all closing events unless we set the 'isMarkedAsPrinted' flag OR 'userDoesNotWantToMarkAsPrinted' cancel = !(isMarkedAsPrinted || userDoesNotWantToMarkAsPrinted); if (cancel) { //If this method takes longer than a second, the LS framework will close all dialogs as 'cancelled' //Hence, we start a backgroundWorker so that we can return from this method straight away var sleepy = new BackgroundWorker(); sleepy.DoWork += (s, e) => { //This code will execute on the thread of the background worker, so //we must ask the screen's dispatcher to invoke the 'askPrintBeforeClosing' //instead of asking this on the background worker's thread this.Details.Dispatcher.BeginInvoke(() => { askPrintBeforeClosing(); }); }; sleepy.RunWorkerAsync(); } } //Helper method void askPrintBeforeClosing() { var answer = this.ShowMessageBox("Would you like to mark these tickets as printed before closing the screen?", "Mark tickets as printed?", MessageBoxOption.YesNoCancel); switch (answer) { case MessageBoxResult.Cancel: //Simply let the screen stay open in current state break; case MessageBoxResult.Yes: //Execute mark as printed, then close screen automatically this.MarkAsPrinted_Execute(); this.Close(false); break; case MessageBoxResult.No: //User doesn't want to mark as printed, close screen automatically userDoesNotWantToMarkAsPrinted = true; this.Close(false); break; default: throw new Exception("Unexpected result."); } }
Keep rocking LS!
Jan
Pingback: Einstein Regarding Haters - The Daily Six Pack; Dec 9, 2014