【问题标题】:MvvmCross DialogMvvmCross 对话框
【发布时间】:2013-09-07 16:13:20
【问题描述】:

我目前正在研究所有可能的解决方案,以便能够在用户需要做出决定时通知用户,即弹出一个对话框。这是 MVVM 模式的常见问题,我正在尝试为 MvvmCross 框架解决它。

可能的解决方案是:

  • 自定义 MvxPresenter 以显示对话框,但这对我来说有点难看
  • 在Core项目中放一个Dialog接口,使用Inversion of Control将UI项目的实现注入到Core项目中
  • 使用 MvxMessenger 插件并在 Core 和 UI 项目之间共享消息。听起来不错,但开发起来可能更复杂...

你有什么建议?

【问题讨论】:

标签: mvvm dialog modal-dialog mvvmcross


【解决方案1】:

对话框输入是一个有趣的话题,它并不总是很适合 Mvvm 数据绑定的流程。

通常,对话框的一些用例用于:

  1. 向提交按钮添加是/否确认选项
  2. 请求额外的单个输入 - 例如列表中的选择
  3. 提供操作选择(例如删除、编辑或复制?)
  4. 提供确认消息
  5. 请求额外的复杂输入 - 例如收集一组 firstname/lastname/age/accept_terms 字段

对于其中一些项目,我建议主要将这些项目建模为纯粹的视图关注点。例如,请求单个项目选择通常是从复合控件标签完成的,这些标签在点击时显示“选择器” - 例如就像 https://github.com/slodge/MvvmCross-Tutorials/blob/master/ApiExamples/ApiExamples.Droid/Resources/Layout/Test_Spinner.axml#L16 中的 MvxSpinner 一样

对于一般情况,如果您希望共享 ViewModel 驱动用户流,则 MvvmCross 中可用的选项包括您列出的 3 个选项,所有这些对我来说似乎都是可行的,但我同意它们都不是完美的。

作为附加建议,Microsoft 的模式和实践团队提供了一个不错的架构建议。在http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx 中,他们建议使用IInteractionRequest 接口,该接口可用于数据绑定,尤其适用于这种情况。

他们的参考实现是:

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)));
        }
    }
}

ViewModel 的一个示例用法是:

private InteractionRequest<Confirmation> _confirmCancelInteractionRequest = new InteractionRequest<Confirmation>();
public IInteractionRequest ConfirmCancelInteractionRequest
{
    get
    {
        return _confirmCancelInteractionRequest;
    }
}

ViewModel 可以使用:

_confirmCancelInteractionRequest.Raise(
    new Confirmation("Are you sure you wish to cancel?"),
    confirmation =>
    {
        if (confirmation.Confirmed)
        {
            this.NavigateToQuestionnaireList();
        }
    });
}

Confirmation 是一个简单的类,例如:

    public class Confirmation
    {
        public string Message { get; private set; }
        public bool Confirmed { get; set; }
        public Confirmation(string message)
        {
           Message = message;
        }
    }

在视图中使用它:

MSDN 链接显示了 Xaml 客户端如何使用行为绑定到此 - 所以我不会在这里进一步介绍。

在 iOS for MvvmCross 中,View 对象可能实现如下属性:

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);
    }
}

此 View 属性使用基于 WeakReference 的事件订阅,以便将 ViewModel Raise 事件传递给 View MessageBox 类型的方法。使用 WeakReference 非常重要,这样 ViewModel 永远不会引用 View - 这些可能会导致 Xamarin.iOS 中的内存泄漏问题。实际的 MessageBox-type 方法本身相当简单 - 类似于:

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();
    }; 
}

并且该属性可以绑定在 Fluent Binding 集中,例如:

set.Bind(this)
   .For(v => v.ConfirmationInteraction)
   .To(vm => vm.ConfirmCancelInteractionRequest);

对于 Android,可以使用类似的实现 - 这可能使用 DialogFragment,也可能在 XML 中使用 View 进行绑定。

注意:

  • 我相信,如果我们进一步添加IInteractionRequest&lt;T&gt;InteractionRequestedEventArgs&lt;T&gt; 定义,基本的交互可以得到改进(在我看来)——但是,对于这个答案的范围,我保持尽可能接近的“基本”实现我可以对http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx中提出的那个
  • 一些额外的帮助类也有助于显着简化视图订阅代码

【讨论】:

    【解决方案2】:

    您可以使用 Brian Chance 的 MvvmCross UserInteraction plugin

    【讨论】:

      【解决方案3】:

      正如 Eugene 所说,使用 UserInteraction 插件。不幸的是,目前还没有 Windows Phone 实现,所以这是我临时使用的代码:

      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();
          }
      }
      

      您会注意到并非所有内容都已实现,即使是那些有限的位(例如,您不能设置 OK 广告 Cancel 按钮文本)

      当然,我还需要在 setup.cs 中注册它:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2020-11-23
        • 2019-05-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多