Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Trying to open a file dialog using the new IFileDialog and IFileOpenDialog interfaces in C# with minimal code [duplicate]

I'm trying to display a standard open file dialog that can select folders, using the IFileOpenDialog interface in C#, Visual Studio 2010.

I'm trying to use the minimal code, so I've only defined the methods I need in the interfaces:

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
{
}

}

If I only call

        IFileDialog dialog = null;
        try
        {
            dialog = new NativeFileOpenDialog();
            dialog.Show(IntPtr.Zero);
        }
        finally
        {
            if (dialog != null)
                System.Runtime.InteropServices.Marshal.FinalReleaseComObject(dialog);
        }

it works fine and it opens the File Dialog without any errors.

If I try:

        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);
        }

so If I add a call to the SetOptions method before the Show method call, I get an exception: "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

I get this trying to run on .Net 2.0 or even 4.0.

What is the error here? Why does just calling the Show method work, but if I try another method before it fails?

like image 697
Alex Vang Avatar asked Dec 13 '22 18:12

Alex Vang


1 Answers

Here's a class that opens a Vista-style folder picker using the .Net private IFileDialog interface, without directly using interop in the code (.Net takes care of that for you). It falls back to the pre-Vista dialog if not in a high enough Windows version. Should work in Windows 7, 8, 9, 10 and higher (theoretically).

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; } }
        }
    }
}

I developed this as a cleaned up version of .NET Win 7-style folder select dialog by Bill Seddon of lyquidity.com (I have no affiliation). I wrote my own because his solution requires an additional Reflection class that isn't needed for this focused purpose, uses exception-based flow control, doesn't cache the results of its reflection calls. Note that the nested static VistaDialog class is so that its static reflection variables don't try to get populated if the Show method is never called.

It is used like so in a Windows Form:

var dialog = new FolderSelectDialog {
    InitialDirectory = musicFolderTextBox.Text
    Title = "Select a folder to import music from"
};
if (dialog.Show(Handle)) {
    musicFolderTextBox.Text = dialog.FileName;
}

You can of course play around with its options and what properties it exposes. For example, it allows multiselect in the Vista-style dialog.

As alluded to in his answer in this thread, Simon Mourier's answer on another question shows how to do the job using interop directly. Unfortunately, I hadn't found that yet when I worked up my solution. Name your poison!

like image 200
ErikE Avatar answered Mar 15 '23 23:03

ErikE