2013-09-04 5 views
7

J'étudie actuellement toutes les solutions possibles pour pouvoir informer l'utilisateur, c'est-à-dire ouvrir une boîte de dialogue, lorsqu'il y a une décision à prendre. C'est un problème courant avec le modèle MVVM et j'essaie de le résoudre pour le framework MvvmCross.MvvmCross Dialog

solutions possibles pourraient être:

  • Personnalisez le MvxPresenter pour être en mesure de montrer les boîtes de dialogue, mais qui ressemble un peu laid pour moi
  • Mettez une interface de dialogue dans le projet de base et utiliser l'inversion de contrôle pour injecter l'implémentation du projet d'interface utilisateur dans le projet Core
  • Utilisez le plug-in MvxMessenger et partagez les messages entre le projet Core et l'interface utilisateur. Ça sonne comme une bonne idée mais peut-être plus compliqué à développer ...

Que suggéreriez-vous?

+0

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

Répondre

13

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:

  1. ajoutant un oui/non l'option de confirmation à un bouton soumettre
  2. demandant unique entrée supplémentaire - par exemple une sélection d'une liste
  3. offrant un choix d'actions (par exemple supprimer, modifier ou dupliquer?)
  4. offrant un message de confirmation
  5. 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(); 
     } 
    }); 
} 

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
3

Comme le dit Eugene, utilisez le plugin UserInteraction. Malheureusement, il n'y a pas actuellement une implémentation de Windows Phone, donc voici le code que je l'ai utilisé dans l'intervalle:

public class WindowsPhoneUserInteraction : IUserInteraction 
{ 
    public void Confirm(string message, Action okClicked, string title = null, string okButton = "OK", string cancelButton = "Cancel") 
    { 
     Confirm(message, confirmed => 
     { 
      if (confirmed) 
       okClicked(); 
     }, 
     title, okButton, cancelButton); 
    } 

    public void Confirm(string message, Action<bool> answer, string title = null, string okButton = "OK", string cancelButton = "Cancel") 
    { 
     var mbResult = MessageBox.Show(message, title, MessageBoxButton.OKCancel); 
     if (answer != null) 
      answer(mbResult == MessageBoxResult.OK); 
    } 

    public Task<bool> ConfirmAsync(string message, string title = "", string okButton = "OK", string cancelButton = "Cancel") 
    { 
     var tcs = new TaskCompletionSource<bool>(); 
     Confirm(message, tcs.SetResult, title, okButton, cancelButton); 
     return tcs.Task; 
    } 

    public void Alert(string message, Action done = null, string title = "", string okButton = "OK") 
    { 
     MessageBox.Show(message, title, MessageBoxButton.OK); 
     if (done != null) 
      done(); 
    } 

    public Task AlertAsync(string message, string title = "", string okButton = "OK") 
    { 
     var tcs = new TaskCompletionSource<object>(); 
     Alert(message,() => tcs.SetResult(null), title, okButton); 
     return tcs.Task; 
    } 

    public void Input(string message, Action<string> okClicked, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null) 
    { 
     throw new NotImplementedException(); 
    } 

    public void Input(string message, Action<bool, string> answer, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null) 
    { 
     throw new NotImplementedException(); 
    } 

    public Task<InputResponse> InputAsync(string message, string placeholder = null, string title = null, string okButton = "OK", string cancelButton = "Cancel", string initialText = null) 
    { 
     throw new NotImplementedException(); 
    } 

    public void ConfirmThreeButtons(string message, Action<ConfirmThreeButtonsResponse> answer, string title = null, string positive = "Yes", string negative = "No", string neutral = "Maybe") 
    { 
     throw new NotImplementedException(); 
    } 

    public Task<ConfirmThreeButtonsResponse> ConfirmThreeButtonsAsync(string message, string title = null, string positive = "Yes", string negative = "No", string neutral = "Maybe") 
    { 
     throw new NotImplementedException(); 
    } 
} 

Vous remarquerez que ne est pas tout mis en œuvre, et même les bits qui sont limitées (vous pouvez » t définir le texte du bouton Annuler l'annonce OK, par exemple)

Bien sûr, j'avais besoin de l'enregistrer dans la configuration.cs ainsi:

Mvx.RegisterSingleton<IUserInteraction>(new WindowsPhoneUserInteraction());