简体   繁体   English

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

[英]How do I subscribe to an event from the ViewModel in the MainWindow?

I'm trying to figure out how to subscribe to an event that's firing in my ViewModel from my MainWindow.xaml.cs.我试图弄清楚如何从我的 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主窗口.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");
        }
    }
}

It would seem all I need to do is in my NOTIFYING CLASS (ViewModel):看来我需要做的就是在我的 NOTIFYING CLASS (ViewModel) 中:

  • Implement INotifyPropertyChanged实施INotifyPropertyChanged

  • Add the public event PropertyChangedEventHandler PropertyChanged;添加public event PropertyChangedEventHandler PropertyChanged;

  • Add the OnNotifyPropertyChanged("IsAuthenticatedUser");添加OnNotifyPropertyChanged("IsAuthenticatedUser");

  • Add the following method to my notifying class:在我的通知 class 中添加以下方法:

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

Then in my SUBSCRIBING class (MainViewWindow) add the following:然后在我的订阅 class (MainViewWindow) 添加以下内容:

  • Subscribe to the event in my ViewModel LoginViewModel.PropertyChanged += UserAuthentication_PropertyChange;订阅我的 ViewModel LoginViewModel.PropertyChanged += UserAuthentication_PropertyChange;中的事件
  • Add the method that is referenced in the subscription like the one below添加订阅中引用的方法,如下所示
    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");
        }
    }

Unfortunately, this doesn't seem to work.不幸的是,这似乎不起作用。 The subscribed method UserAuthentication_PropertyChange is ONLY fired once when the application first starts and there is a change in the value of the IsAuthenticatedUser Property on the ViewModel.订阅的方法UserAuthentication_PropertyChange仅在应用程序首次启动并且 ViewModel 上的IsAuthenticatedUser属性的值发生更改时触发一次。 Why doesn't this work every time there is a change?为什么每次发生变化时这都不起作用?

For more information if you can help me understand this phenomenon (to me) here's a quick run down of what gets printed to the console as the app runs and I trigger login and logout sequences.有关更多信息,如果您可以帮助我(对我而言)理解这种现象,这里是应用程序运行时打印到控制台的内容的快速运行,我触发登录和注销序列。

Start the app - Currently logged out.启动应用程序 - 当前已注销。

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.耶。 It looks like its working... but wait - there's more...它看起来像它的工作......但是等等 - 还有更多......

Then I log in然后我登录

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

The property change is fired but the MainWindow no longer hears it... Why wouldn't it?属性更改被触发,但 MainWindow 不再听到它......为什么不呢?

Right now, it looks like you are operating on two instances of LoginViewModel.现在,看起来您正在对两个 LoginViewModel 实例进行操作。 You only listen to the events of the instance you created in the Loaded handler, but when you login you are using a different maybe XAML instance of LoginViewModel.您只收听您在 Loaded 处理程序中创建的实例的事件,但是当您登录时,您正在使用不同的可能 XAML LoginViewModel 实例。

To answer your question, how to handle the view models, I suggest to replace the Frame with a ContentControl .要回答您的问题,如何处理视图模型,我建议将Frame替换为ContentControl
This requires that each view has its own view model eg LoginViewModel --> LoginView, HomeViewModel --> HomeView.这要求每个视图都有自己的视图 model 例如 LoginViewModel --> LoginView, HomeViewModel --> HomeView。 Furthermore must be each view defined inside a DataTemplate .此外,必须在DataTemplate中定义每个视图。
A ContentControl binds to the SelectedPage property and renders the page by applying the appropriate DataTemplate . ContentControl绑定到SelectedPage属性并通过应用适当的DataTemplate呈现页面。

Navigation now takes place inside the view model by setting a SelectedPage property of the MainWindowViewModel to the view model of the page you wish to navigate to.通过将MainWindowViewModelSelectedPage属性设置为您希望导航到的页面的视图 model,导航现在发生在视图 model 内。
This way you can move the navigation to the view model and can comfortably handle events etc, since the view model has knowledge of other view models when using composition (or aggregation).这样,您可以将导航移动到视图 model 并且可以轻松处理事件等,因为视图 model 在使用组合(或聚合)时了解其他视图模型。

PageName.cs页面名称.cs
Enumeration to eliminate magic strings as page identifiers in XAML and C#.枚举以消除 XAML 和 C# 中的页面标识符的魔法字符串。
Can be used as CommandParameter when used eg with a Button.Command to navigate to a certain page.可以用作CommandParameter ,例如与Button.Command一起使用以导航到某个页面。

enum PageName
{
  LoginView, HomeView
}

MainWindowViewModel.cs 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登录视图模型.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** ** 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 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主页查看.xaml

<UserControl x:Class="LoginView">

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

MainWindow.xaml主窗口.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>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在ViewModel中订阅PropertyChanged事件? - How do I subscribe to PropertyChanged event in my ViewModel? 如何在属于 View 但不属于 MainWindow 的 ViewModel 中处理 MainWindow 的事件 - How to handle event of MainWindow in one of the ViewModel which belongs to a View, but not MainWindow 如何从 ViewModel 上的 WebBrowser 控件订阅 Navigated 事件? (MVVM) - How to subscribe to Navigated event from the WebBrowser control on a ViewModel? (MVVM) 如何从另一个 static class 订阅 class 中的事件? - How do I subscribe to an event in a class from another static class? 如何从Page访问MainWindow视图模型? - How to get access to the MainWindow viewmodel from Page? 我如何订阅用户控件的事件 - how do I subscribe to an event of a usercontrol 如何在WPF中订阅上下文菜单关闭事件? - How do I subscribe to context menu closing event in WPF? 我如何订阅另一个程序集中引发的事件 - how do I subscribe to an event in raised in another assembly Xamarin Android-如何将事件从MainActivity传递给Forms页面上的ViewModel? - Xamarin Android - How do I pass an event from MainActivity to ViewModel on Forms Page? WPF 一个 ViewModel 作为 MainWindow 的属性,如何从页面正确访问 ViewModel? - WPF one ViewModel as a property of MainWindow, how to access ViewModel from pages properly?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM