WPF MVVM 从 icommand 执行更改父视图模型 window

WPF MVVM changing parent window viewmodel from icommand execution

我目前正在掌握 C# WPF MVVM 模式,并且偶然发现了一个相当大的障碍...

我想要做的是触发 LoginCommand,成功执行后将允许我更改父 window 的视图模型。唯一的问题是我想不出一种在不破坏 MVVM 设计模式的情况下更改父 window 的视图模型的方法,因为我无法访问父 window 的 ContentControl 将其路径设置为 window.

中的活动 UserControlViewModel

场景如下:

在我们的 App.xaml 中,我们有两个数据模板:
<DataTemplate DataType="{x:Type ViewModels:LoginViewModel}"> <Views:LoginView /> </DataTemplate> <DataTemplate DataType="{x:Type ViewModels:LoggedInViewModel}"> <Views:LoggedView /> </DataTemplate>

在我们的 MainWindow 中我们有:
<ContentControl Content="{Binding ViewModel}" />
后面的 MainWindow 代码将设置 ViewModel = LoginViewModel

在我们的 LoginViewModel 中我们有:
<Button Command="{Binding LoginCommand}" CommandParameter="{Binding ElementName=pwPasswordBoxControlInXaml}" />

现在为了钱... LoginCommand:
public void Execute(object parameter) { // Do some validation // Async login task stuff // ... // Logged in... change the MainWindow's ViewModel to the LoggedInViewModel }

如何在不破坏 MVVM 模式的情况下使 Execute 方法更改 window 的视图模型?

到目前为止我尝试过的事情:

<ContentControl Content="{Binding ViewModel}">
  <ContentControl.Resources>
     <DataTemplate DataType="{x:Type vm:LoginViewModelClass}">
        <!-- some LoginView -->
     </DataTemplate>

     <DataTemplate DataType="{x:Type vm:LoggedInViewModelClass}">
        <!-- some LoggedInView -->
     </DataTemplate>
  </ContentControl.Resources>
</ContentControl>

我做了一个快速演示来展示一种方法。我尽可能简单地给出了总体思路。有很多不同的方法可以完成同一件事(例如,您可以在 LoginViewModel 中持有对 MainWindowViewModel 的引用,处理那里的所有内容,然后调用 MainWindowViewModel 上的方法来触发工作区更改,或者你可以使用 Events/Messages,等等)。

不过一定要读一读 Navigation with MVVM。这是一个非常好的介绍,我在开始使用它时发现它很有帮助。

要摆脱这个的关键是有一个外部 MainWindowViewModelApplicationViewModel 来处理导航,保存对工作区的引用等。然后选择如何与之交互由你决定。

在下面的代码中,我省略了定义 WindowUserControl 等内容以使其更短。

Window:

<DockPanel>
    <ContentControl Content="{Binding CurrentWorkspace}"/>
</DockPanel>

MainWindowViewModel(对于Window,这应该设置为DataContext):

public class MainWindowViewModel : ObservableObject
{
    LoginViewModel loginViewModel = new LoginViewModel();
    LoggedInViewModel loggedInViewModel = new LoggedInViewModel();

    public MainWindowViewModel()
    {
        CurrentWorkspace = loginViewModel;

        LoginCommand = new RelayCommand((p) => DoLogin());
    }

    private WorkspaceViewModel currentWorkspace;
    public WorkspaceViewModel CurrentWorkspace
    {
        get { return currentWorkspace; }
        set
        {
            if (currentWorkspace != value)
            {
                currentWorkspace = value;
                OnPropertyChanged();
            }
        }
    }

    public ICommand LoginCommand { get; set; }

    public void DoLogin()
    {
        bool isValidated = loginViewModel.Validate();
        if (isValidated)
        {
            CurrentWorkspace = loggedInViewModel;
        }
    }
}

登录视图:

在此示例中,我将 LoginView 上的 Button 绑定到 Window DataContext 上的 LoginCommand(即 MainWindowViewModel).

<StackPanel Orientation="Vertical">
    <TextBox Text="{Binding UserName}"/>
    <Button Content="Login" Command="{Binding RelativeSource={RelativeSource AncestorType=Window}, Path=DataContext.LoginCommand}"/>
</StackPanel>

登录视图模型:

public class LoginViewModel : WorkspaceViewModel
{
    private string userName;
    public string UserName
    {
        get { return userName; }
        set
        {
            if (userName != value)
            {
                userName = value;
                OnPropertyChanged();
            }
        }
    }

    public bool Validate()
    {
        if (UserName == "bob")
        {
            return true;
        }
        else
        {
            return false;
        }
    }
}

LoggedInView:

<StackPanel Orientation="Vertical">
    <TextBox Text="{Binding RestrictedData}"/>
</StackPanel>

LoggedInViewModel:

public class LoggedInViewModel : WorkspaceViewModel
{
    private string restrictedData = "Some restricted data";
    public string RestrictedData
    {
        get { return restrictedData; }
        set
        {
            if (restrictedData != value)
            {
                restrictedData = value;
                OnPropertyChanged();
            }
        }
    }
}

工作区视图模型:

public abstract class WorkspaceViewModel : ObservableObject
{
}

然后其他一些 类 您可能已经实施(或替代方案)。

ObservableObject:

public abstract class ObservableObject : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this,
            new PropertyChangedEventArgs(propertyName));
    }
}

中继命令:

public class RelayCommand : ICommand
{
    private readonly Action<object> execute;
    private readonly Predicate<object> canExecute;

    public RelayCommand(Action<object> execute)
        : this(execute, null)
    { }

    public RelayCommand(Action<object> execute, Predicate<object> canExecute)
    {
        if (execute == null)
        {
            throw new ArgumentNullException("execute");
        }

        this.execute = execute;
        this.canExecute = canExecute;
    }

    public event EventHandler CanExecuteChanged
    {
        add { CommandManager.RequerySuggested += value; }
        remove { CommandManager.RequerySuggested -= value; }
    }

    [DebuggerStepThrough]
    public bool CanExecute(object parameter)
    {
        return canExecute == null ? true : canExecute(parameter);
    }

    public void Execute(object parameter)
    {
        execute(parameter);
    }
}

App.Xaml:

    <DataTemplate DataType="{x:Type ViewModels:LoginViewModel}">
        <Views:LoginView />
    </DataTemplate>
    <DataTemplate DataType="{x:Type ViewModels:LoggedInViewModel}">
        <Views:LoggedInView />
    </DataTemplate>