我們都想追求完美
Every view in the app has an empty codebehind file, except for the standard boilerplate code that calls InitializeComponent in the class's constructor. In fact, you could remove the views' codebehind files from the project and the application would still compile and run correctly
Josh Smith說可以刪除views' codebehind文件,程序也能正常運行。
stylet框架作者是這么說的“Stylet lets you delete the codebehind entirely (it will call InitializeComponent
for you), and you are strongly encouraged to do so. Delete the codebehind!”
啊...還有這么完美的事情?那是不是views' codebehind文件里一行代碼都不寫,所有的代碼都可以寫在ViewModel里就可以了,那這樣顯示層和業務邏輯層就可以完美分離了?
理想是豐滿的,現實卻是骨干的啊!為了追求極致的MVVM,在實際項目開發中會把自己搞的非常糾結,不知從何下手...
比如使用的第三方控件,它不支持依賴項屬性,那我們只能把代碼寫在views' codebehind文件里。問題來了,這個控件要使用ViewModel里的邏輯,那View怎么訪問ViewModel的方法呢?ViewModel里要獲取這個控件的數據,那ViewModel如何調用View里的方法呢?搜了一通,發現這個問題不好搞...
這個“老鼠屎”控件破壞了我們實現完美MVVM的計划,哎呀,這時候開始后悔使用MVVM了,后悔使用WPF了,干脆使用WinForm方式開發算了,或者找個第三方MVVM框架,看能不能解決...我當初就有這種心理。
下面通過一個簡單的demo來演示,如何解決上面的兩個問題:
View中使用ViewModel
我們在程序啟動的時候已經使用了相應的ViewModel初始化了View的DataContext
/// <summary> /// App.xaml 的交互邏輯 /// </summary> public partial class App : Application { protected override void OnStartup(StartupEventArgs e) { base.OnStartup(e); var vw = new MainWindow(); var vm = new MainWindowViewModel(); vw.DataContext = vm; vw.Show(); } }
因此在View中,我們可以通過DataContext獲取ViewModel
private MainWindowViewModel _vm; private void OnLoaded(object sender, RoutedEventArgs e) { if (_vm == null) { _vm = this.DataContext as MainWindowViewModel; ... } }
在View中使用ViewModel中的屬性或方法
private void LvPersons_OnSelectionChanged(object sender, SelectionChangedEventArgs e) { var item = this.LvPersons.SelectedItem as PersonModel; if (item != null) { _vm.PersonProfile = string.Format("姓名:{0}, 性別:{1}, 年齡:{2};", item.Name, item.Sex, item.Age); } }
注:可以使用blend提供的System.Window.interactivity插件,將View中事件轉換為ViewModel中命令
ViewModel中使用View
先定義一個接口
namespace MVVMSample.Comm { public interface IView { void SetAddress(Uri uri); } }
View中初始化並實現這個接口
public partial class MainWindow : IView { private void OnLoaded(object sender, RoutedEventArgs routedEventArgs) { if (_vm == null) { _vm = this.DataContext as MainWindowViewModel; if (_vm != null) { _vm.View = this; } } } ... public void SetAddress(Uri uri) { this.Wb.Navigate(uri); } }
ViewModel中定義並使用View中的接口
public IView View { private get; set; } ... public ICommand NavigationCmd { get { if (_navigationCmd == null) { _navigationCmd = new DelegateCommand(() => { if (View != null) { //IView中接口方法 View.SetAddress(new Uri("http://www.bing.com")); } }); } return _navigationCmd; } }
問題總算解決了,關鍵是,使用上述方法還能夠進行單元測試。
總結
跟業務邏輯相關的操作一定要放到ViewModel中,跟業務邏輯無關的可以放到Views' Codebehind文件里。MVVM模式是開發中的指導原則,不能強套,需要靈活使用。