我目前正在研究所有可能的解决方案,以便在需要用户做出决定时通知用户(例如弹出对话框)。这是MVVM模式中的常见问题,我正在为MvvmCross框架寻找解决方案。
可能的解决方案包括:
- 自定义MvxPresenter以显示对话框,但这看起来有点丑陋
- 在Core项目中放置一个对话框接口,并使用控制反转将实现从UI项目注入到Core项目中
- 使用MvxMessenger插件,在Core和UI项目之间共享消息。听起来是个好主意,但可能更加复杂...
你有什么建议?
我目前正在研究所有可能的解决方案,以便在需要用户做出决定时通知用户(例如弹出对话框)。这是MVVM模式中的常见问题,我正在为MvvmCross框架寻找解决方案。
可能的解决方案包括:
你有什么建议?
Dialog输入是一个有趣的话题,它并不总是与Mvvm数据绑定的流程很好地契合。
通常,对话框的一些用例包括:
对于其中的一些项目,我建议主要将其建模为纯视图问题。例如,请求单个项选择通常是通过复合控件标签完成的,当点击时显示“选择器”,例如像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)));
}
}
}
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);
DialogFragment
并可能也可以在XML中使用View
进行绑定。IInteractionRequest<T>
和InteractionRequestedEventArgs<T>
定义,则基本交互可以得到改进(在我看来) - 但是,针对这个答案的范围,我保持了“基本”实现,尽可能接近http://msdn.microsoft.com/en-us/library/gg405494(v=pandp.40).aspx中介绍的实现。
- 一些额外的帮助类也可以显著简化视图订阅代码。正如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();
}
}
Mvx.RegisterSingleton<IUserInteraction>(new WindowsPhoneUserInteraction());