Как подписаться на событие из ViewModel в MainWindow? - PullRequest
0 голосов
/ 27 апреля 2020

Я пытаюсь выяснить, как подписаться на событие, которое запускается в моей ViewModel из моего MainWindow.xaml.cs.

ViewModel:

public class LoginViewModel : INotifyPropertyChanged
{
    private bool isAuthenticatedUser;
    public bool IsAuthenticatedUser 
    {
        get { return isAuthenticatedUser; }

        set
        {
            Debug.WriteLine("Old value:" + isAuthenticatedUser);

            isAuthenticatedUser = value;
            Debug.WriteLine("New value:" + isAuthenticatedUser);

            OnNotifyPropertyChanged("IsAuthenticatedUser");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected void OnNotifyPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            Debug.WriteLine($"Property Change on LoginView.IsAuthenticatedUser");
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

MainWindow.xaml.cs

public partial class MainWindow
{
    private int? oldUnreadTextsCount = 0;

    public MainWindow()
    {
        DataContext = new MainWindowViewModel();
        InitializeComponent();
        InitializeTimer();
        Loaded += MainWindow_Loaded;
    }

    public TextsViewModel TextsViewModel { get; set; }
    public LoginViewModel LoginViewModel { get; set; }
    public MainWindowViewModel MainWindowViewModel { get; set; }

    private void MainWindow_Loaded(object sender, RoutedEventArgs e)
    {
        LoginViewModel = new LoginViewModel();

        // First Subscribe;
        LoginViewModel.PropertyChanged += UserAuthentication_PropertyChange;

        // Second Fire Change / Fire update on UserAuthentication_PropertyChange.
        LoginViewModel.TestAuthentication();

        // Third Change Views appropriately
        if (LoginViewModel.IsAuthenticatedUser)
        {
            NavigationFrame.NavigationService.Navigate(new HomeView());

            TextsViewModel = new TextsViewModel();
            TextsViewModel.PropertyChanged += UnreadTexts_PropertyChanged;
        }
        else
        {
            NavigationFrame.NavigationService.Navigate(new LoginView());
        }
    }

    private void UserAuthentication_PropertyChange(object sender, PropertyChangedEventArgs e)
    {
        Debug.WriteLine("PROPERTY CHANGE REGISTERED:MainWindow:Checking if logged in.");

        if (LoginViewModel.IsAuthenticatedUser)
        {
            Header.Visibility = Visibility.Visible;
            Footer.Visibility = Visibility.Visible;
            Debug.WriteLine("Logged In");
        }
        else
        {
            Header.Visibility = Visibility.Hidden;
            Footer.Visibility = Visibility.Hidden;
            Debug.WriteLine("Logged Out");
        }
    }
}

Казалось бы, все, что мне нужно сделать, это в моем КЛАССЕ УВЕДОМЛЕНИЙ (ViewModel):

  • Реализация INotifyPropertyChanged

  • Добавить public event PropertyChangedEventHandler PropertyChanged;

  • Добавить OnNotifyPropertyChanged("IsAuthenticatedUser");

  • Добавьте следующий метод в мой уведомляющий класс:

protected void OnNotifyPropertyChanged(string propertyName)
{
    if (PropertyChanged != null)
    {
        Debug.WriteLine($"Property Change on LoginView.IsAuthenticatedUser");
        PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
    }
}

Затем в моем классе ПОДПИСКА (MainViewWindow) добавьте следующее:

  • Подписаться на событие в моей ViewModel LoginViewModel.PropertyChanged += UserAuthentication_PropertyChange;
  • Добавить метод, на который есть ссылка в подписке, как показано ниже
    private void UserAuthentication_PropertyChange(object sender, PropertyChangedEventArgs e)
    {
        Debug.WriteLine("PROPERTY CHANGE REGISTERED:MainWindow:Checking if logged in.");

        if (LoginViewModel.IsAuthenticatedUser)
        {
            Header.Visibility = Visibility.Visible;
            Footer.Visibility = Visibility.Visible;
            Debug.WriteLine("Logged In");
        }
        else
        {
            Header.Visibility = Visibility.Hidden;
            Footer.Visibility = Visibility.Hidden;
            Debug.WriteLine("Logged Out");
        }
    }

К сожалению кажется, это не работает. Подписанный метод UserAuthentication_PropertyChange запускается ТОЛЬКО один раз при первом запуске приложения, и в ViewModel изменяется значение свойства IsAuthenticatedUser. Почему это не работает каждый раз, когда происходят изменения?

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

Запустите приложение - В настоящее время вышли из системы.

TestAuthentication:LoginViewModel:False
Old value:False
New value:False
Property Change on LoginView.IsAuthenticatedUser
PROPERTY CHANGE REGISTERED:MainWindow:Checking if logged in.
Logged Out

Yay! Похоже, это работает ... но подождите - это еще не все ...

Затем я вхожу в систему

AuthenticateUser:LoginViewModel:True
Old value:False
New value:True
Property Change on LoginView.IsAuthenticatedUser

Изменение свойства запускается, но MainWindow больше не слышит его ... Почему бы и нет?

1 Ответ

0 голосов
/ 27 апреля 2020

В данный момент похоже, что вы работаете с двумя экземплярами LoginViewModel. Вы слушаете только события экземпляра, который вы создали в обработчике Loaded, но при входе в систему вы используете другой, возможно, XAML-экземпляр LoginViewModel.

Чтобы ответить на ваш вопрос, как работать с моделями представления, я предлагаем заменить Frame на ContentControl.
. Для этого требуется, чтобы у каждого представления была своя собственная модель представления, например, LoginViewModel -> LoginView, HomeViewModel -> HomeView. Кроме того, каждое представление должно быть определено внутри DataTemplate.
A ContentControl, привязываемого к свойству SelectedPage и отображающего страницу, применяя соответствующий DataTemplate.

. Навигация теперь происходит внутри просмотреть модель, установив SelectedPage свойство MainWindowViewModel для модели представления страницы, к которой вы будете sh переходить.
Таким образом вы можете перемещать навигацию к модели представления и удобно обрабатывать события и т. д. c, поскольку модель представления знает о других моделях представления при использовании композиции (или агрегации).

PageName.cs
Перечисление для исключения строк magi c как страницы идентификаторы в XAML и C#.
Может использоваться как CommandParameter при использовании, например, с Button.Command для перехода к определенной странице.

enum PageName
{
  LoginView, HomeView
}

MainWindowViewModel.cs

public class MainWindowViewModel : INotifyPropertyChanged
{  
  public TextViewModel TextViewModel { get; set; }
  private Dictionary<PageName, object> Pages  { get; set; }

  private object selectedPage;   
  public object SelectedPage
  {
    get => this.selectedPage;
    set 
    { 
      this.selectedPage = value; 
      OnPropertyChanged();
    }
  }

  private bool isAuthenticated;   
  public bool IsAuthenticated
  {
    get => this.isAuthenticated;
    set 
    { 
      this.isAuthenticated = value; 
      OnPropertyChanged();
    }
  }

  public MainWindowViewModel()
  {
    // Alternatively use constructor injection

    var loginPageViewModel = new LoginViewModel();
    loginPageViewModel.AuthenticationStatusChanged += OnAuthenticationStatusChanged;

    this.Pages = new Dictionary<PageName, object>()
    {
      { PageName.LoginView, loginPageViewModel },
      { PageName.HomeView, new HomeViewModel() }
    }

    // Show login screen initially
    this.SelectedPage = Pages[PageName.LoginView];

    this.TextViewModel = new TextViewModel();
  }

  // Example ICommand execute action, triggered e.g. on Button.Command,
  // where the CommandParameter is a value of the PageName enumeration
  private void ExecuteNavigateToPage(object commandParameter)
  {    
    if (commandParameter is PageName pageName
      && this.Pages.TryGetValue(pageName, out object pageViewModel)
    {
      this.SelectedPage = pageViewModel;
    }
  }

  private void OnAuthenticationStatusChanged(object sender, EventArgs e)
  {    
    this.IsAuthenticated = (sender as LoginViewModel).IsAuthenticatedUser;

    // Redirect to main page when user has authenticated successfully        
    if (this.IsAuthenticated)
    {
      this.SelectedPage = Pages[PageName.HomeView];
    }
  }

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

LoginViewModel.cs

public class LoginViewModel : INotifyPropertyChanged
{  
  public event EventHandler AuthenticationStatusChanged;    
  public event PropertyChangedEventHandler PropertyChanged;

  private bool isAuthenticatedUser;   
  public bool IsAuthenticatedUser
  {
    get => this.isAuthenticatedUser;
    set 
    { 
      this.isAuthenticatedUser = value; 
      OnPropertyChanged();

      OnAuthenticationStatusChanged();
    }
  }

  private void OnAuthenticationStatusChanged()
  {
    this.AuthenticationStatusChanged?.Invoke(this, EventArgs.Empty);
  }

  protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
  {
    this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
  }
}

** MainWindow.xaml.cs **

public partial class MainWindow
{
  private int? oldUnreadTextsCount = 0;

  public MainWindow()
  {
    InitializeComponent();
    InitializeTimer();

    var mainWindowViewModel = new MainWindowViewModel();
    DataContext = mainWindowViewModel;

    mainWindowViewModel.TextsViewModel.PropertyChanged += UnreadTexts_PropertyChanged;

    Loaded += MainWindow_Loaded;
  }

  private void MainWindow_Loaded(object sender, RoutedEventArgs e)
  {
    // Navigation (and debugger output) has been moved to the MainWindowViewModel.
    // Toggling of the visibility has been moved to XAML using DataTrigger
  }
}

LoginView .xaml

<UserControl x:Class="LoginView">

  <!-- 
    DataContext is automatically the LoginViewModel, 
    accessible from code-behind via the DataContext property 
  -->
  <TextBlock Text="{Binding IsAuthenticatedUser}" />
</UserControl>

HomeView.xaml

<UserControl x:Class="LoginView">

  <!-- 
    DataContext is automatically the HomeViewModel, 
    accessible from code-behind via the DataContext property 
  -->
  <TextBlock Text="Welcome back user!" />
</UserControl>

MainW indow.xaml

<Window>
  <Window.Resources>
    <DataTemplate DataType="{x:Type LoginViewModel}">
      <LoginView />
    </DataTemplate>

    <DataTemplate DataType="{x:Type HomeViewModel}">
      <HomeView />
    </DataTemplate>
  </Window.Resources>

  <StackPanel>
    <TextBlock x:Name="Header">
      <TextBlock.Style>
       <Style TargetType="TextBlock">
         <Setter Property="Visibility" Value="Hidden" />

         <Style.Triggers>
           <DataTrigger Binding="{Binding IsAuthenticated}" Value="True">
             <Setter Property="Visibility" Value="Visible" />
           </DataTrigger>
         </Style.Triggers>
       </Style>
      </TextBlock>
    </TextBlock>

    <!-- 
      Page host. Setting the content to a page view model,
      will automatically load the corresponding DataTemplate
    -->
    <ContentControl Content="{Binding SelectedPage}" />

    <TextBlock x:Name="Footer">
      <TextBlock.Style>
       <Style TargetType="TextBlock">
         <Setter Property="Visibility" Value="Hidden" />

         <Style.Triggers>
           <DataTrigger Binding="{Binding IsAuthenticated}" Value="True">
             <Setter Property="Visibility" Value="Visible" />
           </DataTrigger>
         </Style.Triggers>
       </Style>
      </TextBlock>
    </TextBlock>
</Window>
...