python文件打开方式如何将opendlg变为idle,C#线程安全打开/保存文件对话框

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

3edb72a90f5f1c5d53ebc6604d6bf952.png

但也存在一些问题,其中一些问题在本文中进行了讨论。

背景

一旦开发了更大的应用程序,您可能会注意到,一旦将调用者线程单元状态设置为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)

2ceb63546c38cec69d01045c0e2cd665.png

通过在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成员。IntPtrWin32System.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));

第二个项目是使用两个对话框以及原始基本实现的示例。

cbf4c4ff0bbe7948aa1a1eb8504eedb5.png

在第13行的Program.cs文件中,您可以看到main方法被标记为[MTAThread]。这就是为什么当您点击标记为“不安全”的按钮时,您将收到上述异常。

隐藏   复制代码

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Forms;

namespace FileDialogTest { static class Program { /// ///The main entry point for the application. /// [MTAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form1()); } } }

兴趣点

实现中最有趣的部分是调用ShowDialog()两个类的方法。此方法定义为:

隐藏   复制代码

public virtual DialogResult ShowDialog()

在CFileDlgBase基类内。

这是类中ShowDialog方法的实现CFileOpenDlgThreadApartmentSafe。

隐藏   收缩

090cbadba5931d31e41dbc861d9a14da.png   复制代码

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在不同线程上创建的实例或实例。显示对话框后,该方法等待对话框线程完成threadsJoin方法。即使在新线程实例上创建了真实文件对话框,阻塞调用程序线程也会产生模态对话框行为。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值