Silverlight View Model 风格弹出窗口






4.86/5 (10投票s)
一个易于使用的 View Modal 风格弹出窗口和 Silverlight Value Converter 的示例。
一个 Silverlight 视图模型风格的弹出窗口
实时示例: http://silverlight.adefwebserver.com/mvvmpopup/default.aspx
如何对弹出窗口进行单元测试: SMVVMUnitTest.aspx
注意:另请参见此新版本,它更易于使用: Silverlight:视图模型、MVVM 的简单弹出窗口行为
参见此完全 MVVM 的版本: HisowaModPopUpBehavior
视图模型模式允许程序员创建一个完全没有 UI(用户界面)的应用程序。程序员只需创建一个 视图模型 和一个 模型。没有编程能力的网页设计师可以从一张白纸开始,在 Microsoft Expression Blend 4(或更高版本)中完全创建 视图(UI)。如果您是视图模型的新手,建议您阅读 Silverlight 视图模型:一个(过于)简化的解释 以获得介绍。
本文演示了如何轻松实现模态弹出窗口。它还演示了 值转换器 的实现。我们将使用来自 Alan Beasley 的 用于 Expression Blend & Silverlight 的 10 个炫酷按钮下载 中的图形。
基本弹出窗口
您单击 显示窗口 按钮,模态弹出窗口就会出现。您选择是或否,然后单击确定或取消。
组合框选择的结果以及单击的按钮将显示在主页面上。
弹出窗口与网页设计师的邂逅
我们使用视图模型,是因为我们希望允许网页设计师轻松更改应用程序的设计,而无需进行任何代码更改。
当网页设计师在 Microsoft Expression Blend 中打开项目时,他们会看到 MainPage.xaml 文件和 PopUpWindow.xaml 文件。网页设计师知道他们可以修改任何扩展名为 .xaml 的文件来重新设计应用程序。
当网页设计师打开 PopUpViewModel.xaml 文件,并同时打开 数据 窗口时,他们会看到该控件关联了一个 数据上下文。上面图形中的箭头指示了当前绑定的内容。
重新设计
网页设计师删除现有项目,并用新设计替换它们(图形来自 Alan Beasley 的 用于 Expression Blend & Silverlight 的 10 个炫酷按钮下载。我还设法让 Alan Beasley 进行了弹出窗口的实际布局)。
连接新的确定和取消按钮很简单。网页设计师只需在每个按钮上放置一个 InvokeCommandAction 行为。
在每个按钮的属性中,将单击选为事件名称,并单击命令旁边的数据绑定按钮。
然后,该按钮将绑定到关联视图模型中的相应ICommand。
伙计,我们需要一个值转换器!
我们使用视图模型的原因之一是它允许“关注点分离”。网页设计师可以完全更改设计,而无需开发人员更改任何代码。
但是,在这种情况下,原始设计有一个绑定到视图模型中的字符串值(SelectedPopUpValueProperty)的组合框。在新设计中,网页设计师希望使用一个布尔值(IsChecked)的切换按钮控件。Expression Blend 不允许网页设计师将 IsChecked 绑定到 SelectedPopUpValueProperty。
于是网页设计师打电话给正在阿鲁巴度假的开发人员,开发人员刚提交了他的视图模型(并在单元测试中确保满足了要求),并解释了问题。开发人员回答道:
“伙计,你开玩笑吗?如果我知道会有切换按钮出现在设计中,我早就为你创建一个布尔属性供你在视图模型中绑定了,该属性会设置主页面正在使用的 SelectedPopUpValueProperty。”
“问题是,视图模型现在由纽约办公室负责开发,而你不想和那些家伙打交道。我们必须在不更改视图模型的情况下解决这个问题。我将编写一个值转换器,你直接使用它就行了。”
“当你将你的 .xaml 页面提交到源代码管理时,你也要提交我给你的文件。纽约的那些家伙不必改变他们正在做的事情,这就能奏效。”
值转换器
网页设计师将值转换器文件(BoolToStringConverter.cs)拖放到项目中,然后按 F5 键构建项目(以便文件被构建并可以使用)。然后,网页设计师使用以下步骤创建绑定:
- 单击使用自定义路径表达式框
- 在框中输入要绑定的属性名称(SelectedPopUpValueProperty)
- 在值转换器下拉列表中选择 BoolToStringConverter。
- 单击确定
应用程序完成!
代码 - 创建弹出窗口
当你创建一个新的 Silverlight Child Window 控件时…
它会创建一个带有代码隐藏文件以及确定和取消按钮(带事件处理程序)的控件。
从按钮中删除事件处理程序,并清空代码隐藏文件中的所有方法。
创建一个类文件(PopUpViewModel.cs),它将作为弹出窗口的视图模型
public class PopUpViewModel : INotifyPropertyChanged { private ChildWindow PopUP; public PopUpViewModel() { // Set the command property SetPopUpCommand = new DelegateCommand(SetPopUp, CanSetPopUp); OKButtonCommand = new DelegateCommand(OKButton, CanOKButton); CancelButtonCommand = new DelegateCommand(CancelButton, CanCancelButton); SelectedPopUpValueProperty = "Yes"; } // Commands #region SetPopUpCommand public ICommand SetPopUpCommand { get; set; } public void SetPopUp(object param) { PopUP = (ChildWindow)param; } private bool CanSetPopUp(object param) { return true; } #endregion #region OKButtonCommand public ICommand OKButtonCommand { get; set; } public void OKButton(object param) { PopUP.DialogResult = true; } private bool CanOKButton(object param) { return true; } #endregion #region CancelButtonCommand public ICommand CancelButtonCommand { get; set; } public void CancelButton(object param) { PopUP.DialogResult = false; } private bool CanCancelButton(object param) { return true; } #endregion // Properties #region SelectedPopUpValueProperty private string _SelectedPopUpValueProperty; public string SelectedPopUpValueProperty { get { return this._SelectedPopUpValueProperty; } set { this._SelectedPopUpValueProperty = value; this.NotifyPropertyChanged("SelectedPopUpValueProperty"); } } #endregion // Utility #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } #endregion }
此类执行以下操作:
- 实现 INotifyPropertyChanged,以便在视图模型中的值更新时自动更新 UI
- 为选定的弹出窗口值创建一个属性(SelectedPopUpValueProperty)
- 为确定和取消按钮创建 ICommand
- 创建一个 ICommand,它允许设置弹出窗口 UI 实例(SetPopUpCommand)
将弹出窗口 UI 绑定到视图模型
弹出窗口很简单,因为我们基本上是将整个弹出窗口 UI 的实例传递给视图模型。然后,视图模型就可以访问弹出窗口的所有功能。对弹出窗口进行编程就像使用普通代码隐藏一样简单,但是,它仍然是视图模型,因此网页设计师可以完全控制 UI。
在 Blend 的对象和时间线窗口中,将一个 InvokeCommandAction 行为放置在 childWindow 上。
在属性窗口中,将 InvokeCommandAction 行为的事件名称设置为 Loaded,并将命令数据绑定到 SetPopUpCommand。
然后,将命令参数绑定到 PopUpWindow。
从主视图调用弹出窗口
从主视图的视图模型(MainViewModel.xaml)调用弹出窗口非常简单。
将一个 InvokeCommandAction 行为放置在按钮上,并将其配置为调用视图模型中的 ShowPopUPCommand 方法。
以下是视图模型的完整代码。
public class MainViewModel : INotifyPropertyChanged { private ChildWindow PopUP; public MainViewModel() { // Create popup(s) PopUP = new PopUpWindow(); // Set the command property PopUP.Closed += new EventHandler(PopUP_Closed); ShowPopUPCommand = new DelegateCommand(ShowPopUP, CanShowPopUP); } // Commands #region ShowPopUPCommand public ICommand ShowPopUPCommand { get; set; } public void ShowPopUP(object param) { // Show PopUP PopUP.Show(); } private bool CanShowPopUP(object param) { return true; } #endregion // Properties #region SelectedPopUpValueProperty private string _SelectedPopUpValueProperty; public string SelectedPopUpValueProperty { get { return this._SelectedPopUpValueProperty; } set { this._SelectedPopUpValueProperty = value; this.NotifyPropertyChanged("SelectedPopUpValueProperty"); } } #endregion // Events #region PopUP_Closed void PopUP_Closed(object sender, EventArgs e) { // Was there a response at all? if (PopUP.DialogResult != null) { // Set the selected value bool boolDialogResult = (PopUP.DialogResult != null) ? Convert.ToBoolean(PopUP.DialogResult) : false; PopUpViewModel objPopUpViewModel = (PopUpViewModel)PopUP.DataContext; SelectedPopUpValueProperty = String.Format("{0} - {1}", objPopUpViewModel.SelectedPopUpValueProperty, (boolDialogResult) ? "OK" : "Cancel"); } } #endregion // Utility #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void NotifyPropertyChanged(String info) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(info)); } } #endregion }
类型转换器
哦,是的,那个开发人员在阿鲁巴编写的文件,在这里
public class BoolToStringConverter : IValueConverter { #region IValueConverter Members public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if ((string)value == "No") { return true; } else { return false; } } public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if ((bool)value == true) { return "No"; } else { return "Yes"; } } #endregion }
这似乎太容易了……
有人会争辩说,以这种方式创建弹出窗口会将你的视图模型与弹出窗口绑定在一起,这是错误的,因为弹出窗口是一个 UI 元素。然而,弹出窗口是视图模型正在执行的一个函数,而不是视图(视图只是请求显示弹出窗口)。它显示在视图的前面,并通过绑定将值发送回视图,但它属于视图模型。因此,视图模型直接实例化一个弹出窗口类就像实例化另一个类(如 Web 服务)一样是可以的。
嘿,我们添加了代码!
我们关心视图模型,因为我们希望允许网页设计师自由地设计应用程序而不进行代码更改,而这个例子就实现了这一点。好吧,也许在某些情况下可能需要添加值转换器,但即使在这种情况下,视图模型也无需更改。
如果开发人员同时扮演着开发人员和网页设计师的角色,那么当需要添加或更改属性类型时,直接修改视图模型可能会更容易。但是,你有选择。值转换器允许你在视图模型面对意外更改时,将 UI 元素绑定到视图模型。
如何开始使用 Silverlight?
要学习如何使用 Expression Blend,你只需要去