是否可以创建/拥有非模态 .net OpenFileDialog 我在主对话框中有一个 UI 元素,始终需要可供用户按下。
不,OpenFileDialog和SaveFileDialog都派生自FileDialog,它本质上是模态的,所以(据我所知)没有办法创建它们中任何一个的非模态版本。
您可以创建一个线程并让该线程托管 OpenFileDialog。示例代码缺乏任何类型的同步,但它可以工作。
public partial class Form1 : Form
{
OFDThread ofdThread;
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ofdThread = new OFDThread();
ofdThread.Show();
}
}
public class OFDThread
{
private Thread t;
private DialogResult result;
public OFDThread()
{
t = new Thread(new ParameterizedThreadStart(ShowOFD));
t.SetApartmentState(ApartmentState.STA);
}
public DialogResult DialogResult { get { return this.result; } }
public void Show()
{
t.Start(this);
}
private void ShowOFD(object o)
{
OpenFileDialog ofd = new OpenFileDialog();
result = ofd.ShowDialog();
}
}
使用此代码,您可以添加一些内容来在 UI 线程中触发事件(调用时要小心!)以了解它们何时完成。您可以通过
访问对话框的结果DialogResult a = ofdThread.DialogResult
来自您的 UI 线程。
我知道我有点晚了,但你可以创建一个新的表单,无边框、透明或在显示范围之外,并显示修改该窗口的文件对话框。
这是一篇旧帖子,但我花了 2 天达到了我想在这里展示的结果(带有“上下文”和完整但简化的代码) @Joshua 的答案对我有用(最后当我将 true 设置为 .ConfigureAwait(true) 时,请参阅第一个代码示例)。也许根据 MSDN Threading Model 的长文,我可以少写几行,我还需要再读一遍。
我的上下文是 WPF(基本 MVVM),我必须选择一个文件才能编写一些 .CSV 备份(数据网格)。我需要(成员)函数
ChooseFileFromExtension()
与非阻塞FileDialog异步
class MainWindowExportToExcelCSV : ICommand
{
...
public async void Execute(object parameter)
{
var usr_ctrl = parameter as UserControl;
MyFileDialog fd = new MyFileDialog();
const bool WhenIComeBackIStillNeedToAccessUIObjectAndThusINeedToRetrieveMyOriginalUIContext = true;
string filename = await fd.ChooseFileFromExtension("CSV files (*.csv)|*.csv|All files (*.*)|*.*").ConfigureAwait(
WhenIComeBackIStillNeedToAccessUIObjectAndThusINeedToRetrieveMyOriginalUIContext);
Visual visual = (Visual)usr_ctrl.Content;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
//look for datagrid element
}
}
}
以及 MyFileDialog 类的代码
using Microsoft.Win32;
...
class MyFileDialog
{
//https://msdn.microsoft.com/en-us/library/ms741870(v=vs.110).aspx
//Article on Threading Model
private delegate void OneArgStrDelegate(string str);
private void MyExternalDialog(string extensions)
{
SaveFileDialog fd = new SaveFileDialog();
fd.Filter = extensions;
fd.ShowDialog();
tcs.SetResult(fd.FileName);
}
private TaskCompletionSource<string> tcs;
public Task<string> ChooseFileFromExtension(string file_ext)
{
//Cf Puppet Task in Async in C#5.0 by Alex Davies
tcs = new TaskCompletionSource<string>();
OneArgStrDelegate fetcher = new OneArgStrDelegate(this.MyExternalDialog);
fetcher.BeginInvoke(file_ext, null, null);
return tcs.Task;
}
}
fetcher.BeginInvoke()
在另一个线程中(异步)启动 SaveFileDialog
ShowDialog()
,这样主 UI 线程/窗口 (...++) 既不会被阻止也不会被禁用,就像通过简单的直接调用一样ShowDialog()
。 TaskCompletionSource<string> tcs
不是 WPF UI 对象,因此可以通过另一个“单”线程访问它。
这仍然是一个我需要进一步研究的领域。我觉得没有关于这个主题的“终极”文档/书籍(也许应该再次看一下像斯蒂芬·克利里(Stephen Cleary)这样的书籍)。这段代码至少应该改进 c-sharp-asynchronous-call-without-endinvoke
所涵盖的主题它与命名空间 Microsoft.Win32 的 FileDialog 一起使用
问题是,所有其他形式都被禁用。所以解决方案是通过
OpenFileDialog.ShowDialog()
方法禁用其他表单后立即重新启用它们。
要打开文件对话框,只需添加一行:
var dialog = new OpenFileDialog();
using (var service = new SystemDiaogService(this))
dialog.ShowDialog();
...然后使用此代码作为实现:
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
namespace MyNamespace
{
public class SystemDiaogService : IDisposable
{
private readonly IWin32Window _owner;
private readonly HookProc _hookProc;
private readonly IntPtr _hHook = IntPtr.Zero;
private WindowsFormsSynchronizationContext _synchronizationContext;
public SystemDiaogService(IWin32Window owner)
{
if (owner == null) throw new ArgumentNullException("owner");
_synchronizationContext = new WindowsFormsSynchronizationContext();
_owner = owner;
_hookProc = DialogHookProc;
_hHook = SetWindowsHookEx(WH_CALLWNDPROCRET, _hookProc, IntPtr.Zero, GetCurrentThreadId());
}
private IntPtr DialogHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
if (nCode < 0)
{
return CallNextHookEx(_hHook, nCode, wParam, lParam);
}
CWPRETSTRUCT msg = (CWPRETSTRUCT)Marshal.PtrToStructure(lParam, typeof(CWPRETSTRUCT));
IntPtr hook = _hHook;
if (msg.message == HCBT_ACTIVATE)
{
try
{
ReenableOtherForms();
}
finally
{
UnhookWindowsHookEx(_hHook);
}
}
return CallNextHookEx(hook, nCode, wParam, lParam);
}
private void ReenableOtherForms()
{
foreach (Form form in Application.OpenForms)
if (form != _owner)
_synchronizationContext.Post(
(state) =>
{
EnableWindow(form.Handle, true);
},
null);
}
public void Dispose()
{
UnhookWindowsHookEx(_hHook);
}
public delegate IntPtr HookProc(int nCode, IntPtr wParam, IntPtr lParam);
public delegate void TimerProc(IntPtr hWnd, uint uMsg, UIntPtr nIDEvent, uint dwTime);
private const int WH_CALLWNDPROCRET = 12;
private const int HCBT_ACTIVATE = 5;
[DllImport("kernel32.dll")]
static extern int GetCurrentThreadId();
[DllImport("user32.dll")]
public static extern IntPtr SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);
[DllImport("user32.dll")]
public static extern int UnhookWindowsHookEx(IntPtr idHook);
[DllImport("user32.dll")]
public static extern IntPtr CallNextHookEx(IntPtr idHook, int nCode, IntPtr wParam, IntPtr lParam);
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool EnableWindow(IntPtr hWnd, bool bEnable);
[StructLayout(LayoutKind.Sequential)]
public struct CWPRETSTRUCT
{
public IntPtr lResult;
public IntPtr lParam;
public IntPtr wParam;
public uint message;
public IntPtr hwnd;
};
}
}
PS
这个想法基于众所周知的
DialogCenteringService
,提到过here。