I'm looking for a good way to implement a truly modal dialog in Silverlight 5. Every example that I find that claims to create a modal dialog really isn't modal in that the calling code waits until the dialog is closed.
I realize this is a challenge because we can't actually block the UI thread because it must be running in order for the dialog (ChildWindow) to function correctly. But, with the addition of the TPL in SL5 and the higher level of adoption Silverlight has seen over the past few years, I'm hoping someone has found a way around this.
A good representative scenario I am trying to solve is an action (say clicking a button or menu item) that displays a login dialog and must wait for the login to complete before proceeding.
Our specific business case (whether logical or not) is that the application does not require user authentication; however, certain functions require "Manager" access. When the function is accessed (via button click or menu item selected, etc), and the current user is not a manager, we display the login dialog. When the dialog closes, we check the user's authorization again. If they are not authorized, we display a nice message and reject the action. If they are authorized, we continue to perform the action which typically involves changing the current view to something new where the user can do whatever it is they requested.
For the sake of closure...
What I ended up with is a new TPL-enabled DialogService with methods like:
public Task<Boolean?> ShowDialog<T>()
where T : IModalWindow
The method creates an instance of the dialog (ChildWindow), attaches a handler to the Closed event and calls the Show method. Internally, it uses a TaskCompletionSource<Boolean?> to return a Task to the caller. In the Closed event handler, the DialogResult is passed to the TrySetResult() method of the TaskCompletionSource.
This lets me display the dialog in a typical async way:
DialogService.ShowDialog<LoginDialog>().ContinueWith(task =>
{
var result = task.Result;
if ((result == true) && UserHasPermission())
{
// Continue with operation
}
else
{
// Display unauthorized message
}
}, TaskScheduler.FromCurrentSynchronizationContext());
Or, I could block using the Task.Wait() method - although this is problematic because, as I mentioned in the original post, it blocks the UI thread, so even the dialog is frozen.
Still not a true modal dialog, but a little bit closer to the behavior I am looking for. Any improvements or suggestions are still appreciated.
Related
I am facing an issue, while trying to start a phone call from my iOS app using:
UIApplication open(_:options: completionHandler:)
iOS shows an confirmation popup/alert before starting the call with two button Call & Cancel and CompletionHandler called with a Bool parameter having true/false based on button action in iOS 10.
But now in iOS11 the problem is that the completionHandler is being called automatically before tapping on "Cancel" or "Call" button of confirmation popup, always having true value.
Is this a bug in iOS11 or is there something that I am doing wrong?
There has been a behavior change in when the closure is called in iOS 11. I cant say if this behavior will be reverted or if this is a bug.
But one of the ways you can identify when the user interacted with the popup is by adding a notification listener around UIApplicationDidBecomeActive in the completion closure of openURL(). To identify whether the call was clicked or not you will need to create another custom notification and listener on the CTCallCenter. That was the best way through which I was able to identify it, there might be better solutions though :D
completionHandler will get a call if your given URL gets open success or failure, this has nothing to do with Cancel & Call buttons on Alert
see what Apple docs has to say about it HERE
completionHandler
The block to execute with the results. Provide a
value for this parameter if you want to be informed of the success or
failure of opening the URL. This block is executed asynchronously on
your app's main thread. The block has no return value and takes the
following parameter:
success
A Boolean indicating whether the URL was
opened successfully.
Lets say, that I’ve got simple app, which is created with Backbone.Marionette.
For sake of simplicity, assume that I’ve got „Show” view for some model (it doesn’t matter here what it is), where I can click „Add new”, which shows me „new” view in „modal” window.
I’m displaying there simple form, which (after validation) is sent to my API. During that, I’m displaying other view (which displays sth. like „Saving, please wait…”).
In case of success, I’m closing this modal window, and everything is OK.
However (that is the tricky part): I would like to handle situations, when from some reason model was not saved (API temporary downtime, connection issue, race condition etc.), and display same view another time (I thought that it should be easy) – however, when I’m doing this, events aren’t handled anymore (ie: submitForm action is not executed second time :( )
Here is my my proof of concept for this:
class MyApp.SampleView extends Marionette.View
events:
"submit form": "submitForm"
submitForm: (event) ->
event.preventDefault()
data = $(event.currentTarget).serializeObject()
model = SampleModel.new(data)
if model.isValid()
MyApp.popupRegion.show(SavingPopup)
savePromise = model.save()
savePromise.success =>
#close()
#displayNotification("Model has been added")
savePromise.error =>
#displayNotification("Something went wrong, please try again")
MyApp.popupRegion.show(this) # displays correct view
# but does not handle events :(
TL;DR version:
How to re–append view to region in such way that my events still will be handled?
Before a region show()'s a view it calls close() on the currently displayed view. close() acts as the view's destructor, unbinding all events, rendering it useless and allowing the garbage collector to dispose of it.
When you execute MyApp.popupRegion.show(SavingPopup), this is close()'d. You will need to create a new instance of the view in savePromise.error, or find some other way to signal your application that the view needs to be recreated.
Something like this should work:
savePromise.error =>
#displayNotification("Something went wrong, please try again")
MyApp.popupRegion.show(new MyApp.SampleView)
Say,there is a requirement that a customer object is loaded from the db to a silverlight application, so that the customer details are shown in the UI. We need to detect if the user is changing any data in the UI.
We are listening to property changed notifications from the view model. However, when the notifications are result of property change as part of the loading process we have to discard it.
class CustomerLoader
{
Helper helerobj;
Address addressobj;
Profile profileobj;
void LoadFromDb()
{
helperobj.Load();
addressobj.Load();
profileobj.Load();
//start listening after the loading finished
this.propertychanged += new PropertyChangedEventHandler(handlepropertychanged);
}
The trouble with this is the inner objects might be calling asynchronous functions which might set properties. So by the time we started the property change listening, the loading might not have been finished.
We need to know when the loading is actually done. As of now we are asking the developers who are developing the inner object classes to accept a callback in the parameter which they should call when the function is finished.
Is there any other way to do it?
You want nothing but a really classic asynchronous objet loading.
So yes, the only solution is to ask developers working on the loading to propose an asynchronous function. Now you hav several solution to achieve asynchronicity in Silverlight.
You could either provide a callback as you do, or use async and await to manage your asynch task as explain here: http://10rem.net/blog/2012/05/22/using-async-and-await-in-silverlight-5-and-net-4-in-visual-studio-11-with-the-async-targeting-pack
I want to show my custom windows form on uninstall. I am going to use a C# custom action for this. How do I make the custom action wait till the user clicks OK or Cancel? I want my custom action to complete only when the form is closed. So do the following in my custom action:
CollectUninstallData f = new CollectUninstallData();
f.Show();
return f.FormResult;
But the form blinks for a moment and the uninstall continues without waiting for the form to close. And this is logical since the GUI is in another thread. But how do I make it wait for the form to close?
I am aware that showing custom windows forms in install packages isn't cool, so if anyone can offer a more elegant solution, then I will thankfully accept it.
You have to use ShowDialog() method instead of Show(). The latter makes the form visible and returns control that's why your Custom Action stops its execution. The former shows the form as a modal dialog box and does not return until the user closes the form in any way.
CollectUninstallData f = new CollectUninstallData();
DialogResult r = f.ShowDialog();
f.Dispose();
return r;
If you want to know whether user clicked OK or Cancel, use this statement return r == DialogResult.OK ? 0 : 1. Return code of zero usually indicates success and non-zero is for failure.
We have a button that fires a command which goes to the server to do some validation. This is done asynchronously and if the validation is okay (i.e. the user has the correct permission), I want to show the SaveFileDialog.
However, this is not a user initiated action which means calling the SaveFileDialog.ShowDialog() method raises a "Dialog must be user initiated" exception.
Is there any way to make this work the way I want?
To other option is to launch the SaveFileDialog and make the request after the file has been selected. Not ideal but it works.
JD.
There is no work around after all it would be a pointless restriction if there were a work around.
I think your alternative design choice make sense. You might consider using a busy indicator with the message "Validating..." or some such whilst the async validation occurs then do what ever it is you would have done once the asyc operation completes.