9

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

可能的解决方案可能是:

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

你有什么建议?

4

3 回答 3

15

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

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

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

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

对于一般情况,您希望共享 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基于 - 的事件订阅,以便将 ViewModelRaise事件传递到 View MessageBox-type 方法。使用 a 非常重要,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,可以使用类似的实现——这可能使用 a DialogFragment,也可能View在 XML 中使用 a 进行绑定。

笔记:

于 2013-09-04T21:58:37.210 回答
7

你可以使用Brian Chance 的MvvmCross UserInteraction 插件

于 2013-10-26T06:04:11.503 回答
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());
于 2014-03-14T06:58:46.660 回答