如何在MainWindow的ViewModel中订阅事件?

时间:2020-04-27 17:07:53

标签: c# wpf events inotifypropertychanged

我试图找出如何从MainWindow.xaml.cs订阅在ViewModel中触发的事件。

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

似乎我需要做的只是在NOTIFYING CLASS(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));
    }
}

然后在我的SUBSCRIBING类(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

是的!看起来像在工作...但是等等-还有更多...

然后我登录

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

已触发属性更改,但MainWindow不再听到它了...为什么不呢?

1 个答案:

答案 0 :(得分:0)

现在,您似乎正在对LoginViewModel的两个实例进行操作。您只能监听在Loaded处理程序中创建的实例的事件,但是登录时,您正在使用的是LoginViewModel的另一个XAML实例。

要回答您的问题,如何处理视图模型,我建议将Frame替换为ContentControl
这就要求每个视图都有自己的视图模型,例如LoginViewModel-> LoginView,HomeViewModel-> HomeView。此外,必须在DataTemplate内定义每个视图。
ContentControl绑定到SelectedPage属性,并通过应用适当的DataTemplate呈现页面。

现在可以通过将SelectedPage的{​​{1}}属性设置为要导航到的页面的视图模型来在视图模型内进行导航。
通过这种方式,您可以将导航移动到视图模型,并可以舒适地处理事件等,因为在使用合成(或聚合)时,视图模型还具有其他视图模型的知识。

PageName.cs
枚举,以消除魔术字符串作为XAML和C#中的页面标识符。
可以在使用时用作MainWindowViewModel,例如CommandParameter导航到特定页面。

Button.Command

MainWindowViewModel.cs

enum PageName
{
  LoginView, HomeView
}

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

** MainWindow.xaml.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));
  }
}

LoginView.xaml

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

HomeView.xaml

<UserControl x:Class="LoginView">

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

MainWindow.xaml

<UserControl x:Class="LoginView">

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