我正在尝试使用MVVM设计模式在WPF中实现一个井字游戏。游戏现在运行正常,但是当我试图重新设置游戏重新开始时,问题就出现了。在这种情况下,我所做的是直接更新集合,然后窗口中的网格不会更新为新值。
除此之外,我希望从MVVM的Angular 对我的实现提供一些反馈。下面是我的实现:
BoardView.xaml:
<Grid Name="boardGrid" Background="White">
<Grid.Resources>
<Style TargetType="Label">
<EventSetter Event="MouseLeftButtonUp" Handler="Label_Click"/>
</Style>
</Grid.Resources>
<UniformGrid Rows="3" Columns="3">
<Label Content="{Binding Path=Board[0], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[1], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[2], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[3], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[4], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[5], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[6], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[7], Mode=TwoWay}"/>
<Label Content="{Binding Path=Board[8], Mode=TwoWay}"/>
</UniformGrid>
</Grid>
字符串
BoardView.xaml.cs:
private BoardViewModel _viewModel;
public BoardView()
{
InitializeComponent();
_viewModel = new BoardViewModel();
this.DataContext = _viewModel;
}
private void Label_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
_viewModel.LabelClick(sender);
}
型
Board.cs:
private string[] tiles;
public Board()
{
tiles = new string[9];
for (int i = 0; i < 9; i++)
tiles[i] = TurnSymbol._.ToString();
}
public string[] Tiles
{
get { return tiles; }
set { tiles = value; }
}
型
以及BoardViewModel.cs中的相关部分:
enum TurnSymbol
{
x,
o,
_
}
internal class BoardViewModel : INotifyPropertyChanged
{
private TurnSymbol currentSymbol;
private Board board;
public event PropertyChangedEventHandler? PropertyChanged;
public string[] Board
{
get { return board.Tiles; }
set
{
board.Tiles = value;
OnPropertyChanged(nameof(Board));
}
}
public BoardViewModel()
{
board = new Board();
}
protected virtual void OnPropertyChanged(string propertyName)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
internal void LabelClick(object sender)
{
if((string)((Label)sender).Content == TurnSymbol._.ToString())
{
((Label)sender).Content = currentSymbol.ToString();
nextSymbol();
//Need completion
IsGameOver();
}
else
{
MessageBox.Show("This cell is already filled! Choose another one.");
}
}
private bool HandleGameOverMessage(string winningSymbol)
{
if(MessageBox.Show($"Game over! {winningSymbol} wins!", "Play again?", MessageBoxButton.YesNo) == MessageBoxResult.Yes)
{
return true;
}
}
private void HandleGameOver(string winningSymbol)
{
//Need completion
bool playAgain = HandleGameOverMessage(winningSymbol);
//Not needed but for extra clarity
if(playAgain)
Reset();
}
private void Reset()
{
for(int i = 0; i < 9; i++)
Board[i] = TurnSymbol._.ToString();
currentSymbol = TurnSymbol.x;
MessageBox.Show("x starts!", "", MessageBoxButton.OK);
}
internal bool IsGameOver()
{
//Check rows
if (board.Tiles[0] != TurnSymbol._.ToString() && board.Tiles[0] == board.Tiles[1] && board.Tiles[1] == board.Tiles[2])
{
HandleGameOver(board.Tiles[1]);
}
else if (board.Tiles[3] != TurnSymbol._.ToString() && board.Tiles[3] == board.Tiles[4] && board.Tiles[4] == board.Tiles[5])
...
return false;
}
}
型
我尝试在xaml中添加UpdateSourceTrigger=PropertyChanged
。我还调试了应用程序,可以看到Board属性和board.Tiles已更新并显示正确的值,所以我猜是我缺少了一些东西,或者我在绑定方面做错了。
1条答案
按热度按时间gkn4icbw1#
为了更新标签,使用ObservableCollection而不是字符串数组:
字符串
不要在XAML中显式创建9个Label元素,而是使用将其
ItemsSource
属性绑定到视图模型的Board
属性的ItemsControl。ItemsControl使用UniformGrid作为其ItemsPanel
,并在其ItemTemplate
中声明Label。型
不要访问视图模型中的任何视图元素(这是违反MVVM的),而只需更新集合元素,如
型
这通常由视图模型中的
ICommand
执行,例如aRelayCommand
。这种命令的一个简单示例如下所示:
型
使用这样一个命令并将被点击的单元格的索引传递给视图模型的一个简单方法可能看起来像下面所示的那样,使用Button而不是Label。注意属性
Command
、CommandParameter
、AlternationCount
和AlternationIndex
的分配。型
作为替代方案,您可以有一个带有数据项类的视图模型,该数据项类保存单元格的内容和更改单元格内容的命令。
一个简单的例子:
型
它的用法如下:
型