Ein kleines Übungs-Projekt für den MVVM-Pattern.
Richtlinie ist, Daten und Oberfläche zu trennen - nur über Databindings miteinander verbunden. Daraus folgt, es gibt auch keinen CodeBehind, wo irgendwelche Control-Events zu behandeln wären.
Das Datenmodell
Ist denkbar einfach: Es gibt 9 Zellen, und jede Zelle kann frei sein, oder bereits von einem Spieler besetzt.
Und dassis auch schon alles vom Datenmodell her.
Dazu kommt noch das abwechselnde Ziehen und die Auswertung nach jedem Zug, ob schon einer gewonnen hat, wer, oder unentschieden. Aber dassis nicht eiglich Datenmodell, sondern Business-Logik.
Also das Viewmodel der Zelle ist zum Weinen primitiv:
2 Properties. Dabei ist IsWin nichtmal wirklich notwendig, das ist nur ein Gimmik, mit dessen Hilfe man die spielentscheidenden Zellen markieren kann, wenn einer gewonnen hat.
Das Mainmodel ist auch nicht wirklich viel:
die Public Properties BoardCells und Move, wobei Move ein Command ist, also etwas, mit dem das Xaml eine Methode im ViewModel aufrufen kann.
GetWinner() stelle ich hier nicht im Einzelnen vor, die Methode ist nämlich bischen länglicher, und nicht untricky.
Aber vlt. wollterja den BoardCell-Konstruktor angugge:
IsProvisional ist ein Feature meines MVVM-Frameworks, was mir die Möglichkeit gibt, zur Designzeit TestDaten zu generieren, die dann bei der Entwicklung des Xamls bereits im Designer angezeigt werden:
Der Code zeigt noch paar annere vlt. eigentümlich erscheindende Extension-Methods, etwa das IEnumerator(Of T).YieldCyclic, mit dem hier vom _Players-Enumerator immer der jeweils nächste Player abgerufen wird, und am Ende gehts von vorne los (zyklisch)
Oder die Integer.Times()-Extension, mit deren Hilfe das MainModel.BoardCells-Array per Einzeiler initialisiert wird (Code-Listing#2).
Jo, noch bischen Xaml?
Also es ist ein ItemsControl, und ItemsSource sind die BoardCells (9 an der Zahl). Als ItemsPanelTemplate ist ein UniformGrid gesetzt - das layoutet die Zellen so schön quadratisch.
Dann gibts noch das ItemTemplate, wo definiert wird, wie ein einzelnes Item (wir erinnern uns: die einzelne BoardCell) präsentiert wird: nämlich als Button, und Button-Content ist die rote Ellipse (ggfs. zur Auszeichnung als Winner-Cell) und der TextBlock, der den Player zeigt.
Das Command des Buttons ist an MainModel.Move gebunden, und übergibt als Parameter den DataContext des Buttons - was war das nochmal? - ach ja: die (geklickste) BoardCell.
Vlt. guggemer das Move-Command nochmal genauer an:
Das enthält ja eine anonyme Sub, die den Zug ausführt, und ausserdem auch eine anonyme Function, deren Boolean-Rückgabewert aussagt, ob auf diese Zelle ühaupt ein Zug ausgeführt werden kann.
Das fügt sich wunderbar in die Spiel-Logik von TicTacToe, denn auf diese Weise wird der Button automatisch disabled nachdem die Zelle per Klick belegt wurde.
Weiterführendes
Ein fortgeschritteneres TicTacToe ist auch in einer umfangreichen Codesammlung enthalten, die man sich hier downloaden kann: Microsoft All-In-One Code Framework
Genau anhand dieses TicTacToes entwickelt sich das geniale Tutorial WPF Styles and Control Templates - also die ziehen da echt vom Leder
Anlass dieses Tuts ist im Grunde dieser Thread, wo ebenfalls ein TicTacToe vorgestellt wird, jedoch ohne MVVM, sondern ausschließlich per CodeBehind gesteuert.
Das geeht auch, und ist auch nichtmal besonders komplex.
Man hat halt viele Buttons, sorgsam in Grid-Zellen angeordnet, und in den Klick-Events muß man einiges an den Buttons rumschrauben, dass die Züge angezeigt werden.
Ich denke aber, mit meiner "IsWin"-Ellipse, die die Gewinnzüge markiert, beginnen beim CodeBehind-Ansatz die Probleme. Weil ist glaub nicht lustig, per Code einen Button-Content zu generieren, der einen Kreis um einen Buchstaben anzeigt.
Richtlinie ist, Daten und Oberfläche zu trennen - nur über Databindings miteinander verbunden. Daraus folgt, es gibt auch keinen CodeBehind, wo irgendwelche Control-Events zu behandeln wären.
Das Datenmodell
Ist denkbar einfach: Es gibt 9 Zellen, und jede Zelle kann frei sein, oder bereits von einem Spieler besetzt.
Und dassis auch schon alles vom Datenmodell her.
Dazu kommt noch das abwechselnde Ziehen und die Auswertung nach jedem Zug, ob schon einer gewonnen hat, wer, oder unentschieden. Aber dassis nicht eiglich Datenmodell, sondern Business-Logik.
Also das Viewmodel der Zelle ist zum Weinen primitiv:
VB.NET-Quellcode
- Imports System.ComponentModel
- <DebuggerDisplay("{Player}")> _
- Public Class BoardCell : Inherits NotifyPropertyChanged
- Private _IsWin As Boolean = False
- Public Property IsWin() As Boolean
- Get
- Return _IsWin
- End Get
- Set(ByVal value As Boolean)
- ChangePropIfDifferent(value, _IsWin, "IsWin")
- End Set
- End Property
- Private _Player As String = ""
- Public Property Player() As String
- Get
- Return _Player
- End Get
- Set(ByVal value As String)
- ChangePropIfDifferent(value, _Player, "Player")
- End Set
- End Property
- End Class
Das Mainmodel ist auch nicht wirklich viel:
VB.NET-Quellcode
- Private _Players As IEnumerator(Of String) = {"O", "X"}.GetEnumeratorX
- Private Const _NoPlayer As String = ""
- Public Property BoardCells() As BoardCell() = 9.Times(Function() New BoardCell).ToArray
- Public Property Move As New RelayCommand(Of BoardCell)( _
- Sub(cell)
- 'actually a move is very simple: a player occupies a cell
- cell.Player = _Players.YieldCyclic
- EvaluateMove()
- End Sub, _
- Function(cell) cell.NotNull AndAlso cell.Player = _NoPlayer)
- Private Sub EvaluateMove()
- Dim winner = GetWinner()
- Select Case winner
- Case Nothing : Return 'continue game
- Case _NoPlayer
- Msg("no Winner")
- Case Else
- Msg("Player ", winner, " is Winner")
- End Select
- For Each cll In BoardCells
- cll.Player = _NoPlayer
- cll.IsWin = False
- Next
- End Sub
- ''' <summary>
- ''' returns the winner. If winner is Nothing game continues. If winner is NoPlayer game is undecided.
- ''' </summary>
- Private Function GetWinner() As String
- GetWinner = Nothing
- Dim dimension = 3
- '...
GetWinner() stelle ich hier nicht im Einzelnen vor, die Methode ist nämlich bischen länglicher, und nicht untricky.
Aber vlt. wollterja den BoardCell-Konstruktor angugge:
Der Code zeigt noch paar annere vlt. eigentümlich erscheindende Extension-Methods, etwa das IEnumerator(Of T).YieldCyclic, mit dem hier vom _Players-Enumerator immer der jeweils nächste Player abgerufen wird, und am Ende gehts von vorne los (zyklisch)
Oder die Integer.Times()-Extension, mit deren Hilfe das MainModel.BoardCells-Array per Einzeiler initialisiert wird (Code-Listing#2).
Jo, noch bischen Xaml?
XML-Quellcode
- <Window x:Class="MainWindow"
- xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
- xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
- Title="MainWindow" Height="350" Width="345"
- xmlns:my="clr-namespace:MyTicTacToe"
- xmlns:hlp="clr-namespace:System.Windows.Controls;assembly=HelpersSmallEd"
- DataContext="{StaticResource MainModel}">
- <Window.Resources>
- <BooleanToVisibilityConverter x:Key="BooleanToVisibility" />
- </Window.Resources>
- <Grid>
- <Rectangle Fill="Green" RadiusX="20" RadiusY="20"/>
- <Grid Margin="20">
- <ItemsControl ItemsSource="{Binding Path=BoardCells}" >
- <ItemsControl.ItemsPanel>
- <ItemsPanelTemplate>
- <UniformGrid/>
- </ItemsPanelTemplate>
- </ItemsControl.ItemsPanel>
- <ItemsControl.ItemTemplate>
- <DataTemplate >
- <Button Command="{Binding Path=Move, Source={StaticResource MainModel}}" CommandParameter="{Binding}"
- Margin="3" FontSize="20" >
- <Grid>
- <Ellipse Width="30" Height="30" Stroke="Red" StrokeThickness="3"
- Visibility="{Binding Path=IsWin, Converter={StaticResource BooleanToVisibility}}" />
- <TextBlock Text="{Binding Path=Player}" HorizontalAlignment="Center" VerticalAlignment="Center" />
- </Grid>
- </Button>
- </DataTemplate>
- </ItemsControl.ItemTemplate>
- </ItemsControl>
- </Grid>
- </Grid>
- </Window>
Dann gibts noch das ItemTemplate, wo definiert wird, wie ein einzelnes Item (wir erinnern uns: die einzelne BoardCell) präsentiert wird: nämlich als Button, und Button-Content ist die rote Ellipse (ggfs. zur Auszeichnung als Winner-Cell) und der TextBlock, der den Player zeigt.
Das Command des Buttons ist an MainModel.Move gebunden, und übergibt als Parameter den DataContext des Buttons - was war das nochmal? - ach ja: die (geklickste) BoardCell.
Vlt. guggemer das Move-Command nochmal genauer an:
Das fügt sich wunderbar in die Spiel-Logik von TicTacToe, denn auf diese Weise wird der Button automatisch disabled nachdem die Zelle per Klick belegt wurde.
Weiterführendes
Ein fortgeschritteneres TicTacToe ist auch in einer umfangreichen Codesammlung enthalten, die man sich hier downloaden kann: Microsoft All-In-One Code Framework
Genau anhand dieses TicTacToes entwickelt sich das geniale Tutorial WPF Styles and Control Templates - also die ziehen da echt vom Leder
Anlass dieses Tuts ist im Grunde dieser Thread, wo ebenfalls ein TicTacToe vorgestellt wird, jedoch ohne MVVM, sondern ausschließlich per CodeBehind gesteuert.
Das geeht auch, und ist auch nichtmal besonders komplex.
Man hat halt viele Buttons, sorgsam in Grid-Zellen angeordnet, und in den Klick-Events muß man einiges an den Buttons rumschrauben, dass die Züge angezeigt werden.
Ich denke aber, mit meiner "IsWin"-Ellipse, die die Gewinnzüge markiert, beginnen beim CodeBehind-Ansatz die Probleme. Weil ist glaub nicht lustig, per Code einen Button-Content zu generieren, der einen Kreis um einen Buchstaben anzeigt.
Dieser Beitrag wurde bereits 12 mal editiert, zuletzt von „ErfinderDesRades“ () aus folgendem Grund: Bug in GetWinner beim Erkennen von Unentschieden