在非UI线程上运行模态对话框

6
我正在使用标准的.NET数据绑定到来自SQL Server的类型化DataSet编写简单的数据UI。
我有一个重新加载按钮,它调用所有DataAdapters上的Fill以从数据库获取新数据(以防其他用户更改了数据)。
这需要一些时间,在此期间UI被冻结。它必须在UI线程上运行,否则数据绑定事件处理程序会抛出跨线程异常。
我想在后台线程上显示一个模态“请稍候”对话框(以便可以进行动画),而UI线程连接到数据库。
如何在非UI线程上显示模态对话框?
编辑:我知道最佳实践是在后台运行操作,但由于数据绑定事件,我无法这样做。
5个回答

8
您应该反其道而行之。在后台线程上运行您的长时间运行的进程,使UI线程保持空闲以响应用户操作。
如果您想在处理过程中阻止任何用户操作,则有多种选择,包括模态对话框。一旦后台线程完成处理,您可以通知主线程结果。

正如我在问题中所解释的那样,我做不到。我非常清楚这是最佳实践。 - SLaks
我认为你仍然可以这样做。这只是意味着后台线程不应直接更新数据。检索它们,打包它们并委托给主线程来更新UI。 - mfeingold
我理解你的困境,但是我仍然认为我的建议比替代方案更好(不会有太多麻烦)。 - mfeingold
有没有简单的方法来做到这一点,同时仍然使用标准数据绑定和更新事件?我正在尝试快速完成这个项目。 - SLaks

2
在数据绑定事件中运行的代码需要与UI解耦,可能需要使用某种数据传输对象。
然后您可以在单独的线程或BackgroundWorker中运行查询操作,保留UI线程不变。
编辑:修复此问题的真正快速方法是使用InvokeRequired.Invoke使事件在其自己的委托中运行。这将为方法提供UI上下文。我的同事经常这样做,但这种方式很少是一个好主意...但如果您想要一种快速的解决方案,这将起作用。(我现在不在工作,所以我没有样本;我会尝试想出一些东西。)
编辑2:我不确定您要求的是否可能。我创建了一个示例应用程序,在另一个线程中创建模态对话框,结果变成非模态。除了使用模态对话框之外,您可以使用其他控件或一组控件来指示进度更改,最有可能直接在同一表单上。

有没有简单的方法来做到这一点,同时仍然使用标准数据绑定和更新事件?我正在尝试快速完成这个项目。 - SLaks
关于编辑:我无法在DataGrid中放置“Invoke”调用。 - SLaks
回复:编辑2:你很有可能是对的。如果这是一个简单的问题,我就不会在这里问了。至于在表单上放置控件,那就更糟糕了——在由不同线程拥有的表单上放置一些控件是完全不可能的。 - SLaks
没错,但如果实际处理是在不同的线程中进行的,那么这些控件只是表单上的普通控件,在与表单相同的线程中。 - Jon Seigel
如果实际处理在不同的线程上,我可以在 UI 线程上调用 ShowDialog 并创建一个模态窗体。 - SLaks
你说得对...抱歉,这个问题让我想反了。自己提醒一下:不要在周末尝试解决技术问题 ;) - Jon Seigel

0
using( var frmDialog = new MyPleasWaitDialog() ) {
    // data loading is started after the form is shown
    frmDialog.Load += (_sender, _e) {
        // load data in separate thread
        ThreadPool.QueueWorkItem( (_state)=> {
            myAdapter.Fill( myDataSet );
            // refresh UI components in correct (UI) thread
            frmDialog.Invoke( (Action)myDataControl.Refresh );
            // close dialog
            frmDialog.Invoke( (Action)frmDialog.Close() );
        }
    }

    // shows dialog
    frmDialog.ShowDialog( this );
}

你需要将数据加载到不与datagrid绑定的不同DataSet中,然后重新将datagrid绑定到新的dataset。或者尝试在dataset/datatable上使用“BeginLoadData()”和“EndLoadData”。 - TcKs
1
正如我在问题中试图解释的那样,当在后台线程上调用myAdapter.Fill时,在网格数据绑定事件处理程序中会抛出一个InvalidOperationException异常。在表上调用BeginLoadData也没有帮助。 - SLaks
1
所以,您需要填充另一个数据集,然后重新绑定数据网格到“新”的(已填充)数据集实例。 - TcKs
1
顺便提一下,当添加匿名事件处理程序时,您可以编写 EventName += delegate { code };(注意没有括号),这将隐式声明参数。 - SLaks

0
这是使用BackgroundWorker来加载数据并运行用户友好的表单以显示“正在加载记录”或类似信息的示例...
    public void Run()
    {
        bgWorkrFillDS = new BackgroundWorker();
        bgWorkrFillDS.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgWorkrFillDS_RunWorkerCompleted);
        bgWorkrFillDS.DoWork += new DoWorkEventHandler(bgWorkrFillDS_DoWork);
        bgWorkrFillDS.RunWorkerAsync();
    }

    void bgWorkrFillDS_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker bgWrkrFillDS = (BackgroundWorker)sender as BackgroundWorker;
        if (bgWrkrFillDS != null)
        {
            // Load up the form that shows a 'Loading....'
            // Here we fill in the DS
            // someDataSetAdapter.Fill(myDataSet);
        }
    }


    void bgWorkrFillDS_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        // Hide or unload the form when the work is done
    }

希望这能有所帮助... 保重, 汤姆。

1
请仔细阅读问题。在后台线程上调用 someDataSetAdapter.Fill 将引发 InvalidOperationException 异常。 - SLaks

0

我通过创建一个新的DataSet,在后台加载它,然后在UI线程上调用DataSet.Merge来解决了这个问题。感谢大家的建议,这导致了这个解决方案。

作为额外的奖励,这比以前运行得快得多(在后台调用Fill,只有没有打开网格时才有效)。有人知道为什么吗?


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接