[英]Correct approach when notifying ViewModels of UserControl events in WPF MVVM?
[英]Correct approach to UserControl creation when using MVVM
這更多是一個概念性問題,而不是實際問題。 我剛剛開始學習用於開發UI的MVVM概念,並且遇到了難題,我不確定該如何回答:
假設我有一個主窗口和一個小彈出窗口(這是一個包含一些UI元素的小窗口)。 該程序的結構如下所示:
主窗口
模型<-MainWindowViewModel.cs <-MainWindowView.xaml(不包含任何代碼)
PopUpWindow (用戶控件)
模型<-PopUpWindowViewModel.cs <-PopUpWindowView.xaml(不包含任何代碼)
*該模型只是一堆與該問題無關的BL類。
現在,假設我要從MainWindowViewModel內部創建一個新的PopUp窗口(或什至將其實例保存在私有數據成員中)。 正確的做法是什么? 我的看法是我不能做這樣的事情:
PopUpWindow pop = new PopUpWindow()
因為這違反了從視圖模型中抽象視圖的目的(如果一年以后我想使用相同的PopUpWindowViewModel創建更好版本的PopUpWindow,該怎么辦?)。 另一方面,我不能僅使用它的視圖模型來初始化PopUpWindow的新實例(據我了解,viewModel不應了解將使用它的視圖的任何知識)。
希望一切都有意義...那么在那種情況下您會怎么做?
*只是為了進一步說明,假設出於論證的原因,我要描述的情況是MainWindowView上的一個按鈕,單擊該按鈕將打開PopUpWindowView。
提前感謝。
我也遇到了類似的困境,我將解釋如何解決。
假設您有MainWindow
和SettingsWindow
,要在單擊SettingsButton
時顯示它們。
您有兩個各自的視圖模型MainWindowViewModel
和SettingsViewModel
,它們將作為它們的Window.DataContext
屬性傳遞。
您的MainWindowViewModel
應該公開一個名為SettingsButtonCommand
(或類似名稱)的ICommand
屬性。 將此命令綁定到SettingsButton.Command
。
現在,您的命令應調用以下內容:
void OnSettingsButtonClicked()
{
var viewModel = new SettingsViewModel();
var window = new SettingsWindow();
window.DataContext = viewModel;
window.Show();
}
當您要使用Window.ShowDialog()
,會有一個小問題,因為您需要恢復執行。
對於這些情況,我具有DelegateCommand的異步變體:
public sealed class AsyncDelegateCommand : ICommand
{
readonly Func<object, Task> onExecute;
readonly Predicate<object> onCanExecute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public AsyncDelegateCommand(Func<object, Task> onExecute)
: this(onExecute, null) { }
public AsyncDelegateCommand(Func<object, Task> onExecute, Predicate<object> onCanExecute)
{
if (onExecute == null)
throw new ArgumentNullException("onExecute");
this.onExecute = onExecute;
this.onCanExecute = onCanExecute;
}
#region ICommand Methods
public async void Execute(object parameter)
{
await onExecute(parameter);
}
public bool CanExecute(object parameter)
{
return onCanExecute != null ? onCanExecute(parameter) : true;
}
#endregion
}
您專門說過,彈出窗口是UserControl,因此您可以使用基本數據模板。 首先為主窗口和彈出控件創建視圖模型:
public class MainViewModel : ViewModelBase
{
private PopUpViewModel _PopUp;
public PopUpViewModel PopUp
{
get { return _PopUp; }
set { _PopUp = value; RaisePropertyChanged(() => this.PopUp); }
}
}
public class PopUpViewModel : ViewModelBase
{
private string _Message;
public string Message
{
get { return _Message; }
set { _Message = value; RaisePropertyChanged(() => this.Message); }
}
}
MainViewModel的PopUp成員最初為null,當我們希望彈出窗口出現時,我們將其設置為PopUpViewModel的實例。 為此,我們在主窗口上創建一個內容控件並將其內容設置為該成員。 我們還使用數據模板來指定設置了彈出視圖模型時要創建的子控件的類型:
<Window.Resources>
<DataTemplate DataType="{x:Type local:PopUpViewModel}">
<local:PopUpWindow />
</DataTemplate>
</Window.Resources>
<StackPanel>
<Button Content="Show PopUp" Click="Button_Click_1" HorizontalAlignment="Left"/>
<ContentControl Content="{Binding PopUp}" />
</StackPanel>
我在這里通過在代碼中創建視圖模型以及單擊處理程序來做大事,但這只是出於說明目的:
public partial class MainWindow : Window
{
MainViewModel VM = new MainViewModel();
public MainWindow()
{
InitializeComponent();
this.DataContext = this.VM;
}
private void Button_Click_1(object sender, RoutedEventArgs e)
{
this.VM.PopUp = new PopUpViewModel { Message = "Hello World!" };
}
}
而已! 單擊該按鈕,將在彈出窗口下方顯示內容。 現在可能並不總是那么簡單,有時您可能想在父控件上創建多個子控件……在這種情況下,您需要設置ItemsControl,將其面板設置為Grid(例如),然后將數據模板修改為在每個元素上設置邊距等以定位它們。 或者,您可能並不總是知道將要創建哪種類型的視圖模型,在這種情況下,您需要為期望的每種類型添加多個數據模板。 無論哪種方式,您仍然可以很好地分離關注點,因為視圖決定了如何在視圖模型中顯示內容。 視圖模型本身仍然對視圖一無所知,它們可以獨立進行單元測試等。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.