Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / C#

Full implementation of IShellBrowser

Rate me:
Please Sign up or sign in to vote.
4.86/5 (29 votes)
5 May 2009CPOL4 min read 158.7K   3.2K   113   51
A VS-like open and save file dialog implementation.

FileDialogs.png

Introduction

This library contains a full implementation of the IShellBrowser interface. The implementation is used in a VS-like OpenFileDialog and SaveFileDialog implementation. The components can be used the same way the System.Windows.Forms.OpenFileDialog and System.Windows.Forms.SaveFileDialog are used. The library also contains the SelectFolderDialog component which can be used as a replacement to the System.Windows.Forms.FolderBrowserDialog.

Background

Ever since I first read the article, Implementing IShellBrowser to host IShellView, I wanted to implement the same thing using C#, but after several unsuccessful tries, I had to put it aside. But, when I really needed a customized implementation of the Open and Save file dialogs in one of my applications, and using templates wasn't sufficient, I had to retry one more time. So, after re-declaring my shell interfaces a couple of times, I finally succeeded. I decided to create this library and write this article to demonstrate the IShellBrowser interface.

Using the Code

OpenFileDialog and SaveFileDialog are very similar to and contain almost the same properties as System.Windows.Forms.OpenFileDialog and System.Windows.Forms.SaveFileDialog respectively. This means that they can be used in almost the same way. The SelectFolderDialog however doesn't look and act as the System.Windows.Forms.FolderBrowserDialog but can still be used in a similar way.

All three components share the following two properties:

  • Options
  • Places

The Options property controls the drop-down items of the OK split button. You can either add the strings using the Designer or by using code, see the following example:

C#
OpenFileDialog openFileDialog1 = new OpenFileDialog();
openFileDialog1.Options.Add("Open");
openFileDialog1.Options.Add("Open As Read Only");

You can then access the selected option through the SelectedOptionIndex property:

C#
if (openFileDialog1.ShowDialog(this) == DialogResult.OK)
{
    if (openFileDialog.SelectedOptionIndex == 1)
        MessageBox.Show(this, "Open as read only selected");
}

The Places property contains the collection of items shown in the places bar. By default it contains the Desktop, My Documents and My Computer. You can eiter change the items using the Designer or by code. It is very easy to customize the places bar using the Places Editor.

Places Editor

Or if you want to change the places bar programatically, see the following example:

C#
openFileDialog1.Places.Add(new FileDialogPlace(SpecialFolder.Desktop));
openFileDialog1.Places.Add(new FileDialogPlace(SpecialFolder.MyDocuments));
openFileDialog1.Places.Add(new FileDialogPlace(SpecialFolder.MyComputer));
CustomFileDialogPlace customPlace1 = new CustomFileDialogPlace(
    "C:\Documents and Settings\[User]\My Documents\Visual Studio 2005\Projects");
customPlace1.Text = "My Projects";
openFileDialog1.Places.Add(customPlace1);

Registry Support

The registry is used to store window position and file name MRU, but the code is wrapped in #if blocks. If you want to store the information in the registry, add the following code to the top of FileDialog.cs: #define REGISTRY_SUPPORT.

How to Implement IShellBrowser

I'm now going to explain how to implement the IShellBrowser in your own dialog. Start by including NativeMethods.cs (which is included in the source code) into your project, and perhaps change the namespace. Then, add the following attributes and inheritance to your form.

C#
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
public partial class Form1: Form, NativeMethods.IShellBrowser, 
                            NativeMethods.IServiceProvider
{
    ...
}

The IShellBrowser interface is used to host the IShellView, and if we don't inherit IServiceProvider, the IShellView will open a new Explorer window every time we browse to a new folder.

Now, add the following member fields:

C#
private NativeMethods.IShellView m_shellView;          // The current IShellView
private IntPtr m_hWndListView;                         // The handle of the listview
private NativeMethods.IShellFolder m_desktopFolder;    // The desktop IShellFolder
private NativeMethods.IShellFolder m_currentFolder;    // The current IShellFolder

private IntPtr m_pidlAbsCurrent;                       // The current absolute pidl
private IntPtr m_desktopPidl;                          // The desktop pidl

private NativeMethods.FOLDERVIEWMODE m_viewMode = NativeMethods.FOLDERVIEWMODE.FVM_LIST;
private NativeMethods.FOLDERFLAGS m_flags = (NativeMethods.FOLDERFLAGS.FWF_SHOWSELALWAYS |
                                             NativeMethods.FOLDERFLAGS.FWF_SINGLESEL |
                                             NativeMethods.FOLDERFLAGS.FWF_NOWEBVIEW);

In the constructor, initialize the desktop PIDL and IShellFolder.

C#
public Form1()
{
    InitializeComponent();

    NativeMethods.Shell32.SHGetSpecialFolderLocation(IntPtr.Zero, 
                  (int)SpecialFolder.Desktop, out m_desktopPidl);
    IntPtr desktopFolderPtr;
    NativeMethods.Shell32.SHGetDesktopFolder(out desktopFolderPtr);
    m_desktopFolder = (NativeMethods.IShellFolder)
                       Marshal.GetObjectForIUnknown(desktopFolderPtr);
}

Now, it's time to implement the IShellBrowser interface.

C#
// Return the window handle.
int NativeMethods.IShellBrowser.GetWindow(out IntPtr hwnd)
{
    hwnd = Handle;
    return NativeMethods.S_OK;
}

int NativeMethods.IShellBrowser.ContextSensitiveHelp(int fEnterMode)
{
    return NativeMethods.E_NOTIMPL;
}

// Allows the container to insert its menu groups into the composite menu
// that is displayed when an extended namespace is being viewed or used.
int NativeMethods.IShellBrowser.InsertMenusSB(IntPtr hmenuShared, 
                                              IntPtr lpMenuWidths)
{
    return NativeMethods.E_NOTIMPL;
}

// Installs the composite menu in the view window.
int NativeMethods.IShellBrowser.SetMenuSB(IntPtr hmenuShared, 
    IntPtr holemenuRes, IntPtr hwndActiveObject)
{
    return NativeMethods.E_NOTIMPL;
}

// Permits the container to remove any of its menu elements from the
// in-place composite menu and to free all associated resources.
int NativeMethods.IShellBrowser.RemoveMenusSB(IntPtr hmenuShared)
{
    return NativeMethods.E_NOTIMPL;
}

// Sets and displays status text about the in-place object in the
// container's frame-window status bar.
int NativeMethods.IShellBrowser.SetStatusTextSB(IntPtr pszStatusText)
{
    return NativeMethods.E_NOTIMPL;
}

// Tells Microsoft Windows Explorer
// to enable or disable its modeless dialog boxes.
int NativeMethods.IShellBrowser.EnableModelessSB(bool fEnable)
{
    return NativeMethods.E_NOTIMPL;
}

// Translates accelerator keystrokes intended
// for the browser's frame while the view is active.
int NativeMethods.IShellBrowser.TranslateAcceleratorSB(IntPtr pmsg, short wID)
{
    return NativeMethods.S_OK;
}

// Informs Microsoft Windows Explorer to browse to another folder.
int NativeMethods.IShellBrowser.BrowseObject(IntPtr pidl, uint wFlags)
{
    int hr;
    IntPtr folderTmpPtr;
    NativeMethods.IShellFolder folderTmp;
    IntPtr pidlTmp;

    if (NativeMethods.Shell32.ILIsEqual(pidl, m_desktopPidl))
    {
        // pidl is desktop folder
        pidlTmp = m_desktopPidl;
        folderTmp = m_desktopFolder;
    }
    else if ((wFlags & NativeMethods.SBSP_RELATIVE) != 0)
    {
        // SBSP_RELATIVE - pidl is relative from the current folder
        if ((hr = m_currentFolder.BindToObject(pidl, IntPtr.Zero, 
            ref NativeMethods.IID_IShellFolder,
            out folderTmpPtr)) != NativeMethods.S_OK)
            return hr;
        pidlTmp = NativeMethods.Shell32.ILCombine(m_pidlAbsCurrent, pidl);
        folderTmp = (NativeMethods.IShellFolder)
                     Marshal.GetObjectForIUnknown(folderTmpPtr);
    }
    else
    {
        // SBSP_ABSOLUTE - pidl is an absolute pidl (relative from desktop)
        pidlTmp = NativeMethods.Shell32.ILClone(pidl);
        if ((hr = m_desktopFolder.BindToObject(pidlTmp, IntPtr.Zero, 
            ref NativeMethods.IID_IShellFolder,
            out folderTmpPtr)) != NativeMethods.S_OK)
            return hr;
        folderTmp = (NativeMethods.IShellFolder)
                     Marshal.GetObjectForIUnknown(folderTmpPtr);
    }

    if (folderTmp == null)
    {
        NativeMethods.Shell32.ILFree(pidlTmp);
        return NativeMethods.E_FAIL;
    }

    // Check that we have a new pidl
    if (NativeMethods.Shell32.ILIsEqual(pidlTmp, m_pidlAbsCurrent))
    {
        Marshal.ReleaseComObject(folderTmp);
        NativeMethods.Shell32.ILFree(pidlTmp);
        return NativeMethods.S_OK;
    }

    m_currentFolder = folderTmp;

    NativeMethods.FOLDERSETTINGS fs = new NativeMethods.FOLDERSETTINGS();
    NativeMethods.IShellView lastIShellView = m_shellView;

    if (lastIShellView != null)
        lastIShellView.GetCurrentInfo(ref fs);
        // Copy the old folder settings
    else
    {
        fs = new NativeMethods.FOLDERSETTINGS();
        fs.fFlags = (uint)m_flags;
        fs.ViewMode = (uint)m_viewMode;
    }

    // Create the IShellView
    IntPtr iShellViewPtr;
    hr = folderTmp.CreateViewObject(Handle, 
         ref NativeMethods.IID_IShellView, out iShellViewPtr);
    if (hr == NativeMethods.S_OK)
    {
        m_shellView = (NativeMethods.IShellView)
                       Marshal.GetObjectForIUnknown(iShellViewPtr);

        m_hWndListView = IntPtr.Zero;
        NativeMethods.RECT rc =
            new NativeMethods.RECT(8, 8,
           ClientSize.Width - 8,
           ClientSize.Height - 8);

        int res;

        try
        {
            // Create the actual list view
            res = m_shellView.CreateViewWindow(lastIShellView, ref fs, 
                  this, ref rc, ref m_hWndListView);
        }
        catch (COMException)
        {
            return NativeMethods.E_FAIL;
        }

        if (res < 0)
            return NativeMethods.E_FAIL;

        // Release the old IShellView
        if (lastIShellView != null)
        {
            lastIShellView.GetCurrentInfo(ref fs);
            lastIShellView.UIActivate((uint)
                NativeMethods.SVUIA_STATUS.SVUIA_DEACTIVATE);
            lastIShellView.DestroyViewWindow();
            Marshal.ReleaseComObject(lastIShellView);
        }

        // Set focus to the IShellView
        m_shellView.UIActivate((uint)
          NativeMethods.SVUIA_STATUS.SVUIA_ACTIVATE_FOCUS);
        m_pidlAbsCurrent = pidlTmp;
    }

    return NativeMethods.S_OK;
}

// This method is used to save and restore the persistent state for a view
// (the icon positions, the column widths,
//  and the current scroll position, for example). 
int NativeMethods.IShellBrowser.GetViewStateStream(uint grfMode, IntPtr ppStrm)
{
    return NativeMethods.E_NOTIMPL;
}

// GetControlWindow is used so views
// can directly manipulate the browser's controls.
int NativeMethods.IShellBrowser.GetControlWindow(uint id, out IntPtr phwnd)
{
    phwnd = IntPtr.Zero;
    return NativeMethods.S_FALSE;
}

// Sends control messages to either the toolbar or the status bar
// in a Microsoft Windows Explorer window.
int NativeMethods.IShellBrowser.SendControlMsg(uint id, uint uMsg, 
                  uint wParam, uint lParam, IntPtr pret)
{
    return NativeMethods.E_NOTIMPL;
}

// Retrieves the currently active (displayed) Shell view object.
int NativeMethods.IShellBrowser.QueryActiveShellView(
                  ref NativeMethods.IShellView ppshv)
{
    Marshal.AddRef(Marshal.GetIUnknownForObject(m_shellView));
    ppshv = m_shellView;
    return NativeMethods.S_OK;
}

// This method informs the browser that the view is getting the focus
// (when the mouse is clicked on the view, for example).
int NativeMethods.IShellBrowser.OnViewWindowActive(NativeMethods.IShellView pshv)
{
    return NativeMethods.E_NOTIMPL;
}

// Adds toolbar items to Microsoft Windows Explorer's toolbar.
int NativeMethods.IShellBrowser.SetToolbarItems(IntPtr lpButtons, 
                                uint nButtons, uint uFlags)
{
    return NativeMethods.E_NOTIMPL;
}

The only important methods in this simple implementation is GetWindow, BrowseObject, and QueryActiveShellView. All the methods aren't necessary; also, note that the BrowseObject method just contains the basic functionality. In the demo, the BrowseObject contains more functionality such as go to parent.

Let's continue by implementing the IServiceProvider interface.

C#
int NativeMethods.IServiceProvider.QueryService(ref Guid guidService, 
                  ref Guid riid, out NativeMethods.IShellBrowser ppvObject)
{
    if (riid == NativeMethods.IID_IShellBrowser)
    {
        ppvObject = this;
        return NativeMethods.S_OK;
    }

    ppvObject = null;
    return NativeMethods.E_NOINTERFACE;
}

When you double-click on a folder, the IShellView first calls IServiceProvier::QueryService() for an IShellBrowser interface, and it finds that the IShellBrowser::BrowseObject() is invoked with the PIDL of the new folder and does nothing (i.e., waits for you to open the new folder). If you don't implement IServiceProvider (or don't return an IShellBrowser from QueryService), IShellView just launches a whole new Windows Explorer to display the new folder.

Now, the only thing that remains is the startup and cleanup.

C#
protected override void OnHandleCreated(EventArgs e)
{
    base.OnHandleCreated(e);

    ((NativeMethods.IShellBrowser)this).BrowseObject(m_desktopPidl, 
                                        NativeMethods.SBSP_ABSOLUTE);
}

protected override void OnHandleDestroyed(EventArgs e)
{
    m_pidlAbsCurrent = IntPtr.Zero;

    // Release the IShellView
    if (m_shellView != null)
    {
        m_shellView.UIActivate((uint)NativeMethods.SVUIA_STATUS.SVUIA_DEACTIVATE);
        m_shellView.DestroyViewWindow();
        Marshal.ReleaseComObject(m_shellView);
        m_shellView = null;
    }

    base.OnHandleDestroyed(e);
}

Points of Interest

One of the hardest things was to figure out which interfaces should be used and how they should be declared. I had to manually handle many things such as keyboard input and filtering. During the development, I found out the following:

  • The IShellBrowser.BrowseObject method isn't called for 'My Documents'.
  • The WM_GETISHELLBROWSER message is never sent.

History

  • 30 Aug 2008

    Initial posting.

  • 24 Sep 2008
    • Fixed some issues concerning the OK and Cancel buttons, when the Enter key was pressed.
    • Fixed a simple bug causing the overwrite prompt to show twice.
    • Fixed a problem with the auto-complete when using absolute paths.
    • Added the SelectFolderDialog component which can be used as a more advanced FolderBrowserDialog.
  • 10 Oct 2008
    • Updated article image.
    • Fixed a problem when creating a new folder the traditional way.
    • Fixed an issue in the BrowseObject method causing desktop pidl to be freed.
    • After comparing with Windows and the VS open file dialog, I removed the part in the BrowseObject method where i was checking if I had a new pidl.
    • Fixed an issue with some keys (Enter, Escape, Delete, Left, Up, Right, Down) when renaming an item.
  • 5 May 2009
    • Added support for shortcuts.
    • Fixed some small bugs when saving.
    • Fixed issue concerning the Enter key.
    • Replaced the OK Button with a SplitButton control.
    • Disabled the selection of special folders in the SelectFolderDialog.
    • Added the ability to customize the places in the places bar through a dialog.
    • Removed properties CheckFileExists and CheckPathExists because neihter of them affects the behavior of the FileDialog.
    • Changed the behavior of the FileName property.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer IDesignIT Kungsbacka
Sweden Sweden
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionThank you! Pin
Member 141148624-Apr-12 1:33
Member 141148624-Apr-12 1:33 
Generalnice work, specifics on error when running ui demo, or compiling source demo, in W7 [modified] Pin
BillWoodruff16-Jun-11 6:00
professionalBillWoodruff16-Jun-11 6:00 
GeneralCan it be shown modeless? Pin
Mizan Rahman6-May-11 11:33
Mizan Rahman6-May-11 11:33 
GeneralSeem to be nice but I can't get it working Pin
Sabrina Hope4-Mar-11 9:21
Sabrina Hope4-Mar-11 9:21 
GeneralMy vote of 5 Pin
Jeroen Richters4-Oct-10 2:56
professionalJeroen Richters4-Oct-10 2:56 
GeneralW7/Vista Test unsuccessful [modified] Pin
Member 9045528-Jun-10 1:07
Member 9045528-Jun-10 1:07 
GeneralRe: W7/Vista Test unsuccessful Pin
richi_87-Jan-11 4:41
richi_87-Jan-11 4:41 
BugRe: W7/Vista Test unsuccessful Pin
N Jones19-Sep-11 5:45
N Jones19-Sep-11 5:45 
GeneralRe: W7/Vista Test unsuccessful Pin
Montasser Ben Ouhida1-Nov-11 6:18
Montasser Ben Ouhida1-Nov-11 6:18 
GeneralRe: W7/Vista Test unsuccessful Pin
N Jones1-Nov-11 11:08
N Jones1-Nov-11 11:08 
GeneralRe: W7/Vista Test unsuccessful Pin
Montasser Ben Ouhida25-Nov-11 3:33
Montasser Ben Ouhida25-Nov-11 3:33 
QuestionAny ideas for how to browse to a server share when a UNC path is entered into Filename combo box? Pin
elothen22-Feb-10 11:29
elothen22-Feb-10 11:29 
AnswerRe: Any ideas for how to browse to a server share when a UNC path is entered into Filename combo box? Pin
Frohmen23-Feb-10 1:45
Frohmen23-Feb-10 1:45 
GeneralRe: Any ideas for how to browse to a server share when a UNC path is entered into Filename combo box? Pin
elothen23-Feb-10 5:52
elothen23-Feb-10 5:52 
GeneralRe: Any ideas for how to browse to a server share when a UNC path is entered into Filename combo box? Pin
elothen23-Feb-10 7:54
elothen23-Feb-10 7:54 
GeneralMissing property to set the sorted column in detail view Pin
christoph344-Sep-09 23:18
christoph344-Sep-09 23:18 
QuestionIs it a bug? Pin
Gilvinit17-Feb-09 20:43
Gilvinit17-Feb-09 20:43 
AnswerRe: Is it a bug? Pin
Frohmen18-Feb-09 4:05
Frohmen18-Feb-09 4:05 
GeneralRe: Is it a bug? [modified] Pin
Paul Selormey25-Aug-09 7:48
Paul Selormey25-Aug-09 7:48 
Thanks for this great work. I am currently trying to use it in my open source project and it is very useful work.

Frohmen wrote:
There isn't any easy way to fix this...

Edit: I have now found the actual source of the problem, it is with the DropDownHeight, not the DropDownWidth.
Just deleting the DropDownHeight set line, solves the problem.
Also, overriding the OnDropDown method instead of the event seems to produce a better results - do not know why.

There is another problem, the processing in the
protected override void WndProc(ref Message m)


It fails in dual monitors, since you are using the primary screen for your computations, moving the dialog to the secondary monitor will cause the drop-down to be still displayed in the primary monitor.
I do not know why it is needed, but I simply commented it out.

Again, thanks.

Best regards,
Paul.


Jesus Christ is LOVE! Please tell somebody.

modified on Tuesday, August 25, 2009 2:22 PM

QuestionIs it possible to replace windows file explorer with such appilication? Pin
Gilvinit15-Feb-09 9:42
Gilvinit15-Feb-09 9:42 
GeneralError - help needed Pin
Gilvinit3-Feb-09 2:37
Gilvinit3-Feb-09 2:37 
AnswerRe: Error - help needed Pin
Frohmen3-Feb-09 7:53
Frohmen3-Feb-09 7:53 
GeneralRe: Error - help needed Pin
Gilvinit3-Feb-09 9:01
Gilvinit3-Feb-09 9:01 
GeneralSmall bug ! Pin
Rumio7-Dec-08 17:41
Rumio7-Dec-08 17:41 
GeneralRe: Small bug ! Pin
Gee.21-Jan-09 10:59
Gee.21-Jan-09 10:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.