【问题标题】:How to bind a boolean array to the visibility properties of a grid of rectangles in WPF?如何将布尔数组绑定到 WPF 中矩形网格的可见性属性?
【发布时间】:2019-02-09 17:15:55
【问题描述】:

我正在尝试使用矩形网格在 WPF 中制作一个简单的蛇游戏。我已经或多或少地完成了蛇逻辑,但我很难理解如何将我的二维布尔数组实际可视化为像素(矩形)是否可见。 This question addresses 如何将可见性视为布尔值,但我将如何扩大它?

下面的代码描述了蛇的移动,我的目标是在一个单独的线程中运行它的 Update 方法。它通过将 SnakeBod 对象添加到具有年龄属性的列表来工作。当 Age 超过某个数字时,Update 方法停止将其视为存在。我的目标是通过 KeyDown 事件来控制蛇。

问题是:如何将二维 VisibleBods 数组绑定到 MainWindow 中的 64x64 网格?

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;
        }
    }
}

编辑:我也知道this codeproject,但我想以自己的方式尝试。通常我主要在 XAML 中进行绑定,并且由于这是大规模的,我认为这必须在 Codebehind 中完成。最大的障碍之一是如何绑定到 ViewModel 中的数组元素,对于普通字段,我只在设置时调用 OnPropertyChanged()。

【问题讨论】:

    标签: c# wpf xaml binding


    【解决方案1】:

    为了在单元格网格中单个单元格的可见性更改上更新 UI,您应该实现标准 MVVM 方法,即将 ItemsControl 绑定到单元格项目的集合,并使用实现 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();
    }
    

    ItemsControl 将使用 UniformGrid 来显示单元格网格:

    <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>
    

    更改单元格的可见性:

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

    【讨论】:

    • 这很好用,但我一直试图弄清楚视图模型中的位是如何工作的。请你走一遍好吗?
    • 你指的是哪一点?所有这些方法都有在线文档。
    • public List&lt;Cell&gt; Cells { get; } = Enumerable.Range(0, 4096).Select(i =&gt; new Cell()).ToList(); 您能否解释一下 lambda 运算符在这种情况下如何工作以创建正确的输入变量?我查看了 => 运算符的文档,但无法弄清楚。
    • 您的意思是Select 方法?它将一个整数(来自Range 调用的虚拟对象)转换为一个新的 Cell,即它从 4096 个整数创建 4096 个 Cell 实例,并将其转换为 List&lt;Cell&gt; by ToList
    • i =&gt; new Cell() 等同于i =&gt; { return new Cell(); }
    【解决方案2】:

    我看到的最简单的解决方案是完全避免使用任何 [,] 数组:

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

    您可以继续使用讨厌的 .Linq 扩展来以更漂亮的方式实现这一点,我相信 .SelectMany(array =&gt; array).ToArray() 会做到。

    然后您的绑定就变成了常规绑定。您唯一需要关心的是适当的BooleanToWahteverYouLikeConverter : IValueConverter - 这家伙是 WPF 绑定系统提供的一部分。您可以找到很多教程和示例。您的转换器必须足够聪明,才能确定这次要考虑哪个 boolean。使用ConverterParameter 传递必要的索引。

    就是这样。

    亲吻。

    【讨论】:

      猜你喜欢
      • 2016-06-11
      • 2012-05-23
      • 1970-01-01
      • 1970-01-01
      • 2011-06-14
      • 2010-12-01
      • 2014-10-17
      • 1970-01-01
      • 2018-10-13
      相关资源
      最近更新 更多