2016-06-04 5 views
3

Я нахожусь в настоящее время в процессе освоения C# WPF MVVM шаблон и наткнулся на довольно большой плетень ...WPF MVVM изменения родительского окна ViewModel из ICommand исполнения

То, что я пытаюсь сделать, выстрелить LoginCommand, что при успешном выполнении позволит мне изменить модель представления родительского окна. Единственная проблема заключается в том, что я не могу представить себе способ изменить модель представления родительского окна без нарушения шаблона проектирования MVVM, потому что я не могу получить доступ к родительскому окну ContentControl, который устанавливает свой путь к активному UserControlViewModel в окне.

Вот сценарий:

В нашем App.xaml мы имеем два DataTemplates:
<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 }

Как я могу сделать метод Execute изменить ViewModel в окна, не нарушая шаблон MVVM?

Вещи я пытался до сих пор:

  • Заставить MainWindow иметь статический Instance синглтон, что я могу получить доступ, а затем изменить ViewModel свойство из команды.
  • Пытается внедрить некоторую форму маршрутизируемого прослушивателя команд в MainWindow, а затем убрать команды с отключенными маршрутными командами, которые будут обрабатываться родительским окном.
+1

Я хотел бы предложить вы хотите '' MainWindowViewModel' или ApplicationViewModel', который обрабатывает навигацию и устанавливает текущую 'Workspace' (' LoginViewModel' или 'LoggedInViewModel'). В [Navigation with MVVM] есть хороший пример (https://rachel53461.wordpress.com/2011/12/18/navigation-with-mvvm-2/). При этом различные представления привязаны к кнопкам, но в вашем случае вы просто хотите, чтобы кнопка входа в систему запускала команду для проверки, а затем переключилась по мере необходимости. (пример кода также имеется там) – Tone

+0

Хорошо, но как бы LoginCommand получить экземпляр ApplicationViewModel для выполнения команды ChangeViewModel? @Tone – Tdorno

+2

'LoginCommand' будет определен в' ApplicationViewModel', а не в 'LoginViewModel' (этот' ApplicationViewModel' будет 'DataContext' для' Window'). – Tone

ответ

3

Я сделал быстрый демонстрационный пример, чтобы показать один из способов сделать это. Я старался как можно проще дать общую идею. Существует много разных способов решения одной и той же задачи (например, вы можете сохранить ссылку на MainWindowViewModel внутри LoginViewModel, обрабатывать все там, затем вызвать метод на MainWindowViewModel, чтобы вызвать изменение рабочей области или вы могли использовать события/сообщения и т. Д.).

Определенно читайте Navigation with MVVM. Это действительно хорошее введение, которое я нашел полезным, когда я начинал с него.

Ключевое значение, которое следует убрать, состоит в том, чтобы иметь внешний MainWindowViewModel или ApplicationViewModel, который обрабатывает навигацию, содержит ссылки на рабочие области и т. Д. Тогда выбор того, как вы взаимодействуете с этим, зависит от вас.

В нижеследующем коде я оставил беспорядок от определения Window, UserControl и т. Д., Чтобы сохранить его короче.

Окно:

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

MainWindowViewModel (это должно быть установлено как DataContext для Window):

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 на LoginView - LoginCommand на WindowDataContext (т.е. MainWindowViewModel).

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

LoginViewModel:

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(); 
      } 
     } 
    } 
} 

WorkspaceViewModel:

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)); 
    } 
} 

RelayCommand:

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> 
0
<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> 
+0

Извините, это не помогает – Tdorno

+0

Возможно, я не понял вопроса –