C# 尝试使用C中新的IFileDialog和IFileOpendDialog接口以最少的代码打开文件对话框
我正在尝试显示一个标准的打开文件对话框,它可以使用Visual Studio 2010 C中的IFileOpenDialog界面来选择文件夹 我试图使用最少的代码,所以我只在接口中定义了我需要的方法:C# 尝试使用C中新的IFileDialog和IFileOpendDialog接口以最少的代码打开文件对话框,c#,interface,com-interop,openfiledialog,C#,Interface,Com Interop,Openfiledialog,我正在尝试显示一个标准的打开文件对话框,它可以使用Visual Studio 2010 C中的IFileOpenDialog界面来选择文件夹 我试图使用最少的代码,所以我只在接口中定义了我需要的方法: using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; // Disable warning CS0108: 'x' hides inherited member 'y'
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// Disable warning CS0108: 'x' hides inherited member 'y'. Use the new keyword if hiding was intended.
\#pragma warning disable 0108
namespace FolderDialog
{
internal static class IIDGuid
{
internal const string IModalWindow = "b4db1657-70d7-485e-8e3e-6fcb5a5c1802";
internal const string IFileDialog = "42f85136-db7e-439c-85f1-e4075d135fc8";
internal const string IFileOpenDialog = "d57c7288-d4ad-4768-be02-9d969532d960";
}
internal static class CLSIDGuid
{
internal const string FileOpenDialog = "DC1C5A9C-E88A-4dde-A5A1-60F82A20AEF7";
}
static class NativeMethods
{
[Flags]
internal enum FOS : uint
{
FOS_PICKFOLDERS = 0x00000020
}
}
[ComImport(),
Guid(IIDGuid.IModalWindow),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IModalWindow
{
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
PreserveSig]
int Show([In] IntPtr parent);
}
[ComImport(),
Guid(IIDGuid.IFileDialog),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileDialog : IModalWindow
{
// Defined on IModalWindow - repeated here due to requirements of COM interop layer
// --------------------------------------------------------------------------------
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
PreserveSig]
int Show([In] IntPtr parent);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetOptions([In] NativeMethods.FOS fos);
}
[ComImport(),
Guid(IIDGuid.IFileOpenDialog),
InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
internal interface IFileOpenDialog : IFileDialog
{
// Defined on IModalWindow - repeated here due to requirements of COM interop layer
// --------------------------------------------------------------------------------
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime),
PreserveSig]
int Show([In] IntPtr parent);
[MethodImpl(MethodImplOptions.InternalCall, MethodCodeType = MethodCodeType.Runtime)]
void SetOptions([In] NativeMethods.FOS fos);
}
// ---------------------------------------------------
// .NET classes representing runtime callable wrappers
[ComImport,
ClassInterface(ClassInterfaceType.None),
TypeLibType(TypeLibTypeFlags.FCanCreate),
Guid(CLSIDGuid.FileOpenDialog)]
internal class FileOpenDialogRCW
{
}
// ---------------------------------------------------------
// Coclass interfaces - designed to "look like" the object
// in the API, so that the 'new' operator can be used in a
// straightforward way. Behind the scenes, the C# compiler
// morphs all 'new CoClass()' calls to 'new CoClassWrapper()'
[ComImport,
Guid(IIDGuid.IFileOpenDialog),
CoClass(typeof(FileOpenDialogRCW))]
internal interface NativeFileOpenDialog : IFileOpenDialog
{
}
}
只要我打电话
IFileDialog dialog = null;
try
{
dialog = new NativeFileOpenDialog();
dialog.Show(IntPtr.Zero);
}
finally
{
if (dialog != null)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dialog);
}
它工作正常,打开文件对话框时没有任何错误
如果我尝试:
IFileDialog dialog = null;
try
{
dialog = new NativeFileOpenDialog();
dialog.SetOptions(NativeMethods.FOS.FOS_PICKFOLDERS);
dialog.Show(IntPtr.Zero);
}
finally
{
if (dialog != null)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dialog);
}
因此,如果我在Show方法调用之前添加对SetOptions方法的调用,我会得到一个异常:尝试读取或写入受保护内存。这通常表示其他内存已损坏
我尝试在.NET2.0甚至4.0上运行时得到这个
这里的错误是什么?为什么只调用Show方法有效,但如果我在失败之前尝试其他方法?如果您只是尝试打开文件夹浏览器对话框,可能会使问题过于复杂。改用这个类 这可能会有所帮助。下载后,请查看\Windows API Code Pack 1.1\source\WindowsAPICodePack\Shell\CommonFileDialogs\CommonFileDialog.cs,了解它如何从COM使用IFileDialogCustomize
希望这能有所帮助。有点晚了,但你可以在我的回答中找到C语言中IFileDialog接口的示例:还有另一个变体,允许选择非文件系统文件夹,如我的电脑/这台电脑:这里有一个类,它使用.Net专用IFileDialog接口打开Vista风格的文件夹选择器,在代码中不直接使用互操作。Net会为您解决这一问题。如果Windows版本不够高,它会返回到Vista之前的对话框。理论上应该可以在Windows7、8、9、10和更高版本中工作
using System;
using System.Reflection;
using System.Windows.Forms;
namespace Ris.Shuriken {
/// <summary>
/// Present the Windows Vista-style open file dialog to select a folder. Fall back for older Windows Versions
/// </summary>
public class FolderSelectDialog {
private string _initialDirectory;
private string _title;
private string _fileName = "";
public string InitialDirectory {
get { return string.IsNullOrEmpty(_initialDirectory) ? Environment.CurrentDirectory : _initialDirectory; }
set { _initialDirectory = value; }
}
public string Title {
get { return _title ?? "Select a folder"; }
set { _title = value; }
}
public string FileName { get { return _fileName; } }
public bool Show() { return Show(IntPtr.Zero); }
/// <param name="hWndOwner">Handle of the control or window to be the parent of the file dialog</param>
/// <returns>true if the user clicks OK</returns>
public bool Show(IntPtr hWndOwner) {
var result = Environment.OSVersion.Version.Major >= 6
? VistaDialog.Show(hWndOwner, InitialDirectory, Title)
: ShowXpDialog(hWndOwner, InitialDirectory, Title);
_fileName = result.FileName;
return result.Result;
}
private struct ShowDialogResult {
public bool Result { get; set; }
public string FileName { get; set; }
}
private static ShowDialogResult ShowXpDialog(IntPtr ownerHandle, string initialDirectory, string title) {
var folderBrowserDialog = new FolderBrowserDialog {
Description = title,
SelectedPath = initialDirectory,
ShowNewFolderButton = false
};
var dialogResult = new ShowDialogResult();
if (folderBrowserDialog.ShowDialog(new WindowWrapper(ownerHandle)) == DialogResult.OK) {
dialogResult.Result = true;
dialogResult.FileName = folderBrowserDialog.SelectedPath;
}
return dialogResult;
}
private static class VistaDialog {
private const string c_foldersFilter = "Folders|\n";
private const BindingFlags c_flags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic;
private readonly static Assembly s_windowsFormsAssembly = typeof(FileDialog).Assembly;
private readonly static Type s_iFileDialogType = s_windowsFormsAssembly.GetType("System.Windows.Forms.FileDialogNative+IFileDialog");
private readonly static MethodInfo s_createVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("CreateVistaDialog", c_flags);
private readonly static MethodInfo s_onBeforeVistaDialogMethodInfo = typeof(OpenFileDialog).GetMethod("OnBeforeVistaDialog", c_flags);
private readonly static MethodInfo s_getOptionsMethodInfo = typeof(FileDialog).GetMethod("GetOptions", c_flags);
private readonly static MethodInfo s_setOptionsMethodInfo = s_iFileDialogType.GetMethod("SetOptions", c_flags);
private readonly static uint s_fosPickFoldersBitFlag = (uint) s_windowsFormsAssembly
.GetType("System.Windows.Forms.FileDialogNative+FOS")
.GetField("FOS_PICKFOLDERS")
.GetValue(null);
private readonly static ConstructorInfo s_vistaDialogEventsConstructorInfo = s_windowsFormsAssembly
.GetType("System.Windows.Forms.FileDialog+VistaDialogEvents")
.GetConstructor(c_flags, null, new[] { typeof(FileDialog) }, null);
private readonly static MethodInfo s_adviseMethodInfo = s_iFileDialogType.GetMethod("Advise");
private readonly static MethodInfo s_unAdviseMethodInfo = s_iFileDialogType.GetMethod("Unadvise");
private readonly static MethodInfo s_showMethodInfo = s_iFileDialogType.GetMethod("Show");
public static ShowDialogResult Show(IntPtr ownerHandle, string initialDirectory, string title) {
var openFileDialog = new OpenFileDialog {
AddExtension = false,
CheckFileExists = false,
DereferenceLinks = true,
Filter = c_foldersFilter,
InitialDirectory = initialDirectory,
Multiselect = false,
Title = title
};
var iFileDialog = s_createVistaDialogMethodInfo.Invoke(openFileDialog, new object[] { });
s_onBeforeVistaDialogMethodInfo.Invoke(openFileDialog, new[] { iFileDialog });
s_setOptionsMethodInfo.Invoke(iFileDialog, new object[] { (uint) s_getOptionsMethodInfo.Invoke(openFileDialog, new object[] { }) | s_fosPickFoldersBitFlag });
var adviseParametersWithOutputConnectionToken = new[] { s_vistaDialogEventsConstructorInfo.Invoke(new object[] { openFileDialog }), 0U };
s_adviseMethodInfo.Invoke(iFileDialog, adviseParametersWithOutputConnectionToken);
try {
int retVal = (int) s_showMethodInfo.Invoke(iFileDialog, new object[] { ownerHandle });
return new ShowDialogResult {
Result = retVal == 0,
FileName = openFileDialog.FileName
};
}
finally {
s_unAdviseMethodInfo.Invoke(iFileDialog, new[] { adviseParametersWithOutputConnectionToken[1] });
}
}
}
// Wrap an IWin32Window around an IntPtr
private class WindowWrapper : IWin32Window {
private readonly IntPtr _handle;
public WindowWrapper(IntPtr handle) { _handle = handle; }
public IntPtr Handle { get { return _handle; } }
}
}
}
当然,您可以使用它的选项和它公开的属性。例如,它允许在“视景样式”对话框中进行多重选择
正如他在这篇文章的回答中提到的,他展示了如何直接使用互操作来完成这项工作。不幸的是,我在制定解决方案时还没有发现这一点。说出你的毒药 为什么使用本机方法而不是托管版本?我希望能够打开文件夹,而不是文件,托管版本只能打开文件。FolderBrowserDialog的问题是,它不允许您键入文本框或复制或粘贴,并且不显示收藏夹,有时也不显示某些网络位置。这真的很难相处。它也不允许多选。请参阅我的。我认为如果你只是复制并粘贴你链接的整个答案,这个答案会有很大的改进。@ErikE-好吧,谢谢你打电话给我,但是我现在有两个相关的答案,所以我有点在不同的版本之间纠结:-上面指向API代码包的链接断了,但是,请看。是否有任何方法(例如某些标志)允许用户同时选择文件夹或文件?或者:当用户浏览选择文件夹时,是否有任何方法也可以显示每个文件夹中的文件?@S.serpoosan只需问一个新问题!
var dialog = new FolderSelectDialog {
InitialDirectory = musicFolderTextBox.Text
Title = "Select a folder to import music from"
};
if (dialog.Show(Handle)) {
musicFolderTextBox.Text = dialog.FileName;
}