【问题标题】:Updating a dialog from another form从另一个表单更新对话框
【发布时间】:2012-08-28 22:04:19
【问题描述】:

我知道有很多类似的问题,我已经阅读了很多。不幸的是,在阅读完它们后我仍然无法解决我的问题 - 但是我对 C# 比较陌生。根据docs,在使用调试器时,不是线程安全的问题会导致InvalidOperationException。我的问题不是这种情况。

我用一个简单的原始测试类重新创建了问题,以专注于我的问题。

主窗体应该显示一种进度对话框。

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

    public void updateFileStatus(string path)
    {
        t_filename.Text = path;         
    }

    public void updatePrintStatus()
    {      
        t_printed.Text = "sent to printer";
    }

    public void updateImportStatus(string clientName)
    {
        t_client.Text = clientName;
    }

    public void updateArchiveStatus()
    {
        t_archived.Text = "archived";
    }
}

当该代码在没有任何 Invoke() 的情况下从主窗体调用时:

private void button1_Click(object sender, EventArgs e)
    {
        ImportStatusDialog nDialog = new ImportStatusDialog();

        nDialog.Show();

        nDialog.updateFileStatus("test");
        Thread.Sleep(1000);
        nDialog.updateImportStatus("TestClient");
        Thread.Sleep(1000);
        nDialog.updatePrintStatus();
        Thread.Sleep(1000);
        nDialog.updateArchiveStatus();
        Thread.Sleep(1000);

        nDialog.Close();
    }

即使我这样称呼它:

private void button3_Click(object sender, EventArgs e)
    {
        ImportStatusDialog nDialog = new ImportStatusDialog();

        nDialog.Show();

        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate
            {
                nDialog.updateFileStatus("Test");
            });
        }
        else
        {
            nDialog.updateFileStatus("Test");
        }

        Thread.Sleep(1000);

        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate
            {
                nDialog.updatePrintStatus();
            });
        }
        else
        {
            nDialog.updatePrintStatus();
        }

        Thread.Sleep(1000);

        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate
            {
                nDialog.updateImportStatus("cName");
            });
        }
        else
        {
            nDialog.updateImportStatus("cName");
        }

        Thread.Sleep(1000);

        if (this.InvokeRequired)
        {
            this.Invoke((MethodInvoker)delegate
            {
                nDialog.updateArchiveStatus();
            });
        }
        else
        {
            nDialog.updateArchiveStatus();
        }

        Thread.Sleep(1000);

        nDialog.Close();
    }

在设计器中看起来像这样的对话框(在我的示例中)

会这样显示:

当我使用 ShowDialog() 而不是 Show() 时,对话框会正确显示,但正如 API Doc 指出的那样

您可以使用此方法在您的 应用。调用此方法时,它后面的代码不是 直到对话框关闭后才执行

这不是我想要的,尤其是因为这意味着对话框更新只会在它再次关闭后才会发生。

我在这里做错了什么?这似乎是一个微不足道的问题,但解决方案却避开了我。请记住,我是 C# GUI 编程的新手。

另外,我想问一下使用 Invoke() 的正确位置是什么?你会在调用对话框方法时在主窗体中使用它还是在对话框更新方法本身中使用它?

【问题讨论】:

  • 您在按钮单击处理程序中阻塞了主 UI 线程,这意味着无法重绘窗口。
  • 如何防止这种情况发生?在此示例中,事件由按钮单击处理程序引发。在我的现实世界问题场景中,事件来自FileSystemWatcher。但在这两种情况下,我都想对事件做出反应
  • 你这样做是根本错误的。 GUI 编程与控制台模式编程完全不同。你无法猜测需要什么,你真的需要读一本书。
  • @HansPassant 我在看书。但是显然它太笼统了,并且没有涵盖使用多个表单的 GUI 处理。你能根据自己的经验推荐一本关于 GUI 设计和交互的好书吗?

标签: c# user-interface dialog thread-safety


【解决方案1】:

看起来您在这里没有使用多个线程,因此不需要调用内容。只有跨线程调用才需要调用 - 您正在创建多个表单,但所有代码都在同一个线程中运行。

如果您在主 UI 线程中进行工作(正如按钮单击事件中的代码所暗示的那样),则只需定期调用 Application.DoEvents() 以允许刷新进度表单。

更好的解决方案是为您的工作使用BackgroundWorker,并让它定期报告其进度。

然后你可以在BackgroundWorker的ProgressChanged事件中更新进度表(会在主线程中执行,所以你仍然不需要调用任何东西)。

【讨论】:

  • 避免向 Application.DoEvents() 推荐。即使您提到 BackgroundWorker 是更好的解决方案。
  • 我认为主程序线程和 C# GUI/事件调度线程已经有 2 个线程在运行
  • 不,GUI线程是主线程。这就是为什么当您在按钮处理程序中做太多工作时,您的窗口会停止响应。
  • 好的,谢谢。我研究了BackgroundWorker 类,我认为了解基本原理。但是,在我的示例中,我有 4 个标签需要更新。您将如何有选择地使用 1 个 BackgroundWorker 更新每一个?还是每个人都需要 1 个?因为 4 BackgroundWorker 对于这么小的对话框来说似乎有点矫枉过正。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-12-14
  • 2011-08-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多