Ale*_*ang 4 c# interface com-interop openfiledialog
我正在尝试使用 C#、Visual Studio 2010 中的 IFileOpenDialog 接口显示可以选择文件夹的标准打开文件对话框。
我正在尝试使用最少的代码,所以我只在接口中定义了我需要的方法:
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
{
}
Run Code Online (Sandbox Code Playgroud)
}
如果我只打电话
IFileDialog dialog = null;
try
{
dialog = new NativeFileOpenDialog();
dialog.Show(IntPtr.Zero);
}
finally
{
if (dialog != null)
System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dialog);
}
Run Code Online (Sandbox Code Playgroud)
它工作正常,它打开文件对话框没有任何错误。
如果我尝试:
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);
}
Run Code Online (Sandbox Code Playgroud)
因此,如果我在 Show 方法调用之前添加对 SetOptions 方法的调用,则会出现异常:“尝试读取或写入受保护的内存。这通常表明其他内存已损坏。”
我尝试在 .Net 2.0 甚至 4.0 上运行。
这里的错误是什么?为什么只调用 Show 方法有效,但如果我在失败之前尝试另一种方法?
适用于 Microsoft® .NET Framework的Windows® API 代码包可以提供帮助。下载后,查看“\Windows API Code Pack 1.1\source\WindowsAPICodePack\Shell\CommonFileDialogs\CommonFileDialog.cs”以了解它如何使用来自 COM 的 IFileDialogCustomize。
希望这可以帮助。
这是一个使用 .Net 私有IFileDialog接口打开 Vista 风格的文件夹选择器的类,而不直接在代码中使用互操作(.Net 会为您处理)。如果 Windows 版本不够高,它会退回到 Vista 之前的对话框。应该适用于 Windows 7、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; } }
}
}
}
Run Code Online (Sandbox Code Playgroud)
我将其开发为.NET Win 7 样式文件夹选择对话框的清理版本,由lyquidity.com的 Bill Seddon (我没有从属关系)。我自己编写了自己的解决方案,因为他的解决方案需要一个额外的 Reflection 类,此重点目的不需要该类,使用基于异常的流控制,不缓存其反射调用的结果。请注意,VistaDialog如果Show从未调用该方法,则嵌套静态类使其静态反射变量不会尝试填充。
它在 Windows 窗体中像这样使用:
var dialog = new FolderSelectDialog {
InitialDirectory = musicFolderTextBox.Text
Title = "Select a folder to import music from"
};
if (dialog.Show(Handle)) {
musicFolderTextBox.Text = dialog.FileName;
}
Run Code Online (Sandbox Code Playgroud)
您当然可以使用它的选项以及它公开的属性。例如,它允许在 Vista 风格的对话框中进行多选。
正如在本主题中的回答中提到的那样,Simon Mourier 对另一个问题的回答展示了如何直接使用互操作来完成这项工作。不幸的是,当我制定我的解决方案时,我还没有找到。说出你的毒药!
| 归档时间: |
|
| 查看次数: |
9416 次 |
| 最近记录: |