Search code examples
c#wpfmvvmdata-bindingarchitecture

How to deal with situations when a view depends on multiple models in MVVM context?


I am working on a battleship game using WPF. I am having a Coordinate model and the ViewModel which I bind with the Grid object. This ViewModel has the property of list of Coordinate objects and describes each as a Border with the Label.

The problem is that the data of the Coordinate class is not enough for my Border view style. It has only the Row and the Column properties but I want to also know whether it contains Ship and also to know if that Ship is Wounded or Sunk. Depending on these values I want to change the Background colour of my Border object.


Scenario 1

If I bind my Border with the Ship then I will lose the binding with the Coordinate object (which is important and my Border still depends on it).

Scenario 2

If I redefine my ViewModel to become 1 to 1 binding for a Border (number of instances of my ViewModel to match a number of instances of Border objects) then it feels a little dirty and not elegant enough. But in this case I would be able to define all the needed properties which my Border use and will be bound to.

Scenario 3

If I create an additional class which would be the hybrid of Coordinate and Ship and that class will be used in a list as a ViewModel property and that list will be bound with my Border, then I see two issues:

  1. The properties of this class will depend on each other. Let's say there will be a property HasShip and then it has a property IsShipSunk. But the latter one doesn't make sense when the first one has the value of false.
  2. Somehow I need to build a way for my Coordinate and Ship class to communicate with that additional class so that values would be in sync. Feels like an extra work.

Scenario 4

If I change my Coordinate model class to contain all the required properties then we are having the same issue I have described in my previous "if" (first point).

Scenario 5

If I don't use bindings at all, then I don't know what would be the best practice (in this case) to implement the linking between my Border view and Coordinate model. Also I will need to get the Battlefield object where that Coordinate belongs to to be able to fetch all the Ship objects from there. And this approach feels like "Why you go for WPF if you are not using MVVM?". I have chosen WPF because I love C# and also because WPF has a great UI Controls architecture.

I am pretty much aware that WPF and MVVM might not be suitable for games like this one and it is designed for different purposes. But I still would like find a nice neat solution in case it exists.


So, in a nutshell: how to build the following:

  1. The user clicks on the Border (looks like a cell)
  2. The corresponding Coordinate object is fetched
  3. The corresponding Battlefield where that Coordinate belongs to is fetched
  4. The iteration of Ship objects is performed to check if any of them contain that coordinate
  5. Depending on the result change the colour of my Border Background (Blue - when missed, Orange - when hit, Black - when the Ship is sunk (please, note, I want all the related Border objects to change the colour from Orange to Black when the Ship has sunk))

Thanks a lot in advance.


Solution

  • If you chose that particular representation (list of ships) for your game state for a good reason, you should not change it for the UI presentation. What you do in the View should theoretically not influence you to change the Model.

    Number 3 is the solution most fitting to MVVM development. You create a real View Model wrapping your actual model.

    Please do remember that you don't need to keep two states, your properties can depend on other properties and objects. You can have a reference to your ship list in the Field and generate its properties based on what it contatins, like:

    public class Field
    {
        GameStateModel GameState { get; set; }
    
        public int Row { get; set; }
        public int Col { get; set; }
    
        private Ship ShipOnField
        {
            get
            {
                return GameState.Ships.Where(s => s.Row == Row && s.Col == Col).FirstOrDefault();
            }
        }
    
        public bool HasShip
        {
            get
            {
                return ShipOnField != null;
            }
        }
    
        public bool IsShipSunk
        {
            get
            {
                return ShipOnField != null && ShipOnField.Ded;
            }
        }
    }
    

    There are two issues with this solution though. One is performance. Other one is an issue of updating the view on changes in the model. You will either have to update everything on each change, or chain the changes in model to your ViewModel updates.