【问题标题】:Timer MinValue, MaxValue, and Value in C#C# 中的定时器 MinValue、MaxValue 和 Value
【发布时间】:2021-04-10 06:00:58
【问题描述】:

好吧,我知道这听起来很奇怪,但老实说,我确实记得在 c# 中用计时器解决了一个示例,在该示例中我设置了计时器的最小值、最大值、刻度、间隔等......但我不记得/不要不知道该怎么做,MVS 2019 不会显示这些参数。

任何帮助将不胜感激。

我正在尝试根据第 7 个索引的值开始一行之后 datagridview (dg) 已从数据库中完全加载,但它无法正常工作,或者某些行没有已加载或已加载但 O 复杂度非常低(程序响应非常糟糕)。

        private void Dg_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
    {
        OleDbConnection con = GetConnection();
        OleDbCommand cmd = new OleDbCommand("SELECT * FROM Items", con);
        try
        {

            OleDbDataReader reader = cmd.ExecuteReader();
            while (reader.Read())
            {
                string PartName = reader.GetValue(0).ToString();
                string SN = reader.GetValue(1).ToString();
                string ModNum = reader.GetValue(2).ToString();
                string TS = reader.GetValue(3).ToString();
                string Man = reader.GetValue(4).ToString();
                string Brand = reader.GetValue(5).ToString();
                string Price = reader.GetValue(6).ToString();
                string Quan = reader.GetValue(7).ToString();
                string IPP = reader.GetValue(8).ToString();
                string HeadMat = reader.GetValue(9).ToString();
                string Descibtion = reader.GetValue(10).ToString();
                string MinQuan = reader.GetValue(11).ToString();
                string MaxQuan = reader.GetValue(12).ToString();
                string PricePI = reader.GetValue(13).ToString();
                string ExpiryDate = reader.GetValue(14).ToString();
                string USDRate = reader.GetValue(15).ToString();

            }
            reader.Close();
            con.Close();
        }
        catch (OleDbException ex)
        {
            MessageBox.Show(ex.Message.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
        }

        if (GetConnection().State == ConnectionState.Closed)
        {
            timer1.Start();
            Thread thread = new Thread(Blink);
            thread.Start();
            timer1.Stop();

        }
    }

没有这段代码,程序运行得很好,没有任何问题

这是处理行颜色变化和闪烁的 Blink 方法。

   private void Blink(object o)
    {
        if (dg.Rows.Count != 0)
        {
            while (dg.Rows.Count >= 0 && go)
            {
                foreach (DataGridViewRow row in dg.Rows)
                {
                    int value = int.Parse(row.Cells[7].Value.ToString());
                    if (value == 0 || value == 1)
                    {
                        row.DefaultCellStyle.BackColor = Color.Red;
                    }
                    else if (value > 1 || value < 4)
                    {
                        row.DefaultCellStyle.BackColor = Color.Yellow;
                    }
                    else
                        row.DefaultCellStyle.BackColor = Color.Green;
                }
                go = true;
                Thread.Sleep(100);

                while (dg.Rows.Count != 0 && !go)
                {

                    foreach (DataGridViewRow row in dg.Rows)
                    {
                        int value1 = int.Parse(row.Cells[7].Value.ToString());
                        if (value1 == 0 || value1 == 1)
                        {
                            row.DefaultCellStyle.BackColor = Color.White;
                        }
                        else if (value1 > 1 || value1 < 4)
                        {
                            row.DefaultCellStyle.BackColor = Color.White;
                        }
                        else
                            row.DefaultCellStyle.BackColor = Color.White;
                    }
                    go = false;
                    Thread.Sleep(1000);
                }
            }
        }
    }

【问题讨论】:

  • 你能解释一下这段代码的目的是什么吗?您正在将数据从数据库获取到阅读器并通过阅读器循环,但对数据不做任何事情。为什么需要那个代码?
  • 我正在从数据库中填充一个 datagridview 并更改每行的背景颜色,如果第 7 个索引上的值,验证过程必须仅在 datagridview 完全填充时开始,我让它工作到目前为止,但我无法对数据做任何事情,我的意思是出售、复制、删除编辑...
  • 我认为您需要先阅读有关如何在 gridview 中加载数据的信息...此外,您共享的 Dg_CellPainting 代码除了检索数据和通过阅读器循环之外没有任何作用。
  • 哦,让我编辑我的帖子。我忘记了眨眼方法...等一下请检查我的帖子
  • 不要将该代码放在CellPainting 事件中。该事件经常被触发。考虑到代码在做什么……看到性能拖累并不奇怪。另外,代码应该做什么? while (reader.Read()) … 循环中的代码什么也不做。该代码的目的是什么?另外,Blink 方法中的代码看起来很奇怪……if (value &gt; 1 || value &lt; 4) ?这看起来很奇怪。您确定不想使用 && 代替 || 吗?代码让Thread 进入睡眠状态?你到底想用这段代码做什么?您需要重新考虑这一点。

标签: c# mysql sql datagridview


【解决方案1】:

我不想劝阻你这样做,但是,如果你会放纵我片刻,因为我很难理解“为什么”,你会希望行“闪烁”一小段时间的时间。这对我来说似乎很奇怪,如下所述。

我对“闪烁”的行有些怀疑。我不确定这是怎么回事 行“闪烁”5 秒 “帮助”用户考虑 最后将行颜色更改为特定颜色。我个人 认为“眨眼”会让人分心/烦人,而且毫无用处。 当然,当数据加载到 表明这些行有问题。但即便如此,也很容易 想象用户在那一刻移开视线并错过“眨眼” 行以及低于可见网格大小的行呢? “闪烁”这些行是没有意义的。所以,我不明白“什么 目的”这个“闪烁”的行有什么用?我认为它会使 如果“闪烁”在用户纠正之前从未停止过,则更有意义 违规数据。

话虽如此,我建议仔细研究一下您目前在代码方面的知识。这种代码方法可能很有效,但是在我的测试中,我相信 UI 会表现得很慢并且响应速度会很慢。例如,如果您在任一方向滚动网格,您会注意到显示是如何缓慢和跳跃,并且不像预期的那样平滑。此外,如果您编辑一个单元格并按“Enter”键,您会发现有延迟……需要一两秒才能下拉到下一个单元格。

这主要是因为Blink 代码是从网格的CellPainting 事件中执行的。网格的CellPainting 事件经常被触发。当没有任何单元格发生变化时,它将触发。例如,如果用户“滚动”网格。因此,Blink 代码将不必要地运行,因为没有更改任何单元格。换句话说,当行已经是正确的颜色时,Blink 方法可以运行并更改行的颜色。这导致了呆滞。代码运行Blink 方法的次数超出了所需的次数。

你可能会说……“好吧,我在另一个线程下运行Blink 方法。”……用……

Thread thread = new Thread(Blink);
thread.Start();

你是对的……但是,这实际上“创造”了更多与 UI 迟缓有关的问题。如果你退后一步看看这个,你应该问自己……“我需要多少线程?”……这段代码实际上是在“创建”数百个 threads, 一次使用它们并且从不处理他们。

例如,假设数据包含三 (3) 列二十 (20) 行。将数据加载到网格中时,网格的 CellPainting 事件将至少触发六十 (60) 次(每个单元格一次)。 (我相信它实际上会触发更多),然而,即使是 60 次,这也意味着代码会创建至少 60 个永远不会被处理的“独立”线程。有增无减,崩溃应该不足为奇。

如果代码在某处正确处理线程,您可能会有所缓解,但是,在代码当前状态下,这是不可能的,因为 Thread 是“创建”、启动的,并且从未在相同的if 声明。一旦退出if 语句,变量thread 就会超出范围(未正确处理)。

当然,一旦超出范围,GC 可能会处理它,但是,我不指望它会很快处理它。这里的要点是,为此启动多个Threads 似乎没有必要并且是有问题的。另外,我的理解是在使用win forms时,最好使用BackGroundWorker而不是Thread.

我只是在暗示这样一个事实……如果您确实为此使用线程/后台工作人员,那么实际上只需要一个后台工作人员。我们可以一遍又一遍地使用同一个后台工作人员。由于代码执行需要几毫秒,因此工作人员将很快完成。我可能弄错了,但我只是认为这里不需要后台工作人员。在某些情况下,可能需要它并且需要一种完全不同的方法。

闪烁代码……

参考Blink 代码……它可能工作得很好,但是,用户永远不会看到行颜色的这种“变化”。用户不会看到颜色变化,因为代码不会“刷新”网格。我认为这就是为什么存在 sleep 语句的原因。在代码退出Blink 方法之前,网格不会“自动”刷新。用户只会看到最终产品。用户不会看到行颜色发生变化,并且行将显示为保持白色。

您可以考虑简单地调用网格Refresh 方法,然后休眠执行几毫秒以允许用户“看到”行颜色的变化。不幸的是,这行不通。 Blink 正在从网格 CellPainting 事件中调用。如果我们从Blink 调用网格Refresh 方法……那将“重新触发”网格CellPainting 事件。几乎可以保证重入错误。当Blink 方法设置行颜色时,同样的想法也适用。这可能会再次触发网格的单元格绘制事件。更有理由避免使用此事件。

鉴于所有这些并强调我的第一条评论,应该清楚的是,出于多种原因,为此使用网格 CellPainting 事件可能不是最好的方法。大多数网格“绘制”事件经常触发,并且对于代码正在执行的操作,我们最终会运行代码超过所需的次数。

在这种情况下,如果当前代码确实执行了,即使在 5 秒后,它也会继续“闪烁”行。每次触发网格CellPainting 事件时,代码将调用Blink 方法并且行将开始闪烁。最终,网格将开始像圣诞树一样闪烁,而 UI 将开始停止。

这里的主要问题是,当使用“GRIDS”绘画事件时,它们触发的次数比我们需要的要多得多,但是我们可以解决这个问题,但是考虑到您只希望行“闪烁”5数据加载后几秒钟,然后 5 秒钟后,为什么网格会担心呢?这应该运行 5 秒钟,然后停止。因此,引入网格事件似乎有点矫枉过正。可以,但是为什么。数据加载后,启动 5 秒计时器,开始闪烁行……然后,五秒钟后,停止闪烁行……完成。

鉴于此,我的方法不使用任何网格事件(目前)。而不是使用网格CellPainting 事件来调用Blink 代码……让我们按照您最初的建议使用Timers. 我们可以使用两(2)个计时器。一个计时器的间隔为 5000,并将跟踪我们希望在数据加载到网格后行“闪烁”的 5 秒。第二个计时器的时间间隔为 500 秒或 1/2 秒。随着此计时器的每一次滴答,我们将调用 Blink 方法来“切换/闪烁”行颜色。

当前Blink 代码的小痕迹……

如果我们从第一个 foreach 开始循环遍历网格行,如果第 7 列的单元格为零 (0) 或一 (1),则代码会将该行着色为红色。在else 部分,我们有……if (value &gt; 1 || value &lt; 4)……我相信您的意思是用 && 来检查数字 2 和 3。否则,您需要解释这将如何成为 false. 假设我们想使用 &&,当单元格 7 的值为 2 或 3 时,这会将行着色为黄色。

如果单元格值不是 0、1、2 或 3,则将该行颜色设置为绿色。

继续,代码不必要地冻结 UI 并“休眠”100 毫秒。然后代码再次启动foreach 循环遍历网格中的行。在这里,我不遵循逻辑,如果所有行都设置为白色,那么为什么要检查单元格 7 的值?检查单元格 7 中的值是不必要的,因为代码将行设置为相同的颜色(白色)。

看起来,一种更简单的方法是根据第 7 列中的单元格值“着色”所有行……或……将所有行着色为白色。这应该简化代码,我们可以使用计时器告诉代码“哪种”方式为行着色……即,根据第 7 列为行着色或将行着色为白色。

为了提供帮助,我们可以创建一个全局 bool 变量并将其命名为 ColorOn. 我们可以使用此变量来帮助我们“切换”行的颜色状态。如果ColorOntrue 并且我们运行Blink 代码,它将根据第7 列的单元格值对行着色。如果ColorOnfalse,那么Blink 会将行着色为白色。

由于我们可能会多次循环遍历网格行来为行着色,如果我们创建一个名为 SetRowColor 的方法可能会有所帮助,该方法采用 DataGridViewRowBoolean 值来指示我们是否应该制作行颜色“白色”或根据第 7 列为行着色。当代码循环遍历网格行并更改行颜色时,此方法将派上用场。

SetRowColor 方法如下。首先,代码检查“新”行……如果网格“新”行存在,我们将忽略它。接下来,检查bool white 参数。如果是true,则将该行涂成白色。如果是false,则根据第 7 列中的值对行着色。

如果我们想根据第 (7) 列“TargetColumn”中的单元格值为行着色。 TryParse 用于安全地将单元格值验证为数字并将该数字放入out 变量value. 接下来在value 变量上使用switch 语句来确定设置行的颜色. 0 或 1,红色; 2 或 3 黄色和任何其他数字绿色。

private void SetRowColor(DataGridViewRow row, bool white) {
  if (row.IsNewRow) {
    return;
  }
  if (white) {
    row.DefaultCellStyle.BackColor = Color.White;
  }
  else {
    if (int.TryParse(row.Cells["TargetColumn"].Value.ToString(), out int value)) {
      switch (value) {
        case 0:
        case 1:
          row.DefaultCellStyle.BackColor = Color.Red;
          break;
        case 2:
        case 3:
          row.DefaultCellStyle.BackColor = Color.Yellow;
          break;
        default:
          row.DefaultCellStyle.BackColor = Color.LightGreen;
          break;
      }
    }
  }
}

我们可以在新的Blink 方法中使用SetRowColor 方法,它可能如下所示。在代码之后,对ColorOn 变量进行检查...

  • 如果 true, 则循环遍历所有行并根据第 7 列中的单元格值设置每行颜色。
  • 如果 false, 则循环遍历所有行并将每行颜色设置为白色。

我们将在MilliSecondTimer_Tick 事件中调用此Blink 方法。

private void Blink() {
  if (ColorOn) {
    foreach (DataGridViewRow row in dg.Rows) {
      SetRowColor(row, false);
    }
  }
  else {
    foreach (DataGridViewRow row in dg.Rows) {
      SetRowColor(row, true);
    }
  }
}

这看起来更干净,执行需要几毫秒。

现在我们已经设置了Blink 方法,接下来我们需要一个Timer 来调用它。需要设置此计时器的间隔,以便它定义我们希望行颜色切换的“频率”。例如,如果我们将间隔设置为 1 秒,那么行将每秒更改一次颜色。对于每个刻度,我们调用Blink,然后切换ColorOn 变量以在下次更改颜色。这个MilliSecondTimer_Tick 事件可能看起来像……

private void MilliSecondTimer_Tick(object sender, EventArgs e) {
  Blink();
  ColorOn = !ColorOn;
}

如果我们运行它,计时器滴答事件将在每个间隔触发并切换行颜色,但它不会在五秒后停止。可以“检查”自计时器首次启动以来经过的时间量,如果大于 5 秒,则停止计时器。这是可行的;但是,我走的是懒惰的路线,并使用了间隔为 5 秒的第二个计时器。

与“眨眼”计时器同时启动此五秒计时器。然后,经过 5 秒后,它的 tick 事件将触发,我们可以关闭“闪烁”计时器并停止它自己的计时器,因为它不再需要。由于我们希望行在“闪烁”之后“保持”在彩色状态,我们需要将ColorOn 设置为true 并最后一次调用Blink 方法。简单地停止计时器可能会使网格处于所有行都为白色的状态。这个FiveSecondTimer_Tick 事件可能看起来像……

private void FiveSecondTimer_Tick(object sender, EventArgs e) {
  MilliSecondTimer.Stop();
  FiveSecondTimer.Stop();
  ColorOn = true;
  Blink();
}

辅助方法用于设置和启动全局Timers。我们设置时间间隔,订阅(连接)定时器的滴答事件并启动定时器。我们将在加载数据后立即调用此方法。需要注意的是,如果您减少 MilliSecondTimer interval 行将“闪烁”得更快,显然,更大的数字会“闪烁”得更慢。五秒定时器的间隔设置为 5 秒。增加其间隔将允许行继续“闪烁”超过 5 秒。

private void StartTimers() {
  FiveSecondTimer.Interval = 5000;
  MilliSecondTimer.Interval = 500;
  FiveSecondTimer.Tick += new EventHandler(FiveSecondTimer_Tick);
  MilliSecondTimer.Tick += new EventHandler(MillSecondTimer_Tick);
  MilliSecondTimer.Start();
  FiveSecondTimer.Start();
}

创建了获取一些测试数据GetDT 的方法。 DataTable 用作网格的数据源。它有三 (3) 列,因此我们要检查值的“目标”列在第 2 列中。它的名称是“TargetColumn”,该名称在SetRowColor 方法中使用,而不是列索引号.然后表中填充了三十 (30) 行,这样TargetColumn 的值将来自随机数生成器,其中随机数从 0 到 6。

private DataTable GetDT() {
  DataTable dt = new DataTable();
  dt.Columns.Add("Col0", typeof(string));
  dt.Columns.Add("TargetColumn", typeof(int));
  dt.Columns.Add("Col2", typeof(string));
  Random rand = new Random();
  for (int i = 0; i < 30; i++) {
    dt.Rows.Add("COR" + i, rand.Next(0, 7), "C2R" + i);
  }
  return dt;
}

把所有这些放在一起……

为了将所有这些放在一起并进行测试,下面的代码应该演示上面描述的内容。创建一个新的 win 表单项目,如上所示将 DataGridView 拖放到表单上,然后使用 OPs 网格名称将网格重命名为 dg...dg. 运行代码应该“闪烁”网格中的行5 秒和 5 秒后将使行保持彩色状态。

DataTable GridDT;
bool ColorOn = true;
System.Windows.Forms.Timer FiveSecondTimer = new System.Windows.Forms.Timer();
System.Windows.Forms.Timer MilliSecondTimer = new System.Windows.Forms.Timer();

private void Form1_Load(object sender, EventArgs e) {
  GridDT = GetDT();
  dg.DataSource = GridDT;
  StartTimers();
}

如果用户“更改”目标列单元格中的值会怎样?

现在我们已经完成了行的着色和闪烁,您可能会问自己,“如果用户(或代码)更改“目标”列中的单元格值之一会发生什么? ” 颜色不会改变,我们可能需要再次运行Blink 代码。

这是其中一个网格事件会派上用场的地方。当用户更改单元格值并尝试“离开”单元格时,网格CellValueChanged 事件将触发。如果我们连接(订阅)这个事件,我们可以再次运行Blink 代码。但这似乎有点矫枉过正。换句话说,只有一 (1) 个单元格发生了变化,似乎没有必要遍历所有行。而且我们已经知道它是哪一行。

在这种情况下,我们只想在“TargetColumn”单元格更改值时更改行颜色。如果其他列的值发生变化,我们可以忽略这些。我们只关心“TargetColumn”单元格的变化。下面是与Blink 代码相同的代码,但它不会遍历所有网格行……它只会重新着色更改的行。使用SetRowColor 方法让事情变得简单一些。

private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
  if (dg.Columns[e.ColumnIndex].Name == "TargetColumn") {
    if (dg.Rows[e.RowIndex].Cells["TargetColumn"].Value != null) {
      SetRowColor(dg.Rows[e.RowIndex], false);
    }
  }
}

这应该完成示例。请原谅我的长篇大论。我希望它有意义并有所帮助。

【讨论】:

  • 您从根本上解决了这个问题,并为我想到的其他一些想法添加了解决方案。编辑部分,我有一个按钮,它打开另一个表单并将 CurrentSelectedRow 的所有数据从父级传递给子级(form1 到 form2)期望主键然后将新编辑的值更新到主键匹配的数据库而不更新主键键或更改它。非常感谢您,它的效果非常好。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-02-07
相关资源
最近更新 更多