다음을 통해 공유


Understand the Reversi app structure

[This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation]

The Reversi sample uses a layered structure known as the Model-View-ViewModel (MVVM) pattern. This structure separates UI from non-UI code, which can simplify debugging, testing, and future development. This topic describes each layer in the sample, the parts in each layer, and how all the parts work together.

The Reversi sample differs from simpler XAML samples that put most of their non-XAML code in code-behind files. Reversi adds code to code-behind files in a few cases, but moves most of its non-XAML code to other layers and uses data binding to connect those layers to the UI.

The Reversi sample also differs from more complex XAML samples that use more advanced techniques, including separate MVVM frameworks. Such frameworks are useful for increasingly complex apps, but the Reversi sample shows that there are benefits to even a simple layered structure built using standard XAML.

For a short intro to MVVM, see Use the Model-View-ViewModel (MVVM) pattern.

This topic assumes that you already know XAML and C#. If you are not familiar with these technologies, see Create your first Windows Store app using C# or Visual Basic. If you understand C++, you can also benefit from the C++ version of the Reversi game engine, although this is not required to understand the rest of the sample.

For a general introduction to the sample, see Reversi, a Windows Store game in XAML, C#, and C++. To see how specific features are used in the sample, see Learn how the Reversi sample uses Windows Store app features. To learn how the original C# game engine was ported to C++, see Learn about the Reversi C++ game engine.

Download the Reversi sample app or browse the source code.

App layers

The Reversi app is divided into the following layers:

  • The model layer includes the core game code that represents the game rules, game state, and artificial intelligence (AI), and is completely independent of the UI. The majority of the model code is in the ReversiGameComponentCS project for the C# version and the ReversiGameComponentCPP project for the C++ version of the game engine.
  • The view layer is implemented by the Views folder in the Reversi project. This code provides the game UI, which is defined in XAML with extensive use of data binding.
  • The view model layer is implemented by the ViewModels folder in the Reversi project. This code provides UI binding targets and handles all user interactions, but is otherwise completely independent of the UI.

The model layer

The Reversi sample includes two different implementations of the model layer:

  • The original C# version is in the ReversiGameComponentCS project.
  • The performance-enhanced C++ version is in the ReversiGameComponentCPP project.

There is also a ReversiGameModel project that contains interfaces and support classes used to decouple the app and unit tests from the game component implementation. The Reversi and unit test projects have dependencies only on these interfaces and support classes, and do not have any direct dependencies on the game component implementations.

The ReversiGameComponentCS project implements the game model interface directly. The ReversiGameComponentCPP project, however, cannot implement .NET interfaces. Therefore, the Reversi project includes a CLRSerializableCPPGame class in the Models folder. This class implements the game interface and provides a .NET wrapper around the C++ component. In this way, the unit tests and the main app can consume either the C# or the C++ component, including serializing the game objects to disk when the app is suspended.

Of course, the app is not designed to use both game engine implementations at the same time. Instead, it always creates game instances using the GameFactory class in the Models folder. This class creates and returns either a C# or C++ component instance depending on whether the CSHARP compiler constant is defined.

Both versions of the model layer are implemented as Windows Runtime Component projects. Each version of the component includes the core game logic: everything needed to represent the game rules, the changing state of an actual game in progress, and the game AI.

Putting the core game logic in a separate Windows Runtime Component project is useful for several reasons besides enabling the creation of a separate version written in C++. For example, you can build a game component project and then reference it as a library assembly in a new app solution. That way, you can replace the UI (perhaps as a learning exercise) without changing anything else. You can even use the component in a JavaScript-based app.

Another benefit of using a separate layer is to keep the core game code as simple as possible. Creating a complete board-game app is much harder than just writing the minimum amount of code possible for the rules of a board game. However, the code for even simple game rules can still be quite complex, so keeping the UI out of this code makes it much easier to write and test than if it were not kept separate.

For more info, see Using a Windows Runtime Component in Reversi feature scenarios.

The view layer

The view layer is in the Reversi project's Views folder and includes the code for the major visual parts of the app, including each page, the settings flyouts, and the game controls.

Most of these parts have both XAML and code-behind files, but in some cases, the code-behind is just the boilerplate code generated by the Microsoft Visual Studio app template. Most of the code that would normally be in the code-behind files is in the model and view-model layers instead.

The XAML files contain most of the view code. These files include major use of the XAML Binding markup extension to define data bindings between the XAML and the view models.

Some views also use code-behind to perform tasks that are more difficult to implement in XAML and are unsuited to the view model and model layers. These include:

  • Special setup for the data context or data bindings of views.
  • Calling UI methods in the XAML API, such as navigation and animation methods.
  • Handling UI events to call view-model methods when there is no convenient XAML alternative.

As your app becomes more complex, you may find it useful to refactor these tasks into more components or layers, or to use one of the many MVVM frameworks that provide related services.

For more info about how views are connected to view models, see Data binding in Reversi feature scenarios.

Pages

Reversi includes three pages:

  • StartPage displays the game and help options shown when you first start the app.
  • GamePage displays a PlayerStatus control and a Board control full of BoardSpace controls.
  • HelpPage displays the game instructions, which include a PlayerStatus and Board for demonstration and illustration purposes.

These pages were created using the Basic Page template in the Project -> Add New Item dialog box of Visual Studio.

User controls and custom controls

The main complexity in the UI is in the controls that represent the game board, scoreboard, and clock. These include both user controls and custom controls. The UI for these controls, including some animated state transitions, is defined in XAML. The controls manage the transitions by using custom dependency properties that are data-bound to view-model properties. For more info, see Data binding and Custom dependency properties in Reversi feature scenarios.

  • Board represents the game board. This is a subclass of UserControl, and displays a Grid panel containing BoardSpace controls and the various game messages (paused game, pass move, and game over). Board is a user control because user controls make it easy to define a simple UI part and its functionality in a XAML and code-behind pair. At some point in future development, it might be useful to refactor this into a custom control for greater reusability.
  • BoardSpace represents each space on the game board. This is a custom control that derives from the Button class, taking advantage of the Command property, but replacing the default UI. For simpler UI replacements, you can use a Button and just set the Style property, as demonstrated by the text buttons on the start page. BoardSpace is a custom control because it uses a custom dependency property to drive its animated UI transitions. Because it is a custom control, it is defined in the BoardSpace.cs file and the Reversi/Themes/Generic.xaml file.
  • PlayerStatus represents the scoreboard and clock. This is a custom control that derives from the Control class and is defined in PlayerStatus.cs and the Generic.xaml file. This is a custom control because it incorporates BoardSpace controls, and requires greater control over the visual state initialization than a UserControl can provide. Specifically, it uses an OnApplyTemplate method override to initialize the BoardSpace to one of two states before the animated transitions are automatically triggered by the data-binding initialization. This method is not available on the UserControl class, where the earliest chance to modify the control after data binding is in a Loaded event handler—too late to prevent the default animated transitions.

At run time, the Reversi controls look like this.

Settings flyouts

Reversi includes two SettingsFlyout controls, which are in the View/Settings folder:

  • DisplaySettings includes options to show or hide the clock and the indicators that show the valid moves and the effects of the last move.
  • NewGameSettings includes options to change the board size, switch between human and computer players, and change the AI search depth.

For more info, see Settings flyouts in Reversi feature scenarios.

Designer data

The Views/SampleData folder includes XAML definitions of GameViewModel and SettingsViewModel instances. This data is referenced by various XAML UI files in the Views folder in order to display a meaningful view in the designer. It is not used at run time.

The SettingsViewModel designer data also makes use of the GameDesignerStub class in the Models folder. The designer cannot easily use the GameFactory class to create an instance of the C# or C++ game component, so the GameDesignerStub class provides a stripped-down IGame implementation that the designer can instantiate from the XAML data.

The view-model layer

The view-model layer is in the Reversi project's ViewModels folder and includes the non-visual UI code and code that interacts with the model layer.

As mentioned earlier, the views use the XAML Binding markup extension to bind to view-model properties. The view models, however, have no direct references to the views. Instead, they provide properties and commands for the view to bind to.

View-model properties can provide:

  • Direct exposure of model properties.
  • Reformatted or type-converted model properties.
  • Values calculated from multiple model properties or method-call return values.
  • Commands that convert user input into model method calls.
  • Values that represent UI or app states that aren't relevant to the model.

For example, the Reversi UI needs to render the state of each space on the board, which is stored in the Game model object. However, the UI also needs to display indicators about which moves are legal and which spaces were affected by the most recent move. The Game class includes this info in the form of move-validation methods and the return values of the move methods. However, the view model is necessary to bring this information together into a single property (the this string indexer property of the GameViewModel class) that the view can bind to.

Reversi includes 3 view models:

  • GameViewModel contains all interactions with the Game model class and includes a reference to a ClockViewModel.
  • SettingsViewModel contains properties that store the current app settings and includes a reference to the GameViewModel that represents the current game.
  • ClockViewModel contains all clock functionality, including display formatting and commands for pausing and starting the clock.

The ViewModels folder also includes:

  • ISettingsViewModel, IGameViewModel, and IClockViewModel interface definitions, which decouple the view model unit tests from the view model implementations. For more info, see the Unit tests section.
  • Player and BoardSpaceState enumerations, which provide values for some view-model properties.

For more info, see data binding and content sharing in Reversi feature scenarios.

Infrastructure and common code

The Reversi project also includes some infrastructure code to make everything work together, and some reusable code that supplements the platform libraries.

The main infrastructure files are the App.xaml file and its code-behind, and the Package.appxmanifest file. For more info about these files, see C#, VB, and C++ project templates for Windows Store apps and Using the Manifest Designer.

The Common folder contains files that are suitable for reuse in other projects. Most of these files are generated by the app and item templates in Visual Studio. However, Reversi adds a few files and makes a few small changes to the template-generated files.

These files support common data binding scenarios:

  • BindableBase provides the base class for each view model. This class provides reusable change-notification infrastructure code that works with the data-binding system. For more info, see Data binding in Reversi feature scenarios.
  • DelegateCommand and DelegateCommandBase provide ICommand implementations that lets you define command functionality inline in a bindable property implementation. This implementation is copied from Prism for the Windows Runtime, and is needed because the template-generated RelayCommand class does not support async command methods.
  • NullStateToVisibilityConverter provides a standard way for bindings to translate non-null values to Visible values and null values to Collapsed values. You can use this converter instead of having to create special view-model properties to perform this translation.
  • BooleanToVisibilityConverter performs a similar translation between Boolean and Visibility values. This converter is generated by Visual Studio templates, but Reversi uses a modified version of it to accept a ConverterParameter value that reverses the translation.
  • ObservableDictionary provides a default view model class for template-generated pages.

These files support page layout and navigation, including suspend and resume:

  • RichTextColumns provides a panel that renders rich text content in one or more columns as needed by the current screen size, resolution, and view state. Reversi uses this class in the HelpPage.
  • SuspensionManager is a template-generated class for saving and restoring temporary app state when your app is suspended or terminated. Reversi uses a modified version of this class to enable it to serialize the interconnected view models.
  • NavigationHelper is used by template-generated for performing common tasks such as navigating backward and forward and loading or saving page data.
  • RelayCommand provides a simple ICommand implementation used by NavigationHelper and the back buttons of template-generated pages. This class is similar to DelegateCommand, but does not support async command methods.

Additionally, the Toast class provides a simple toast notification service.

Unit tests

The Tests folder includes two projects:

  • The ReversiGameComponentTests project includes unit tests for the Reversi game component projects. These tests work with both the C# and C++ versions of the game component, and will test whichever version is configured for use in the Models\GameFactory.cs file in the Reversi project.
  • The ReversiViewModelTests project includes unit tests for the Reversi project's view-model classes.

The tests in the ReversiGameComponentTests project were created during the development of the core game logic to check various design assumptions as the code grew in complexity. The complexity made the code somewhat error prone, and these unit tests helped to identify and diagnose bugs and to gradually improve the code design.

The tests are split into 4 classes:

  • BasicFunctionalityTests includes tests for the Board, Moves, and Move APIs of the Game class and features such as undo and redo.
  • ValidationTests includes tests for the code that checks whether a particular move is legal.
  • AiTests includes tests that verify the behavior of the game AI, which is really just code that evaluates the quality of different moves.
  • SerializationTests includes tests for the code that represents game moves and states as strings. This code is useful in the other tests.

These tests provide a small amount of coverage, but were enough to provide significant value during development.

The tests in the ReversiViewModelTests project were created to fill out the unit testing coverage, and to show how to handle the extra complexities of view model testing. View models are more complicated to test because they have dependencies on model classes and on one-another. To ensure that the unit tests are testing just the specific view models, all external dependencies are fulfilled using mock objects that provide minimal implementations of the required interfaces.

Like the other benefits of MVVM, unit testing does not have to be an all-or-nothing matter. Even small amounts of code separation and unit testing can provide great value. In fact, using small amounts at first will help you better understand what works and what doesn't work. That way, you can effectively manage the growing complexity of an app during development, and avoid adding extra complexity before you need it.

For more info, see Creating and running unit tests on a Windows Store app.

Reversi sample app

Reversi, a Windows Store game in XAML, C#, and C++

Use the Model-View-ViewModel (MVVM) pattern

Learn how the Reversi sample uses Windows Store app features

Understand the Reversi app structure

Learn about the Reversi C++ game engine

Create your first Windows Store app using C# or Visual Basic

Roadmap for Windows Runtime apps using C# or Visual Basic

Data binding