简体   繁体   中英

How to bind a boolean array to the visibility properties of a grid of rectangles in WPF?

I'm trying to make a simple snake game in WPF using a grid of rectangles. I have the snake logic finished more or less, but I'm stumped on how to actually visualise my two dimensional array of booleans as pixels(rectangles) being visible or not. This question addresses how to treat visibility as a boolean, but how would I scale this up?

The code below describes the snake moving, I aim to run its Update method in a separate thread. It works by adding SnakeBod objects to a list, which have an age property. When the Age is over a certain number, the Update method stops treating it as existing. I aim to control the snake with KeyDown events.

The Question is: how do I bind the two dimensional VisibleBods array to a 64x64 grid in my MainWindow?

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;

namespace SnakeGame
{
    static class Snake
    {
        static public int SnakeLife;
        static public List<SnakeBod> SnakeBods;
        static SnakePos headPos;
        static public bool[,] VisibleBods = new bool[64, 64];
        internal static SnakePos HeadPos
        {
            get => headPos;
            set
            {
                if (headPos.X != value.X || headPos.Y != value.Y)
                {
                    if (headPos.X > 64 || headPos.X < 0 || headPos.Y > 64 || headPos.X < 0) SnakeLife = -1;
                    bool exists = false;
                    headPos = value;
                    foreach (SnakeBod curBod in SnakeBods)
                    {
                        if (curBod.BodPos.X == value.X && curBod.BodPos.Y == value.Y)
                        {
                            exists = true;
                            if (curBod.age > SnakeLife) curBod.age = 0;
                            else SnakeLife = -1;
                        }
                    }
                    if (!exists) SnakeBods.Add(new SnakeBod(value.X, value.Y));
                }
            }
        }


        static int snakeWait;
        static Direction SnakeDir;
        enum Direction : int
        {
            LEFT = 0,
            UP = 1,
            RIGHT = 2,
            DOWN = 3,
        }

        static Snake()
        {
            headPos = new SnakePos(32, 32);
            for (int i = 0; i < 64; i++)
            {
                for (int j = 0; j < 64; j++)
                {
                    VisibleBods[i, j] = false;
                }
            }
            SnakeLife = 10;
        }

        static void UpdateBod()
        {
            for (int i = 0; i < 64; i++)
            {
                for (int j = 0; j < 64; j++)
                {
                    VisibleBods[i, j] = false;
                }
            }
            foreach (SnakeBod curBod in SnakeBods)
            {
                if (curBod.age < SnakeLife) VisibleBods[curBod.BodPos.X, curBod.BodPos.Y] = true;
            }

        }

        static Thread UpdateThread;
        static void UpdateSnake()
        {
            Thread.CurrentThread.IsBackground = true;
            while (SnakeLife > 0)
            {
                switch (SnakeDir)
                {
                    case Direction.LEFT:
                        HeadPos = new SnakePos(HeadPos.X - 1, HeadPos.Y);
                        break;
                    case Direction.UP:
                        HeadPos = new SnakePos(HeadPos.X, HeadPos.Y - 1);
                        break;
                    case Direction.RIGHT:
                        HeadPos = new SnakePos(HeadPos.X + 1, HeadPos.Y);
                        break;
                    case Direction.DOWN:
                        HeadPos = new SnakePos(HeadPos.X, HeadPos.Y + 1);
                        break;
                }
                foreach (SnakeBod curBod in SnakeBods)
                {
                    curBod.age++;
                }
                Thread.Sleep(snakeWait);
            }
        }
    }

    class Crumb
    {

    }

    class SnakeBod
    {
        public SnakePos BodPos;
        public int age;
        public SnakeBod(int xIn, int yIn)
        {
            age = 0;
            BodPos.X = xIn;
            BodPos.Y = yIn;
        }
    }

    internal struct SnakePos
    {
        public int X;
        public int Y;

        public SnakePos(int xIn, int yIn)
        {
            X = xIn;
            Y = yIn;
        }
    }
}

EDIT: I am also aware of this codeproject , but I wanted to try this in my own way. Usually I do Bindings mostly in XAML, and since this is large scale I figured this has to be done in the Codebehind. One of the biggest hurdles is how to bind to an array element inside the ViewModel, as for normal fields I just call OnPropertyChanged() when set.

The simplest solution I see is to avoid any of [,] arrays at all:

public MyType[] RegularArray { get { return ReduceArrayDimensionality(TwoDimensionalArray); } }

You can proceed on nasty .Linq extensions to achieve that in a more handsome manner, I believe .SelectMany(array => array).ToArray() will do.

Then your binding becomes a regular one. The only thing you have to care about is appropriate BooleanToWahteverYouLikeConverter : IValueConverter - this guy is a part of shipped by the WPF binding system. You can find lots of tutorials and examples. Your converter has to be smart enough to figure out which exactly boolean to be considered this time. Use ConverterParameter to pass necessary index.

That's it.

KISS.

In order to update the UI on a visibility change of a single cell in a grid of cells, you should implement the standard MVVM approach, ie bind an ItemsControl to a collection of cell items, with an item class that implements INotifyPropertyChanged:

public class Cell : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

    private bool isVisible;
    public bool IsVisible
    {
        get { return isVisible; }
        set
        {
            if (isVisible != value)
            {
                isVisible = value;
                PropertyChanged?.Invoke(this,
                    new PropertyChangedEventArgs(nameof(IsVisible)));
            }
        }
    }
}

public class ViewModel
{
    public List<Cell> Cells { get; } =
        Enumerable.Range(0, 4096).Select(i => new Cell()).ToList();
}

The ItemsControl would use a UniformGrid to show the cell grid:

<Window.Resources>
    <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
    <ItemsControl ItemsSource="{Binding Cells}">
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <UniformGrid Columns="64"/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
        <ItemsControl.ItemTemplate>
            <DataTemplate>
                <Rectangle Width="10" Height="10" Fill="Green"
                           Visibility="{Binding IsVisible,
                               Converter={StaticResource BooleanToVisibilityConverter}}"/>
            </DataTemplate>
        </ItemsControl.ItemTemplate>
    </ItemsControl>
</Grid>

To change the visibility of a cell:

((ViewModel)DataContext).Cells[index].IsVisible = true;

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM