C#线程安全打开/保存文件对话框
下载FileDialogsThreadAppartmentSafe_v1.zip
如果您将C#用于桌面开发项目,您可能知道Win32打开和保存文件对话框的默认C#包装类的强大功能。它们易于使用,您将始终拥有正确的Windows风格。

但也存在一些问题,其中一些问题在本文中进行了讨论。
背景
一旦开发了更大的应用程序,您可能会注意到,一旦将调用者线程单元状态设置为MTA,默认 OpenFileDialog和SaveFileDialog 对话框将不再起作用。调用ShowDialog 实例的方法后,您将收到以下异常。
{System.Threading.ThreadStateException: Current thread must be set to
single thread apartment (STA) mode before OLE calls can be made.
Ensure that your Main function has STAThreadAttribute marked on it.
This exception is only raised if a debugger is attached to the process.
at System.Windows.Forms.FileDialog.RunDialog(IntPtr hWndOwner)
at System.Windows.Forms.CommonDialog.ShowDialog(IWin32Window owner)

通过在STA模式下创建一个调用打开或保存文件对话框的新线程,可以轻松解决此问题。但是,如果您的应用程序应该在多个显示设备上运行,则会弹出下一个问题。您会注意到,您无法像使用常见的winforms表单实例那样设置打开文件对话框的父级。
那么,如何解决这个问题呢?好吧,我想到的第一件事就是使用默认Win32 方法通过使用以下PInvokes来设置这些对话框的位置:
[DllImport("User32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool GetWindowRect(IntPtr handle, ref RECT r);
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
public static extern IntPtr GetParent(IntPtr hWnd);
[DllImport("user32.dll", SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetWindowPos(IntPtr hWnd,
IntPtr hWndInsertAfter, int x, int y, int cx, int cy, uint uFlags);
可以在任何Win32 对话框实例上设置窗口的位置。但是,一旦您仔细查看了打开/保存文件对话框的不同成员和属性,您会注意到这些对话框没有像常见对话框中那样的底层对话框实例的Handle 成员。IntPtr Win32System.Windows.Forms
但是使用static PInvoke 方法并不是面向.NET面向对象,对吧?如果您的应用程序是MDI应用程序,那么事情会变得有点混乱,因为您需要使用IntPtr 所有这些对话框实例的对话框来使用这些Win32 PInvokes方法。
所以我决定创建两个类,CFileOpenDlgThreadApartmentSafe并CFileSaveDlgThreadApartmentSafe使用CFileDlgBase 以通用方法命名的基类和文件对话框的成员。
我的目标是拥有对话类:
- 具有与默认.NET对话框相当的属性
- 可从STA和MTA线程调用者调用
- 与原始对话框一样的模态行为
- 不使用
static PInvoke方法
这些类可以在FileDialogsThreadAppartmentSafe 程序集中找到。
如何使用代码
在项目中引用程序集FileDialogsThreadAppartmentSafe.dll,并按以下方式使用这些类:
CFileOpenDlgThreadApartmentSafe dlg = new CFileOpenDlgThreadApartmentSafe();
dlg.Filter = "Text file (*.txt)|*.txt";
dlg.DefaultExt = "txt"; Point ptStartLocation = new Point(this.Location.X, this.Location.Y); dlg.StartupLocation = ptStartLocation; DialogResult res = dlg.ShowDialog(); if (res != System.Windows.Forms.DialogResult.OK)
return; MessageBox.Show(string.Format("Open file {0}", dlg.FilePath));
第二个项目是使用两个对话框以及原始基本实现的示例。

在第13行的Program.cs文件中,您可以看到main方法被标记为[MTAThread]。这就是为什么当您点击标记为“不安全”的按钮时,您将收到上述异常。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms; namespace FileDialogTest
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[MTAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}
兴趣点
实现中最有趣的部分是调用ShowDialog()两个类的方法。此方法定义为:
public virtual DialogResult ShowDialog()
在CFileDlgBase 基类内。
这是类中ShowDialog 方法的实现CFileOpenDlgThreadApartmentSafe 。
复制代码public override DialogResult ShowDialog()
{
DialogResult dlgRes = DialogResult.Cancel;
Thread theThread = new Thread((ThreadStart)delegate
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Multiselect = false;
ofd.RestoreDirectory = true; if (!string.IsNullOrEmpty(this.FilePath))
ofd.FileName = this.FilePath;
if (!string.IsNullOrEmpty(this.Filter))
ofd.Filter = this.Filter;
if (!string.IsNullOrEmpty(this.DefaultExt))
ofd.DefaultExt = this.DefaultExt;
if (!string.IsNullOrEmpty(this.Title))
ofd.Title = this.Title;
if (!string.IsNullOrEmpty(this.InitialDirectory))
ofd.InitialDirectory = this.InitialDirectory; //Create a layout dialog instance on the current thread to align the file dialog Form
frmLayout = new Form(); if (this.StartupLocation != null)
{ //set the hidden layout form to manual form start position
frmLayout.StartPosition = FormStartPosition.Manual; //set the location of the form
frmLayout.Location = this.StartupLocation;
frmLayout.DesktopLocation = this.StartupLocation;
} //the layout form is not visible
frmLayout.Width = 0;
frmLayout.Height = 0;
dlgRes = ofd.ShowDialog(frmLayout); if (dlgRes == DialogResult.OK)
this.FilePath = ofd.FileName;
}); try
{
//set STA as the Open file dialog needs it to work
theThread.TrySetApartmentState(ApartmentState.STA); //start the thread
theThread.Start(); // Wait for thread to get started
while (!theThread.IsAlive) { Thread.Sleep(1); } // Wait a tick more (@see: http://scn.sap.com/thread/45710)
Thread.Sleep(1); //wait for the dialog thread to finish
theThread.Join(); DialogSuccess = true;
}
catch (Exception err)
{
DialogSuccess = false;
} return (dlgRes);
}
该方法在单线程单元模式下启动一个新线程,并创建一个不可见的对话框实例,用作Win32 文件对话框的父对象。这样,我们就不需要处理IntPtr在不同线程上创建的实例或实例。显示对话框后,该方法等待对话框线程完成threads Join 方法。即使在新线程实例上创建了真实文件对话框,阻塞调用程序线程也会产生模态对话框行为。
C#线程安全打开/保存文件对话框的更多相关文章
- MFC打开/保存文件对话框:CFileDialog
MFC打开/保存文件对话框:CFileDialog CFileDialog 文件选择对话框的使用:首先构造一个对象并提供相应的参数,构造函数原型如下: CFileDialog::CFileDial ...
- Winform控件:保存文件对话框(SaveFileDialog)
SaveFileDialog用于保存文件 1.新建Winform窗体应用程序,命名为SaveFileDialogDemo. 2.在界面上添加一个按钮的控件(用于打开保存文件对话框),添加文本控件,用于 ...
- C#项目打开/保存文件夹/指定类型文件,获取路径
C#项目打开/保存文件夹/指定类型文件,获取路径 转:http://q1q2q363.xiaoxiang.blog.163.com/blog/static/1106963682011722424325 ...
- ASP.NET—016:ASP.NET中保存文件对话框
本想在asp.net中使用savediallog保存文件,结果提示:当应用程序不是以 UserInteractive 模式执行时显示模式对话框或窗口是无效操作. 在ASP.NET中使用例如以下方式.保 ...
- Delphi的保存文件对话框-TsaveDialog
TsaveDialog继承于TOpenDialog,只介绍以下几个内容: 1.TsaveDialog如何设定为保存的默认路径是当前程序所在的文件夹: 默认目录是当前程序所在目录应设置属性Initial ...
- c# 打开、保存文件对话框 和 文件夹选择对话框
1. OpenFileDialog openImageDialog = new OpenFileDialog(); openImageDialog.Filter = "Image Files ...
- java中文件保存、打开文件对话框
package com.soft.test; //AWT: FileDialog类 + FilenameFilter类 可以实现本功能 //Swing: JFileChooser类 + FileFil ...
- 【转】python qt(pyqt)的文件打开、文件保存、文件夹选择对话框
import PyQt4.QtCore,PyQt4.QtGui # 获取文件路径对话框 file_name = QFileDialog.getOpenFileName(self,"open ...
- 12.JAVA之GUI编程打开与保存文件
功能:java图形用户界面开发,练习打开保存文件 代码如下: import java.awt.FileDialog; import java.awt.Frame; import java.awt.Me ...
随机推荐
- php 设计模式之工厂模式
php 设计模式之工厂模式 简介: 在开发大型系统过程中,往往会出现这样一种情况: 我有一部分基础数据,是类classA是从数据库A读取出来的,其他很多的功能都是基于这个基础数据来操作的.现在呢,我想 ...
- linux 应用软件集合
史上最全面的Linux应用软件大集合 | 博客水木 1. 生产力 Linux 桌面的便利贴:Stickynotes sudo add-apt-repository ppa:umang/indicato ...
- xBIM 基础15 IFC导出Excel报表
系列目录 [已更新最新开发文章,点击查看详细] IFC导出Excel空间报表文件 本篇将向您展示从IFC文件读取数据所需的一些概念.它使用IFC4接口,适用于IFC2x3和IFC4型号.要创建 ...
- 「JavaSE 重新出发」02.02 引用数据类型
引用(复合)数据类型 1. 枚举类型 例: 枚举类型 Size 的声明: enum Size { SMALL, MEDIUM, LARGE, EXTRA_LARGE }; 声明 Size 类型变量: ...
- oracle数据库的关闭
数据库停止: shutdown normal 无新连接 等待当前会话结束 等待当前事务结束 强制检查点并关闭文件(一致性关闭) shutdown transactional 无新连接 结束当前会话 等 ...
- php查询字符串是否存在 strpos
/*** 查询字符是否存在于某字符串** @param $haystack 字符串* @param $needle 要查找的字符* @return bool*/function str_exists( ...
- Python内置数据结构之字符串str
1. 数据结构回顾 所有标准序列操作(索引.切片.乘法.成员资格检查.长度.最小值和最大值)都适用于字符串,但是字符串是不可变序列,因此所有的元素赋值和切片赋值都是非法的. >>> ...
- 洛谷P2766 最长不下降子序列问题 网络流_DP
Code: #include<cstdio> #include<iostream> #include<vector> #include<algorithm&g ...
- Spring MVC 搭建过程中web.xml配置引入文件的路径问题
为啥要说一下这么low的问题,因为我是一个比较low的人,哈哈.本来我技术有限,没事干自己撘个环境找找乐趣,结果被各种基础问题,弄的一脸蒙蔽.算了不多说,直接说问题. 1.首先说一下java编译后的文 ...
- 第五周-磁盘分区GPT、shell脚本练习、lvm详解
1. 描述GPT是什么,应该怎么使用 Linux中磁盘分区分为MBR和GPT. MBR全称为Master Boot Record,为主引导记录,是传统的分区机制,应用于绝大多数使用的BIOS的PC设备 ...