我正在为学校开发WPF应用程序(一个黑白棋游戏)。我对MVVM很陌生,所以原谅我的 noobishness 。
我有一个StartScreen.xaml
绑定到StartViewModel
代表游戏开头的屏幕,BoardScreen.xaml
绑定到BoardViewModel
,这是一个屏幕黑白棋。
我在ContentControl
中使用MainWindow.xaml
来显示屏幕。 ViewModels实现了一个抽象的Screen
类,我使用导航器在它们之间切换。
在我的BoardScreen.xaml.cs
中,我试图绑定BoardViewModel
中的某个事件。该事件称为GameEnded
,并在游戏结束时调用。我想从后面的代码(BoardScreen.xaml.cs
)订阅该事件,以便在游戏结束时播放声音。像这样:
GameEnded += BoardScreen_GameEnded
...
private void BoardScreen_GameEnded(object sender, EventArgs e)
{
PlaySound(WinSound);
}
问题在于我真的不知道如何参加那个活动。我似乎无法使用DataContext
或Content
属性访问它。
有没有人知道如何解决这个问题?如果我需要进一步澄清,请询问。谢谢! :)
MainWindow.xaml
<Window x:Class="View.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:View"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel"
mc:Ignorable="d"
Title="Reversi"
SizeToContent="WidthAndHeight">
<ContentControl Content="{Binding CurrentScreen}" Margin="30" />
</Window>
App.xaml
<Application x:Class="View.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
StartupUri="MainWindow.xaml"
xmlns:view="clr-namespace:View"
xmlns:vm="clr-namespace:ViewModel;assembly=ViewModel">
<Application.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Skins\blue.xaml" />
</ResourceDictionary.MergedDictionaries>
<DataTemplate DataType="{x:Type vm:StartViewModel}">
<view:StartScreen />
</DataTemplate>
<DataTemplate DataType="{x:Type vm:BoardViewModel}">
<view:BoardScreen />
</DataTemplate>
</ResourceDictionary>
</Application.Resources>
</Application>
Navigation.cs
public abstract class Screen
{
protected readonly Navigator navigator;
protected Screen(Navigator navigator)
{
this.navigator = navigator;
}
protected void SwitchTo(Screen screen)
{
this.navigator.CurrentScreen = screen;
}
}
public class Navigator : INotifyPropertyChanged
{
private Screen currentScreen;
public Navigator()
{
this.currentScreen = new StartViewModel(this);
}
public Screen CurrentScreen
{
get
{
return currentScreen;
}
set
{
this.currentScreen = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentScreen)));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
public class SimpleCommand : ICommand
{
private readonly Action action;
public SimpleCommand(Action action)
{
this.action = action;
}
public event EventHandler CanExecuteChanged
{
add { }
remove { }
}
public bool CanExecute(object parameter)
{
return true;
}
public void Execute(object parameter)
{
action();
}
}
BoardScreen.xaml
public partial class BoardScreen : UserControl
{
public System.Windows.Media.MediaPlayer ClickSound { get; private set; }
public System.Windows.Media.MediaPlayer WinSound { get; private set; }
public BoardScreen()
{
// Initialize sounds
ClickSound = new MediaPlayer();
WinSound = new MediaPlayer();
ClickSound.Open(new Uri(@"..\..\Sounds\click.mp3", UriKind.Relative));
WinSound.Open(new Uri(@"..\..\Sounds\win.mp3", UriKind.Relative));
}
private void BoardScreen_GameEnded(object sender, EventArgs e)
{
PlaySound(WinSound);
}
private void PlaySound(MediaPlayer sound)
{
sound.Stop();
sound.Position = TimeSpan.Zero;
sound.Play();
}
internal void PlayClickSound(object sender, RoutedEventArgs e)
{
PlaySound(ClickSound);
}
internal void PlayWinSound(object sender, RoutedEventArgs e)
{
PlaySound(WinSound);
}
}
更新
正如评论中提到的@Spongebrot,我能够通过在DataContextChanged事件中进行订阅来订阅该事件。类型检查是必要的,否则我会得到NullPointerException。
DataContextChanged += (o, e) => {
if(DataContext is BoardViewModel)
{
var viewModel = DataContext;
(DataContext as BoardViewModel).GameEnded += BoardScreen_GameEnded;
}
};