如何在 table 的一列单元格中输入字母,而在另一列单元格中仅输入数字?

How to enter letters in one column cell of the table and only numbers in the other column cell?

我想在 C# 中创建一个 table。 table 包含两列 header。一列 header 是产品,另一列 header 是产品的价格。 我想通过单击 table 中的单元格来输入数据。我只想在产品价格列的单元格中输入数字。 有谁知道如何在产品价格下方的单元格中只允许输入数字?

Picture of the table

我的想法: 我的想法是,如果我在产品价格栏的单元格中输入一个字符串,我应该使用“replace”将单元格清空。也许可以使用正则表达式,虽然我不知道如何。我的另一个想法是,在我离开单元格后,如果单元格中有字符串,我必须清空单元格。但不幸的是我不知道如何实现我的想法。我不知道如何处理这个任务。

我想使用“Windows Forms App (.Net Framework)”完成我的任务。

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void Btn_FillDataGridView_Click(object sender, EventArgs e)
    {
        var source = new BindingSource();
        List<Product> list = new List<Product> { new Product("Shoes",50.14), new Product("T-Shirt", 20.55) };
        source.DataSource = list;

        DtGrdV_Products.DataSource = source;
    }
}

public class Product
{
    private string _name;
    private double _price;

    [DisplayName("Product")]
    public string Name { get => _name; set => _name = value; }

    [DisplayName("Price of product")]
    public double Price { get => _price; set => _price = value; }

    public Product(string name, double price)
    {
        Name = name;
        Price = price;
    }
}

一个经常被忽视的问题是,只允许将数字字符输入到单元格中的所有麻烦是......用户仍然可以“copy/paste”将冒犯性文本输入到单元格中。所以从技术上讲,即使我们只允许输入数值……这并不能使我们免于在用户离开单元格时验证文本是否为有效数字。

因此,既然我们无论如何都要验证文本,那么,连接网格 CellValidating 事件就是为此而做的。当此事件触发时,我们知道用户正试图“离开”单元格,因此,在用户离开单元格之前,我们需要检查文本是否为有效数字。否则,网格的 DataError 将继续抱怨,直到有问题的文本被修复。

鉴于此,您可以选择。如果您不关心用户在单元格中键入什么值,而只关心用户何时“离开”单元格,那么您需要做的就是连接网格 CellValidating 事件。如果你想通过只允许在单元格中输入数值来“帮助”用户,那么需要做更多的工作。

此外,如果我们连接网格的 CellValidating 事件,那么,我们可以在坏数据传递到底层数据源之前捕获它。然而,问题是……“发生这种情况时我们该怎么办?”我们必须做点什么;我们不能在没有网格 DataError 抱怨的情况下放手。

有几种方法可以解决这个问题,我在下面使用的方法是这样的:如果文本无效,则会显示一个消息框,说明单元格中的文本无效。此外,有问题的单元格被涂成浅红色,以帮助用户查看哪个单元格是有问题的单元格。当用户按下对话框上的“确定”按钮时,有问题的文本将替换为以前的值。基本上,当用户按下 Esc 键减去我们警告用户的消息框提示时,用户会遇到同样的事情。

这是你的决定,你如何处理这个,IMO,警告用户,然后回到旧值,然后继续。这样,用户在修复有问题的文本之前不会一直被窃听,但是,在某些情况下,这可能不是期望的行为。

因此,在此示例中,我们希望帮助用户并只允许将数值输入到单元格中,并且当用户离开单元格时,我们希望确保 typed/or 粘贴的文本是有效的。我们在上面讨论的最后一部分......我们将使用网格 Cellvaidating 事件专门用于用户离开单元格的时间。

大图……

从大局的角度来看,我们下面要做的事情是这样的:首先我们将连接网格 EditingControlShowing 事件。此事件是第一步,它将允许代码检查正在编辑“哪个”单元格。如果编辑的单元格是数字单元格之一,那么我们只需将该单元格转换为名为 CurrentCellBeingEdited 的全局 TextBox 变量,然后连接该文本框 KeyPress 事件。然后我们只是等待用户输入字符。

当用户确实键入内容时,文本框 KeyPress 事件将触发。在那种情况下,我们将简单地检查字符是否是有效的数字字符。如果该字符不是有效的数字字符,那么我们将“忽略”该字符并继续。

最后,当用户离开单元格时,这将触发网格 CellValidating 事件,我们将在其中检查有效的数值。显然,如前所述,我们需要此事件以防万一用户“copy/pasted”将一些违规文本输入单元格。

下面是上述内容的一个小例子。在示例中,网格有四 (4) 列:描述、成本、数量和总计。说明是一个简单的 string 单元格,可以包含字母字符和数字。成本单元格是一个 decimal 值,我们将只允许数值和一个小数位。数量列将是一个 int 值,其中只允许使用数字值。最后一个总计列是基础 DataTable 中计算成本乘以数量的表达式列。请注意,总计列不可由用户编辑。

创建一个新的 winforms 解决方案并将 DataGridView 拖放到窗体上,然后按照下面的步骤操作。成品应该看起来像……

首先,我创建了一个名为 CurrentCellBeingEdited 的全局 TextBox 变量。此变量将设置为当前编辑的单元格。这是为了方便而使用,并不是真正必要的,但是对于这个例子,它使事情更清楚一些。

TextBox CurrentCellBeingEdited = null;

如果用户输入的单元格是数值单元格之一,我们会将此变量分配给单元格。然后我们将 TextBoxKeyPress 事件订阅(连接)到正确的 KeyPress 事件。这一切都是在网格 EditingControlShowing 事件中完成的,可能看起来像……

private void dataGridView1_EditingControlShowing(object sender, DataGridViewEditingControlShowingEventArgs e) {
  if (CurrentCellBeingEdited != null) {
    CurrentCellBeingEdited.KeyPress -= new KeyPressEventHandler(DecimalNumbersOnlyCell_KeyPress);
    CurrentCellBeingEdited.KeyPress -= new KeyPressEventHandler(NumbersOnlyCell_KeyPress);
  }
  string targetCellColName = dataGridView1.Columns[dataGridView1.CurrentCell.ColumnIndex].Name;
  if (targetCellColName == "Cost" || targetCellColName == "Qty") {
    CurrentCellBeingEdited = (TextBox)e.Control;
    if (targetCellColName == "Cost") {
      CurrentCellBeingEdited.KeyPress += new KeyPressEventHandler(DecimalNumbersOnlyCell_KeyPress);
    }
    else {
      // Qty cell
      CurrentCellBeingEdited.KeyPress += new KeyPressEventHandler(NumbersOnlyCell_KeyPress);
    }
  }
}

如果需要,此事件只是将 TextBox 单元连接到正确的按键事件。第一个 if 语句…

if (CurrentCellBeingEdited != null) {
  CurrentCellBeingEdited.KeyPress -= new KeyPressEventHandler(DecimalNumbersOnlyCell_KeyPress);
  CurrentCellBeingEdited.KeyPress -= new KeyPressEventHandler(NumbersOnlyCell_KeyPress);
}

用于取消订阅(un-wire)任何之前订阅的事件。这可以防止事件在错误的单元格中触发。例子;如果用户选择描述单元格。同样的想法也适用于我们不希望用户在小数位键入句点的数量单元格。要点是,如果我们不从事件中“取消订阅”文本框,那么它可能会被触发多次,而且可能是针对错误的单元格。

取消订阅之前启用的任何事件后,代码仅检查正在编辑的单元格。如果编辑的单元格是成本或数量列,则代码将我们的全局变量 CurrentCellBeingEdited 转换为编辑的单元格,然后订阅适当的事件。

接下来,我们将需要成本和数量单元格的两个 KeyPress 事件,它们可能类似于……

// Quantity cell
private void NumbersOnlyCell_KeyPress(object sender, KeyPressEventArgs e) {
  if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar)) {
    e.Handled = true;
  }
}

// Cost cell
private void DecimalNumbersOnlyCell_KeyPress(object sender, KeyPressEventArgs e) {
  if (!char.IsControl(e.KeyChar) && !char.IsDigit(e.KeyChar) && e.KeyChar != '.') {
    e.Handled = true;
  }
  if (e.KeyChar == '.' && (sender as TextBox).Text.IndexOf('.') > -1) {
    e.Handled = true;
  }
}

最后,网格 CellValidating 事件。请注意,代码允许单元格为“空”。该代码检查单元格是否为成本或数量列,然后将 TryParse 应用于适当的单元格以验证单元格中的文本是否确实是有效的 intdecimal价值。如果 TryParse 失败,则单元格颜色为红色,并显示一个消息框,指示无效文本。用户单击消息框上的确定按钮后,取消编辑。

private void dataGridView1_CellValidating(object sender, DataGridViewCellValidatingEventArgs e) {
  if (CurrentCellBeingEdited != null) {
    string targetCellColName = dataGridView1.Columns[e.ColumnIndex].Name;
    if (targetCellColName == "Cost" || targetCellColName == "Qty") {
      if (!string.IsNullOrEmpty(CurrentCellBeingEdited.Text)) { // <- Allow empty cells
        bool valid = true;
        if (targetCellColName == "Cost") {
          if (!decimal.TryParse(CurrentCellBeingEdited.Text, out decimal value)) {
            valid = false;
          }
        }
        if (targetCellColName == "Qty") {
          if (!int.TryParse(CurrentCellBeingEdited.Text, out int value2)) {
            valid = false;
          }
        }
        if (!valid) {
          CurrentCellBeingEdited.BackColor = Color.LightCoral;
          MessageBox.Show("Invalid input - value will revert to previous amount");
          dataGridView1.CancelEdit();
          CurrentCellBeingEdited.BackColor = Color.White;
        }
      }
    }
  }
}

将所有这些放在一起并完成示例,下面的代码使用上面的事件来演示此功能。

TextBox CurrentCellBeingEdited = null;

public Form1() {
  InitializeComponent();
  dataGridView1.CellValidating += new DataGridViewCellValidatingEventHandler(dataGridView1_CellValidating);
  dataGridView1.EditingControlShowing += new DataGridViewEditingControlShowingEventHandler(dataGridView1_EditingControlShowing);
}


private void Form1_Load(object sender, EventArgs e) {
  DataTable GridTable = GetDT();
  GetData(GridTable);
  dataGridView1.DataSource = GridTable;
}

private DataTable GetDT() {
  DataTable dt = new DataTable();
  dt.Columns.Add("Description", typeof(string));
  dt.Columns.Add("Cost", typeof(decimal));
  dt.Columns.Add("Qty", typeof(int));
  dt.Columns.Add("Total", typeof(decimal), "Cost * Qty");
  return dt;
}

private void GetData(DataTable dt) {
  dt.Rows.Add("Product1", 12.49, 2);
  dt.Rows.Add("Product3", 2.33, 3);
  dt.Rows.Add("Product16", 5.00, 12);
}

我希望这有道理并有所帮助。