MvvmCross 对话框

9

我目前正在研究所有可能的解决方案,以便在需要用户做出决定时通知用户(例如弹出对话框)。这是MVVM模式中的常见问题,我正在为MvvmCross框架寻找解决方案。

可能的解决方案包括:

  • 自定义MvxPresenter以显示对话框,但这看起来有点丑陋
  • 在Core项目中放置一个对话框接口,并使用控制反转将实现从UI项目注入到Core项目中
  • 使用MvxMessenger插件,在Core和UI项目之间共享消息。听起来是个好主意,但可能更加复杂...

你有什么建议?


如果你的第二个解决方案类似于这个链接 https://dev59.com/Veo6XIcBkEYKwwoYTS5D - 我会支持你 :) - blindmeis
3个回答

15

Dialog输入是一个有趣的话题,它并不总是与Mvvm数据绑定的流程很好地契合。

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

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

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

对于一般情况,当您希望共享的ViewModel驱动用户流程时,MvvmCross中可用的选项包括您列出的3个选项,这些选项对我来说都是可行的,但我同意没有一个是完美的。

作为额外的建议,微软的模式和实践团队提供了一个不错的架构建议。在http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx中,他们建议使用接口,特别是在数据绑定中,尤其是针对这种情况。

他们的参考实现如下:

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

在 Views 中使用:

MSDN 链接展示了一个 Xaml 客户端如何使用行为绑定到这个属性 - 所以我在这里不再详细介绍。

在 MvvmCross 的 iOS 中,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);
    }
}

这个视图属性使用基于WeakReference的事件订阅,以便通过View MessageBox类型方法将ViewModel Raise事件传递。重要的是要使用WeakReference,这样ViewModel永远不会引用View,因为这可能会在Xamarin.iOS中导致内存泄漏问题。实际的MessageBox类型方法本身将非常简单,例如:
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();
    }; 
}

属性可以在流畅的绑定设置中绑定,例如:

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

对于Android,可以使用类似的实现方式 - 可能可以使用DialogFragment并可能也可以在XML中使用View进行绑定。
注意:
- 我认为,如果我们添加进一步的IInteractionRequest<T>InteractionRequestedEventArgs<T>定义,则基本交互可以得到改进(在我看来) - 但是,针对这个答案的范围,我保持了“基本”实现,尽可能接近http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx中介绍的实现。 - 一些额外的帮助类也可以显著简化视图订阅代码。

7

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

你会注意到并不是所有的功能都已实现,即使已经实现的部分也有限制(例如你无法设置“确定”和“取消”按钮的文本)。
当然,我还需要在 setup.cs 中注册这个功能。
Mvx.RegisterSingleton<IUserInteraction>(new WindowsPhoneUserInteraction());

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接