L'entrée de dialogue est un sujet intéressant qui ne cadre pas toujours bien avec le flux de liaison de données Mvvm.
En général, certains cas d'utilisation de Dialogs sont pour des choses comme:
- ajoutant un oui/non l'option de confirmation à un bouton soumettre
- demandant unique entrée supplémentaire - par exemple une sélection d'une liste
- offrant un choix d'actions (par exemple supprimer, modifier ou dupliquer?)
- offrant un message de confirmation
- demandant entrée complexe supplémentaire - par exemple collecter un ensemble de champ prénom/nom/age/accept_terms
Pour certains de ces éléments, je suggèrerais que ceux-ci pourraient être principalement modélisés comme des préoccupations purement View. Par exemple, la sélection d'une sélection d'un seul élément est couramment effectuée à partir d'étiquettes de contrôles composites qui affichent des «sélecteurs» lorsqu'ils sont tapés - par ex. Comme un MvxSpinner dans https://github.com/slodge/MvvmCross-Tutorials/blob/master/ApiExamples/ApiExamples.Droid/Resources/Layout/Test_Spinner.axml#L16
Pour les cas généraux où vous voulez que les ViewModels partagés pilotent le flux utilisateur, les options disponibles dans MvvmCross incluent la liste 3 vous, qui me semble tout à fait viable, mais je suis d'accord que aucun d'eux n'est parfait.
À titre de suggestion supplémentaire, une suggestion architecturale intéressante provient de l'équipe Pattern and Practices de Microsoft. Dans http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx, ils suggèrent une interface IInteractionRequest
qui peut être utilisée dans la liaison de données, en particulier pour ce type de situation.
Leur mise en œuvre de référence de c'est:
public interface IInteractionRequest
{
event EventHandler<InteractionRequestedEventArgs> Raised;
}
public class InteractionRequestedEventArgs : EventArgs
{
public Action Callback { get; private set; }
public object Context { get; private set; }
public InteractionRequestedEventArgs(object context, Action callback)
{
Context = context;
Callback = callback;
}
}
public class InteractionRequest<T> : IInteractionRequest
{
public event EventHandler<InteractionRequestedEventArgs> Raised;
public void Raise(T context, Action<T> callback)
{
var handler = this.Raised;
if (handler != null)
{
handler(
this,
new InteractionRequestedEventArgs(
context,
() => callback(context)));
}
}
}
Un exemple ViewModel utilisation de c'est:
private InteractionRequest<Confirmation> _confirmCancelInteractionRequest = new InteractionRequest<Confirmation>();
public IInteractionRequest ConfirmCancelInteractionRequest
{
get
{
return _confirmCancelInteractionRequest;
}
}
et le ViewModel peut soulever cette utilisant:
_confirmCancelInteractionRequest.Raise(
new Confirmation("Are you sure you wish to cancel?"),
confirmation =>
{
if (confirmation.Confirmed)
{
this.NavigateToQuestionnaireList();
}
});
}
où Confirmation
est une classe simple comme:
public class Confirmation
{
public string Message { get; private set; }
public bool Confirmed { get; set; }
public Confirmation(string message)
{
Message = message;
}
}
Pour utiliser ce dans les Vues:
Le lien MSDN montre comment un client peut Xaml lier à ce comportement en utilisant - donc je ne vais pas couvrir cet autre ici.
Dans iOS pour MvvmCross, un objet View pourrait mettre en œuvre une propriété comme:
private MvxGeneralEventSubscription _confirmationSubscription;
private IInteractionRequest _confirmationInteraction;
public IInteractionRequest ConfirmationInteraction
{
get { return _confirmationInteraction; }
set
{
if (_confirmationInteraction == value)
return;
if (_confirmationSubscription != null)
_confirmationSubscription.Dispose();
_confirmationInteraction = value;
if (_confirmationInteraction != null)
_confirmationSubscription = _confirmationInteraction
.GetType()
.GetEvent("Raised")
.WeakSubscribe(_confirmationInteraction,
DoConfirmation);
}
}
Cette propriété Voir utilise un WeakReference
abonnement événement basé pour canaliser ViewModel Raise
événements par à une vue MessageBox
-type méthode . Il est important d'utiliser un WeakReference
pour que le ViewModel n'ait jamais de référence au View
- ils peuvent provoquer des problèmes de fuite de mémoire dans Xamarin.iOS. La méthode actuelle MessageBox
-type elle-même serait assez simple - quelque chose comme:
private void DoConfirmation(InteractionRequestedEventArgs args)
{
var confirmation = (Confirmation)args.Context;
var alert = new UIAlertView();
alert.Title = "Bazinga";
alert.Message = confirmation.Message;
alert.AddButton("Yes");
alert.AddButton("No");
alert.Clicked += (sender, e) => {
var alertView = sender as UIAlertView;
if (e.ButtonIndex == 0)
{
// YES button
confirmation.Confirmed = true;
}
else if (e.ButtonIndex == 1)
{
// NO button
confirmation.Confirmed = false;
}
args.Callback();
};
}
Et la propriété pourrait être lié à un ensemble de reliure Courant comme:
set.Bind(this)
.For(v => v.ConfirmationInteraction)
.To(vm => vm.ConfirmCancelInteractionRequest);
Pour Android, une application similaire pourrait être utilisé - cela pourrait peut-être utiliser un DialogFragment
et pourrait peut-être aussi être lié en utilisant un View
au sein de XML.
Note:
- Je crois que l'interaction de base pourrait être améliorée (à mon avis) si nous avons ajouté d'autres
IInteractionRequest<T>
et InteractionRequestedEventArgs<T>
définitions - mais, pour la portée de cette réponse, je continuais à la la tenue de la mise en œuvre « de base » aussi proche que possible de celui présenté dans http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx
- certaines classes d'aide supplémentaires pourraient également contribuer à simplifier considérablement le code d'abonnement vue trop
si votre deuxième solution est quelque chose comme ça http://stackoverflow.com/questions/3801681/good-or-bad-practice-for-dialogs-in-wpf-with-mvvm - J'irais avec toi :) – blindmeis