如何从主窗口中的 ViewModel 订阅事件?
How do I subscribe to an event from the ViewModel in the MainWindow?
我正在尝试了解如何从我的 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");
}
}
}
看来我需要做的就是在我的 NOTIFYING CLASS (ViewModel):
实施INotifyPropertyChanged
添加public event PropertyChangedEventHandler PropertyChanged;
添加OnNotifyPropertyChanged("IsAuthenticatedUser");
在我的通知中添加以下方法class:
protected void OnNotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
Debug.WriteLine($"Property Change on LoginView.IsAuthenticatedUser");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
然后在我的 SUBSCRIBING class (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 不再听到它...为什么不呢?
现在,您似乎正在对 LoginViewModel 的两个实例进行操作。您只监听您在 Loaded 处理程序中创建的实例的事件,但是当您登录时,您使用的可能是 XAML LoginViewModel 的另一个实例。
为了回答您的问题,即如何处理视图模型,我建议将 Frame
替换为 ContentControl
。
这要求每个视图都有自己的视图模型,例如登录视图模型 --> 登录视图、主页视图模型 --> 主页视图。此外必须在 DataTemplate
.
中定义每个视图
ContentControl
绑定到 SelectedPage
属性 并通过应用适当的 DataTemplate
.
呈现页面
现在通过将 MainWindowViewModel
的 SelectedPage
属性 设置为您希望导航到的页面的视图模型,导航在视图模型内进行。
这样您就可以将导航移动到视图模型并可以轻松地处理事件等,因为视图模型在使用组合(或聚合)时了解其他视图模型。
PageName.cs
在 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>
MainWindow.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>
我正在尝试了解如何从我的 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");
}
}
}
看来我需要做的就是在我的 NOTIFYING CLASS (ViewModel):
实施
INotifyPropertyChanged
添加
public event PropertyChangedEventHandler PropertyChanged;
添加
OnNotifyPropertyChanged("IsAuthenticatedUser");
在我的通知中添加以下方法class:
protected void OnNotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
Debug.WriteLine($"Property Change on LoginView.IsAuthenticatedUser");
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
然后在我的 SUBSCRIBING class (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 不再听到它...为什么不呢?
现在,您似乎正在对 LoginViewModel 的两个实例进行操作。您只监听您在 Loaded 处理程序中创建的实例的事件,但是当您登录时,您使用的可能是 XAML LoginViewModel 的另一个实例。
为了回答您的问题,即如何处理视图模型,我建议将 Frame
替换为 ContentControl
。
这要求每个视图都有自己的视图模型,例如登录视图模型 --> 登录视图、主页视图模型 --> 主页视图。此外必须在 DataTemplate
.
中定义每个视图
ContentControl
绑定到 SelectedPage
属性 并通过应用适当的 DataTemplate
.
现在通过将 MainWindowViewModel
的 SelectedPage
属性 设置为您希望导航到的页面的视图模型,导航在视图模型内进行。
这样您就可以将导航移动到视图模型并可以轻松地处理事件等,因为视图模型在使用组合(或聚合)时了解其他视图模型。
PageName.cs
在 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>
MainWindow.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>