调用OpenFileDialog.ShowDialog()时,Windows Forms GUI卡住了。

7

我的项目是一个三层架构项目,与后端的WCF服务通信。当后端能够从服务中获取数据时,它使用发布-订阅通知业务层,业务层再通知GUI层。

我在UI设计中使用了Visual Studios设计器添加了一个OpenFileDialog。一个按钮事件处理程序调用ShowDialog消息。然而,一旦我点击按钮,整个UI就会挂起。

经过一番谷歌搜索,我发现使用委托是处理这类任务的首选方式。然而,无论使用还是不使用委托,问题仍然存在。

目前我的代码看起来像这样:

private void bOpen_Click(object sender, EventArgs e)
{
    Func<Image> del = delegate
    {
        OpenFileDialog d = new OpenFileDialog();
        if (d.ShowDialog() == DialogResult.OK)
        {
            return Image.FromFile(d.FileName);
        }

        return null;
    };

    Invoke(del);
}

我来自Java世界,对C# UI编程的细节不太熟悉。

这里有什么我不知道的吗?


1
我很好奇,为什么不直接使用以下代码:private void bOpen_Click(object sender, EventArgs e) { OpenFileDialog d = new OpenFileDialog(); if (d.ShowDialog() == DialogResult.OK) { Image.FromFile(d.FileName); } }; - Vlad
它在没有使用委托的情况下也会挂起。我创建了这个委托是因为我发现有几个网站说这是执行由 GUI 触发的长时间操作的首选方式。就像在 Java 中使用 SwingWorker 一样,我认为。 - Kage
2
这些网站提到的首选方法是在单独的线程中启动委托。类似于 new Thread(del).Start(); - Vlad
你的GUI卡住了?你具体指的是什么?如果你调用ShowDialog(),你期望会发生什么? - Tigran
1
您是否已经正确地将STA线程属性应用于GUI进程的主体上? http://msdn.microsoft.com/zh-cn/library/system.stathreadattribute.aspx - Martyn Lovell
显示剩余5条评论
6个回答

13
openFileDialog1->ShowHelp = true;

我在我的代码中加入了这行,然后问题就解决了。


1
哈哈,这是我在我的情况下唯一的解决方案! - Alexander
对我也起作用了。这很奇怪,我很好奇为什么这会使对话框工作。 - Sebastiano
这个解决方法在Windows Vista早期是有帮助的。相同的代码在Windows XP上正常工作,在Windows Vista上总是会挂起。这是一个简单的解决方法。 - BiLaL
在互联网上搜索了很多,包括许多关于SO的问题,这是唯一一个从OpenFileDialog.ShowDialog()中消除了未解释的5秒延迟的解决方案。谁能想到ShowHelp与此有关呢?但不知何故它确实有关系。 - Joe Gayetty
它在我的旧Windows 7机器上运行良好。 d.ShowHelp = true; - 拇指 muzhi.com

9

我似乎已经解决了这个问题,只需将[STAThread]属性添加到主方法中即可。在调试器中运行程序后,有人告诉我要这样做 - 因为之前我从Visual Studio运行服务,而客户端则是在Windows上运行的,所以没有这样做。

[STAThread]
public static void Main(string[] args)
{
    GUI gui = new GUI();
    gui.ShowDialog();
}

有人能解释一下具体发生了什么吗?

1
文件打开/保存对话框只能在单线程公寓模型中显示。这在文档中已经很好解释了。如果我早点看到这个问题,我本来会发表这个观点的。 - Cody Gray

8
这往往是一个环境问题,当您使用 OpenFileDialog 时,许多 shell 扩展会加载到您的进程中。不良扩展很容易捣乱,导致程序出现问题。有很多不好的扩展程序存在。
调试这个问题很困难,因为这些 shell 扩展是非托管代码,所以需要使用非托管调试器。您可以在死锁后断点查看调用堆栈了解一些信息。需要 Windows 调试符号,启用 Microsoft 符号服务器。但最有效的方法是使用 SysInternals 的 AutoRuns 工具。首先禁用所有非 Microsoft 生产的 shell 扩展,然后逐个重新启用那些您不能没有的扩展程序。
而且,正如您发现的那样,这些 shell 扩展希望在 STA 线程上运行,如果它们没有得到,则会失败。程序的 UI 线程必须始终是 STA,以支持剪贴板、拖放和各种控件(如 WebBrowser)。通常,在 Main() 方法上自动处理为 [STAThread] 属性,并由项目模板放置。而 Application.Run() 调用则需要实现 STA 协定,否则可能出现死锁。

1
奇怪的是,我可以毫无问题地打开颜色选择器。但这似乎证实了 shell extension 的理论。 - Kage

4
我认为“委托”首选的方式实际上是使用单独的线程。 我将用BackgroundWorker给您提供一个示例。
代码如下:
public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            m_Worker.DoWork += new DoWorkEventHandler(m_Worker_DoWork);
            m_Worker.ProgressChanged += new ProgressChangedEventHandler(m_Worker_ProgressChanged);
            m_Worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(m_Worker_RunWorkerCompleted);
        }

        void m_Worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            //Usually, used to update a progress bar
        }

        void m_Worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            //Usually, used to add some code to notify the user that the job is done.
        }

        void m_Worker_DoWork(object sender, DoWorkEventArgs e)
        {
            //e.Argument.ToString() contains the path to the file
            //Do what you want with the file returned.
        }        

        private void bOpen_Click(object sender, EventArgs e)
        {
            OpenFileDialog d = new OpenFileDialog();
            if (d.ShowDialog() == DialogResult.OK)
            {
                m_Worker.RunWorkerAsync(d.FileName);    
            }            
        }

        BackgroundWorker m_Worker = new BackgroundWorker();
    }

关于你的UI“挂起”的原因,是因为默认情况下,你的操作在UI线程上运行,所以如果你运行一些繁重的操作,UI就不会响应。


如果我不能在线程上使用它,那我就没办法了。一旦执行进入ShowDialog()方法,程序就会挂起。我可以尝试在另一台机器上运行它。如果像你在下面的回复中所说的那样,汉斯,它应该在另一台机器上工作,对吧? - Kage

0

我也遇到了这个问题。我尝试了这里的所有解决方案,但都没有解决。然后我将目标框架从.Net Framework 4.7更改为4.6.2,问题就解决了...


0

我认为我的问题是不同的,因为上述解决方案都没有起作用。

我编写了临时代码来OpenFileDialog.FileName 属性设置为非空值或非空字符串(当出现卡顿时为空字符串),然后我重新启动了电脑。当我再次启动 Visual Studio 并运行它时,它又可以正常工作了。


我遇到了一个问题,当我尝试选择一个新文件夹时,OpenFileDialog会卡住,而FileName设置为""。将FileName更改为"Image.jpeg"似乎解决了这个问题。 - SimonKravis

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