为数据网格视图中的每个单元格制作一个列表

Making a list for each cell in a datagridview

我有一个 9x9 DataGridView,每个单元格只能包含 1-9 之间的数字(如数独)。我希望为包含所有 9 个数字的每个单元格提供某种列表。随着游戏的进行,我将需要开始从不同单元格的“列表”中删除数字。我能得到一些关于在这种情况下应该使用什么的建议吗?有没有办法避免制作 81 个不同的列表并手动将所有 9 个数字插入所有 81 个列表?

for (int i = 0; i < 9; i++)
{
  for (int j = 0; j < 9; j++)
  {
    string listname;
    listname = "cell" + i.ToString() + j.ToString();
    List<int> listname = new List<int>();
  }
}

我在想这样的事情会有助于制作 81 个不同的列表,因为列表名称将是 cell(rownum)(columnnum) 但这显然行不通。

为了让您了解我的意思,请自己创建一个枚举,如下所示:

[Flags]
public enum SudokuValue
{
    None = 0,
    One = 1,
    Two = 2,
    Three = 4,
    Four = 8,
    Five = 16,
    Six = 32,
    Seven = 64,
    Eight = 128,
    Nine = 256
}

Flags 属性很重要,我们稍后会看到。您现在可以在 class 中利用此枚举,如下所示:

public class SudokuHelper
{
    private SudokuValue[,] gridValues;
    private bool[,] gridFills;

    public SudokuHelper()
    {
        gridValues = new SudokuValue[9, 9];
        gridFills = new bool[9, 9];
        for (int i = 0; i < 9; i++)
        {
            for (int j = 0; j < 9; j++)
            {
                gridValues[i, j] = (SudokuValue)511;
                gridFills[i, j] = false;
            }
        }
    }
    public void UpdateGrid(int row, int col, int val)
    {
        gridFills[row, col] = true;
        SudokuValue sudoku = (SudokuValue)(Math.Pow(2, val - 1));
        gridValues[row, col] = sudoku;
        //Update same col
        for (int i = 0; i < 9; i++)
        {
            if (!gridFills[i, col] &&  gridValues[i, col].HasFlag(sudoku))
            {
                gridValues[i, col] -= sudoku;
            }
        }
        //Update same col
        for (int i = 0; i < 9; i++)
        {
            if (!gridFills[row, i] && gridValues[row, i].HasFlag(sudoku))
            {
                gridValues[row, i] -= sudoku;
            }
        }
        //Update same block
        int startBlockRow;
        int startBlockCol;
        if (row < 3)
        {
            startBlockRow = 0;
        }
        else if (row < 6)
        {
            startBlockRow = 3;
        }
        else
        {
            startBlockRow = 6;
        }
        if (col < 3)
        {
            startBlockCol = 0;
        }
        else if (col < 6)
        {
            startBlockCol = 3;
        }
        else
        {
            startBlockCol = 6;
        }
        for (int i = startBlockRow; i < startBlockRow + 3; i++)
        {
            for (int j = startBlockCol; j < startBlockCol + 3; j++)
            {
                if (!gridFills[i, j] && gridValues[i,j].HasFlag(sudoku))
                {
                    gridValues[i, j] -= sudoku;
                }
            }
        }
    }
    public string GetStringRepresentation(int row, int col)
    {
        var sB = new StringBuilder();
        if (gridValues[row, col].HasFlag(SudokuValue.One))
        {
            sB.Append(",1");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Two))
        {
            sB.Append(",2");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Three))
        {
            sB.Append(",3");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Four))
        {
            sB.Append(",4");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Five))
        {
            sB.Append(",5");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Six))
        {
            sB.Append(",6");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Seven))
        {
            sB.Append(",7");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Eight))
        {
            sB.Append(",8");
        }
        if (gridValues[row, col].HasFlag(SudokuValue.Nine))
        {
            sB.Append(",9");
        }
        //remove leading comma
        return sB.ToString().Substring(1);
    }
}

构造函数创建一个 SudokuValues 的初始二维数组,并用表示所有可能值 1 ... 9 (511) 的初始值填充它。请注意,虽然枚举中未列出 511,但我们仍然可以将其转换为 SudokuValue。这是因为默认情况下所有枚举都是整数。它还创建并填充一个二维布尔数组,以表示已为其提供值的那些单元格。

然后,如果在其中一个单元格中输入了值,它会提供一种方法来更新网格中的值。此方法采用三个参数,即更新单元格的行和列,以及作为整数输入到单元格中的值(必须是 1 .. 9)。首先,它更新网格中的单元格本身(有效地擦除所有其他可能性),并设置布尔标志。然后它继续删除输入的值作为同一列、同一行和同一 3x3 块中其他单元格中的可能性。此处它在删除值之前检查之前的值是否仍然存在(使用 HasFlags 方法 - 可用,因为我们在上面设置了 Flags 属性),以避免单元格被输入两次。这里有一个小技巧:输入的值 (1 .. 9) 可以转换为其等效的枚举值,方法是将其减 1,然后取 2 的乘方。

最后,助手提供了任何给定单元格所包含内容的文本表示(如果设置了单个值,或者剩余的可能性)。

为了测试这一点,我们可以这样做(我使用 WinForms 应用程序进行测试,因此 MessageBox.Show()):

var sH = new SudokuHelper();
for (int i = 0; i < 9; i++)
{
    var sB = new StringBuilder();
    for (int j = 0; j < 9; j++)
    {
        sB.Append(" ; " + sH.GetStringRepresentation(i, j));
    }
    MessageBox.Show($"Row {i}: " + sB.ToString().Substring(3));
}

sH.UpdateGrid(4, 7, 9);
for (int i = 0; i < 9; i++)
{
    var sB = new StringBuilder();
    for (int j = 0; j < 9; j++)
    {
        sB.Append(" ; " + sH.GetStringRepresentation(i, j));
    }
    MessageBox.Show($"Row {i}: " + sB.ToString().Substring(3));
}

如果是我构建这个应用程序,我会使用一个 9x9 的文本框网格(而不是 DataGridView),所有文本框都带有一个 KeyDown/KeyPress 事件,如果其中一个事件调用 SudokuHelper 来更新网格按键 1 .. 9 被按下。但也许您出于其他原因需要 DataGridView?

将列设为 DataGridViewComboBoxColumn。这样,这些列中的每个单元格都将是 DataGridViewcomboBoxCell。

属性 DataGridViewComboBoxColumn.DataSource 和 DataGridViewComboBoxCell.DataSource 包含所有可能的值。最初将列数据源设置为所有初始值,在创建单元格后,您可以限制单元格数据源中的值。

class SudokuRow
{
    public int C0 {get; set;}
    public int C1 {get; set;}
    public int C2 {get; set;}

    public int C3 {get; set;}
    public int C4 {get; set;}
    public int C5 {get; set;}

    public int C6 {get; set;}
    public int C7 {get; set;}
    public int C8 {get; set;}
}

使用visual studio设计器在虚拟项目中添加一些列以查看哪些列 应填写属性

DataGridView dgvSudoku = new DataGridView()
for (int i=0; i<9; ++i)
{
    DataGridViewComboBoxColumn column = new DataGridViewComboBoxColumn();
    column.Name = "column" + i.ToString();
    column.DataPropertyName = "C" + i.ToString();
    ...
    column.DataSource = enumerable.Range(1,9).ToList();  // allowed values
    dgvSudoko.Columns.Add(column);
}

如果要加空格,那就不要加数字,加字符0..9和空格。 sudokuRow 包含 char 而不是 int。

如果您使用DataBinding 来显示项目,并且您只想单向显示,您可以使用一个List 来分配给DataSource。但是,如果您想读取操作员输入,则必须将数据放在 BindingList 中。

private BindingList<SudokuRow> SudokuData
{
    get => (BindingList<SudokuRow>)this.dgvSudoku.DataSource;
    set => this.dgvSudokuy.DataSource = value;
}

现在初始化您的数据网格视图:

List<SudokuData> initialSudokuData = ...
this.SudokuData = initialSudokuData;

在一些运算符输入之后你想限制一个单元格的可能性:

LimitPossibilites(DataGridViewComboBoxCell cell, IEnumerable<int> allowedValues)
{
    // TODO: what to do if current value is not allowed?

    // limit the combobox values
    cell.DataSource = new BindingList<int>(allowedValues.ToList());
}

或者,如果您不想用新的 BindingList 替换 BindingList,请考虑使用 Bindling<T> 属性添加/删除项目。但是,这似乎需要更多工作。 }