使用MVVM时正确创建UserControl的方法

时间:2014-01-28 19:38:20

标签: c# wpf xaml mvvm

这更像是一个概念问题,而不是一个实际问题。我刚开始学习用于开发UI的MVVM概念,而且我遇到了一个dillema我不确定答案:

假设我有一个主窗口和一个小弹出窗口(意味着它是一个带有一些UI元素的小窗口)。该程序的结构将如下所示:

MainWindow
model< - MainWindowViewModel.cs< - MainWindowView.xaml(不包含代码隐藏)

PopUpWindow (A UserControl)
model< - PopUpWindowViewModel.cs< - PopUpWindowView.xaml(不包含代码隐藏)

*模型只是一堆与这个问题无关的BL类。

现在,假设我想从MainWindowViewModel内部创建一个新的PopUp窗口(甚至在私有数据成员中保存它的实例)。这样做的正确方法是什么? 我看待它的方式我做不到这样的事情:

PopUpWindow pop = new PopUpWindow()

因为它有点破坏了从视图模型中抽象视图的目的(如果一年后我会想要使用相同的PopUpWindowViewModel创建更好的PopUpWindow版本怎么办?)。 另一方面,我无法使用它的视图模型初始化PopUpWindow的新实例(我理解的viewModel不应该知道将使用它的视图)。

希望这一切都有意义......那么在那种情况下你会做什么?

*为了进一步澄清,让我们说为了论证,我所描述的情况是MainWindowView上的一个按钮,点击后会打开一个PopUpWindowView。

感谢advnace。

2 个答案:

答案 0 :(得分:0)

我有点类似的困境,我会解释我是如何解决的。

假设您有MainWindowSettingsWindow,您希望在点击SettingsButton时显示。

您有两个相应的视图模型,MainWindowViewModelSettingsViewModel,您将作为Window.DataContext属性传递。

您的MainWindowViewModel应该公开名为ICommand(或类似)的SettingsButtonCommand媒体资源。将此命令绑定到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
}

答案 1 :(得分:0)

您已经明确表示弹出窗口是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(比如说)并将数据模板修改为在每个元素上设置边距等以定位它们。或者您可能并不总是知道要创建哪种类型的视图模型,在这种情况下,您需要为您期望的每种类型添加多个数据模板。无论哪种方式,您仍然可以很好地分离关注点,因为正是决定如何在视图模型中显示内容的视图。视图模型本身仍然不了解视图,它们可以独立进行单元测试等。