MVVM 模式中程式碼隱藏的實用使用
我試圖在 WPF 應用程序中盡可能地遵循 MVVM 模式,主要是為了能夠為我的 ViewModel 邏輯創建單元測試。
在大多數情況下,ViewModel 屬性和可視元素屬性之間的數據綁定工作得很好而且很容易。但有時我遇到的情況是,我看不到明顯和直接的方法,而從程式碼隱藏訪問和操作控制項的解決方案非常容易。
這是我的意思的一個範例:將文本片段插入到
TextBox目前插入符號位置的 a由於
CaretIndex不是依賴屬性,它不能直接綁定到 ViewModel 的屬性。這是通過創建依賴屬性來解決此限制的解決方案。這是在程式碼隱藏中執行此操作的解決方案。在這種情況下,我更喜歡程式碼隱藏方式。我最近遇到的另一個問題是將動態列集合綁定到 WPF 數據網格。在程式碼隱藏中程式既清晰又簡單。但是對於 MVVM 友好的數據綁定方法,我只能在幾個部落格中找到解決方法,這些部落格對我來說都很複雜,並且在一個或另一個方面都有各種限制。我不想不惜一切代價保持 MVVM 架構中程式碼隱藏邏輯的清潔。如果工作量太大,對 MVVM 友好的解決方案需要大量我不完全理解的程式碼(我仍然是 WPF 初學者)並且太耗時我更喜歡程式碼隱藏解決方案並犧牲我的應用程序的幾個部分的自動可測試性。
出於上述務實的原因,我現在正在尋找“模式”,以在不破壞 MVVM 架構或不破壞太多的情況下在應用程序中控制使用程式碼隱藏。
到目前為止,我已經找到並測試了兩種解決方案。我將使用插入符號位置範例繪製粗略的草圖:
解決方案1)通過抽象介面給ViewModel一個View的引用
- 我將有一個介面,其中包含將由視圖實現的方法:
public interface IView { void InsertTextAtCaretPosition(string text); } public partial class View : UserControl, IView { public View() { InitializeComponent(); } // Interface implementation public void InsertTextAtCaretPosition(string text) { MyTextBox.Text = MyTextBox.Text.Insert(MyTextBox.CaretIndex, text); } }
- 將此介面注入到 ViewModel
public class ViewModel : ViewModelBase { private readonly IView _view; public ViewModel(IView view) { _view = view; } }
- 通過介面方法從 ViewModel 的命令處理程序執行程式碼隱藏
public ICommand InsertCommand { get; private set; } // Bound for instance to a button command // Command handler private void InsertText(string text) { _view.InsertTextAtCaretPosition(text); }要創建一個 View-ViewModel 對,我將使用依賴注入來實例化具體 View 並將其註入 ViewModel。
解決方案2)通過事件執行程式碼隱藏方法
- ViewModel 是特殊事件的發布者,命令處理程序引發這些事件
public class ViewModel : ViewModelBase { public ViewModel() { } public event InsertTextEventHandler InsertTextEvent; // Command handler private void InsertText(string text) { InsertTextEventHandler handler = InsertTextEvent; if (handler != null) handler(this, new InsertTextEventArgs(text)); } }
- 視圖訂閱這些事件
public partial class View : UserControl { public View() { InitializeComponent(); } private void UserControl_Loaded(object sender, RoutedEventArgs e) { ViewModel viewModel = DataContext as ViewModel; if (viewModel != null) viewModel.InsertTextEvent += OnInsertTextEvent; } private void UserControl_Unloaded(object sender, RoutedEventArgs e) { ViewModel viewModel = DataContext as ViewModel; if (viewModel != null) viewModel.InsertTextEvent -= OnInsertTextEvent; } private void OnInsertTextEvent(object sender, InsertTextEventArgs e) { MyTextBox.Text = MyTextBox.Text.Insert(MyTextBox.CaretIndex, e.Text); } }我不確定
Loaded和Unloaded的事件是否UserControl是訂閱和取消訂閱事件的好地方,但我在測試期間找不到問題。我在兩個簡單的例子中測試了這兩種方法,它們似乎都有效。現在我的問題是:
**1. 您認為哪種方法更可取?我可能看不到的解決方案之一有什麼好處或缺點嗎? 2. 您是否看到(並可能練習)其他解決方案?
感謝您提前回饋!**
**開發 WPF 應用程序我發現這兩種方法都很有用。如果您只需要從 ViewModel 到 View 的一次呼叫,那麼帶有事件處理程序的第二個選項看起來更簡單且足夠好。但是如果你需要這些層之間更複雜的介面,那麼引入介面是有意義的。
我個人的偏好是恢復您的選項一,並讓我的 ViewModel 實現一個 IViewAware 介面,並將這個 ViewModel 注入到 View 中。看起來像選項三。
public interface IViewAware { void ViewActivated(); void ViewDeactivated(); event Action CloseView; } public class TaskViewModel : ViewModelBase, IViewAware { private void FireCloseRequest() { var handler = CloseView; if (handler != null) handler(); } #region Implementation of IViewAware public void ViewActivated() { // Do something } public void ViewDeactivated() { // Do something } public event Action CloseView; #endregion }這是您的視圖的簡化程式碼:
public View(IViewAware viewModel) : this() { _viewModel = viewModel; DataContext = viewModel; Loaded += ViewLoaded; } void ViewLoaded(object sender, RoutedEventArgs e) { Activated += (o, v) => _viewModel.ViewActivated(); Deactivated += (o, v) => _viewModel.ViewDeactivated(); _viewModel.CloseView += Close; }在實際應用中,我通常使用外部邏輯來連接 V 和 VM,例如附加行為。**