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 的视图模型?
到目前为止我尝试过的事情:
- 使 MainWindow 具有我可以访问的静态实例单例,然后从命令中更改
ViewModel
属性。
- 尝试在 MainWindow 中实现某种形式的路由命令侦听器,然后让命令触发路由命令事件以由父级处理 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。这是一个非常好的介绍,我在开始使用它时发现它很有帮助。
要摆脱这个的关键是有一个外部 MainWindowViewModel
或 ApplicationViewModel
来处理导航,保存对工作区的引用等。然后选择如何与之交互由你决定。
在下面的代码中,我省略了定义 Window
、UserControl
等内容以使其更短。
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>
我目前正在掌握 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 的视图模型?
到目前为止我尝试过的事情:
- 使 MainWindow 具有我可以访问的静态实例单例,然后从命令中更改
ViewModel
属性。 - 尝试在 MainWindow 中实现某种形式的路由命令侦听器,然后让命令触发路由命令事件以由父级处理 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。这是一个非常好的介绍,我在开始使用它时发现它很有帮助。
要摆脱这个的关键是有一个外部 MainWindowViewModel
或 ApplicationViewModel
来处理导航,保存对工作区的引用等。然后选择如何与之交互由你决定。
在下面的代码中,我省略了定义 Window
、UserControl
等内容以使其更短。
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>