Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Data binding to a nested property with possibly null parent

I want to perform data-binding to Room.ToNorth.ImageName while ToNorth may be null.

I'm trying to write my own text adventure and have images next to each of the directions a player can go (ie. open/closed door, pathway). When there isn't a room in a given direction I have the controls bound to show a 'no exit' image, this works great so long the player starting location has an exit in all 4 directions, however when one is null I get the following exception

System.ArgumentNullException: 'Value cannot be null. Parameter name: component'

This is isn't an issue on a new game but I'm making randomly generated dungeons so it can't be guaranteed on a load game. I realise I could probably fudge it by creating a safe space while the bindings are set then moving the player to where they need to be but is there a proper way to handle this?

The closest answer I've found is this question, but I'm not sure I can apply it here due to how it's bound.

private GameSession _CurrentGame;
private BindingSource _BindToPlayer = new BindingSource();

private void BtnNewGame_Click(object sender, EventArgs e)
{
    _CurrentGame = new GameSession();

    _BindToPlayer.DataSource = _CurrentGame.CurrentPlayer;

    PicBoxNorth.DataBindings.Add("ImageLocation", _BindToPlayer, 
        "CurrentRoom.ToNorth.ImageName", true, DataSourceUpdateMode.OnPropertyChanged, 
        "Images\\Wall.png");
}

The ToNorth property just gets the object from an an array of exits, but telling it to return an empty exit if it's null (like suggested in the above link) would be problematic in that the exit wouldn't have any rooms to connect to and thus fail to initialise likely throwing an exception itself along the way. To explain better, I have my rooms set up as follows

public Class Room
{
   public int ID { get; set; }
    public String Name { get; set; }
    public String Description { get; set; }
    public String FarDescription { get; set; }
    public CoOrds Co_Ords { get; set; }

    public Boolean UniqueRoom { get; set; }

    public Exit[] ExitArr { get; set; }

    public Exit ToNorth => ExitArr[0];
    public Exit ToSouth => ExitArr[1];
    public Exit ToWest => ExitArr[2];
    public Exit ToEast => ExitArr[3];
    public Exit ToUp => ExitArr[4];
    public Exit ToDown => ExitArr[5];

    public Exit ToIn => ExitArr[6];
    public Exit ToOut => ExitArr[7];

    public Room(int id, String name, String desc, String fardesc,bool unique = false)
    {
        ID = id;
        Name = name;
        Description = desc;
        FarDescription = fardesc;
        Co_Ords = new CoOrds();
        ExitArr = new Exit[8];

        UniqueRoom = unique;

    }

And my exits are set up like so

public class Exit
{
    public String Description {get;}         

    public Room RoomA { get; set; }
    public Room RoomB { get; set; }

    public Boolean CanExitA { get; set; } = true;
    public Boolean CanExitB { get; set; } = true;

    public Room NextRoom
    {
        get
        {

            if (RoomA.PlayerHere && CanExitA)
            { return RoomB; }
            else if (RoomB.PlayerHere && CanExitB)
            { return RoomA; }
            else
                return null;
        }
    }

    public String ImageName
    {
        get
        {
            string imagePath = "Images\\";
            if (DoorHere != null)
            {
                return imagePath + DoorHere.ImageName;
            }
            else
                return imagePath + "Pathway.png";
        }

    }

    public Door DoorHere { get; set; }

    public Exit(Room roomA, int Adir, Room roomB, int Bdir, Door door = null)
    {
        RoomA = roomA;
        RoomA.ExitArr[Adir] = this;
        RoomB = roomB;
        RoomB.ExitArr[Bdir] = this;
        DoorHere = door;

    }

Door is just an unnamed object with a few bools for IsOpen, IsLocked etc. and shouldn't be needed if you want to test this but exits are created anonymously and add themselves to the Exit array in both connecting rooms, hence creating empty ones would fail.

Any suggestions would be greatly appreciated.

like image 646
Rowie Avatar asked Feb 16 '18 21:02

Rowie


1 Answers

As an option, you can create a property to do null-checking and get/set second level property using that property.

For example in your code, you can create ToNorthImageName property to get/set the value of ToNorth.ImageNamein Room class:

public string ToNorthImageName
{
    get { return ToNorth?.ImageName }
    //set { if (ToNorth != null) ToNorth.ImageName = value; }
}

The property setter is optional, you can remove it if you just want to read the image name.

Then setup data-binding to that property:

PicBoxNorth.DataBindings.Add("ImageLocation", _BindToPlayer, "ToNorthImageName",
    true, DataSourceUpdateMode.OnPropertyChanged);
like image 176
Reza Aghaei Avatar answered Nov 04 '22 03:11

Reza Aghaei