Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Extended Folder Browser

0.00/5 (No votes)
20 Jun 2006 1  
Adding functionality to a Folder Browser Dialog control.

Folder Browser with disabled 'Make New Folder' button

Introduction

Have you ever tried to change anything in the folder browser dialog of C#?

In this article, I will show you how you can make changes to the dialog. For example, what if we want to disable the �Make New Folder� button every time a CD drive is selected? Or what if we want the user to select only from existing folders, except one folder that is to be allowed to make child folders?

Background

.NET has a FolderBrowserDialog control which is, basically, a wrapper for the Win32 function SHBrowseForFolder. The only issue here is that this class is a sealed class, so we can�t derive from it and add functionality. This control doesn�t provide any event we can register to and execute our code there. So basically, if you are satisfied with the control as it is, then you are OK. But once you need to do a small change, you have to figure out how the control really works.

In this article, we will see, step by step, how we can disable the �Make New Folder� every time a CD drive is selected.

To achieve our goal, we will have to overcome two main issues:

  1. Getting the control to fire an event every time a selection is changed.
  2. Disabling the button.

Using the code

The first thing we are going to do is create an ExtendedFolderBrowser class. This class will hold a private class, InternalFolderBrowser, which will be derived from CommonDialog. InternalFolderBrowser is, basically, an implementation of the folder browser dialog, but this implementation will allow us to extend the functionality and add our new add-ins in the parent class. When deriving from the CommonDialog class, we need to implement two methods: Reset and Rundialog.

private class InternalFolderBrowser : CommonDialog
{
    private string m_selectedPath = null;
    private Environment.SpecialFolder m_rootFolder;
    public event EventHandler SelectedFolderChanged;
    private string m_descriptionText = String.Empty;
    
    public override void Reset()
    {
        m_rootFolder = Environment.SpecialFolder.Desktop;
        m_selectedPath = string.Empty;
    }

    protected override bool RunDialog(System.IntPtr hwndOwner)
    {
        IntPtr ptr1 = IntPtr.Zero;
        bool flag1 = false;
        Win32API.SHGetSpecialFolderLocation(hwndOwner, 
                         (int)m_rootFolder, ref ptr1);
        if (ptr1 == IntPtr.Zero)
        {
            Win32API.SHGetSpecialFolderLocation(hwndOwner, 0, ref ptr1);
            if (ptr1 == IntPtr.Zero)
            {
                throw new Exception("FolderBrowserDialogNoRootFolder");
            }
        }

        //Initialize the OLE to current thread.

        Application.OleRequired();
        IntPtr ptr2 = IntPtr.Zero;
        try
        {
            Win32API.BROWSEINFO browseinfo1 = new Win32API.BROWSEINFO();
            IntPtr ptr3 = Marshal.AllocHGlobal((int) 
                          (260 * Marshal.SystemDefaultCharSize));
            IntPtr ptr4 = Marshal.AllocHGlobal((int) 
                          (260 * Marshal.SystemDefaultCharSize));
            Win32API.BrowseCallbackProc proc1 = 
              new Win32API.BrowseCallbackProc(
              this.FolderBrowserDialog_BrowseCallbackProc);
            browseinfo1.pidlRoot = ptr1;
            browseinfo1.hwndOwner = hwndOwner;
            browseinfo1.pszDisplayName = ptr3;
            browseinfo1.lpszTitle = m_descriptionText;
            browseinfo1.ulFlags = 0x40;
            browseinfo1.lpfn = proc1;
            browseinfo1.lParam = IntPtr.Zero;
            browseinfo1.iImage = 0;
            ptr2 = Win32API.SHBrowseForFolder(browseinfo1);

            string s = Marshal.PtrToStringAuto(ptr3);

            if (ptr2 != IntPtr.Zero)
            {
                Win32API.SHGetPathFromIDList(ptr2, ptr4);
                this.m_selectedPath = Marshal.PtrToStringAuto(ptr4);
                Marshal.FreeHGlobal(ptr4);
                Marshal.FreeHGlobal(ptr3);
                flag1 = true;
            }
        }
        finally
        {
            Win32API.IMalloc malloc1 = GetSHMalloc();
            malloc1.Free(ptr1);
            if (ptr2 != IntPtr.Zero)
            {
                malloc1.Free(ptr2);
            }
        }
        return flag1;
    }

When we create the BROWSEINFO struct, we pass a delegate to the function that will be called on every change in the control:

private int FolderBrowserDialog_BrowseCallbackProc(IntPtr hwnd, 
                         int msg, IntPtr lParam, IntPtr lpData)
{
    switch (msg)
    {
        case Win32API.BFFM_INITIALIZED:
            if (m_selectedPath != string.Empty)
            {
                Win32API.SendMessage(new HandleRef(null, hwnd), 
                                     0x467, 1, m_selectedPath);
            }
            break;

        case Win32API.BFFM_SELCHANGED: //Selction Changed

        {
            IntPtr ptr1 = lParam;
            if (ptr1 != IntPtr.Zero)
            {
                IntPtr ptr2 = Marshal.AllocHGlobal((int) 
                              (260 * Marshal.SystemDefaultCharSize));
                bool flag1 = Win32API.SHGetPathFromIDList(ptr1, ptr2);
                m_selectedPath = Marshal.PtrToStringAuto(ptr2);

                //Fire Event

                if(SelectedFolderChanged != null)
                {
                    SelectedFolderChanged(this,null);
                }
                Marshal.FreeHGlobal(ptr2);
                Win32API.SendMessage2(new HandleRef(null, hwnd), 
                                      0x465, 0, flag1 ? 1 : 0);
            }
            break;
        }
    }
    return 0;
}

Every time a selection is changed, we will be notified by the BFFM_SELCHANGED message. Once we receive a message, we will fire an event so our parent class, ExtendedFolderBrowser, will be notified too.

The last thing we should do is register the event of the inner class and disable the button.

When the selection changes, we will execute a CheckState method:

/// <summary>

/// Check if we should disable the 'Make New Folder' button

/// </summary>

private void CheckState()
{
    if(m_ShowNewButtonHandler != null)
    {
        if(m_ShowNewButtonHandler(SelectedPath))
        {
            //Disabel the button

            UpdateButtonState(GetButtonHandle(IntPtr.Zero), false);
        }
        else
        {
            //Enable the button

            UpdateButtonState(GetButtonHandle(IntPtr.Zero),true);
        }
    }
}

m_ShowNewButtonHandler is a delegate to the user function that will check if we should disable the button according to the current selection. When a client creates a dialog, it will pass a delegate to its function to make a decision about the state of the button.

To change the state of the button, we simply use the EnableWindow API:

private void UpdateButtonState(IntPtr handle, bool state)
{
    if(handle != IntPtr.Zero)
    {
        Win32API.EnableWindow(handle,state);
    }
}

The last tricky thing left to do is to get the handle for the button. To get the handle, we will search for the class name "#32770", this is the class of the folder browser dialog. The problem is that this class name is not unique, so we have to make another check whether this handle we found is actually from the same thread that we are running. If it is from the same thread, bingo!, we have found the handler for the dialog control, otherwise we will continue searching.

Once we have found the dialog handle, all that is left is to return the first child handle of the Button type. This will be the handle to the 'Make New Folder" button.

private bool isFromTheSameThread(IntPtr windowHandle)
{
    //Get the thread that running given handler

    IntPtr activeThreadID = Win32API.GetWindowThreadProcessId(windowHandle, 
                                                              IntPtr.Zero); 

    //Get current thread

    int currentThread = AppDomain.GetCurrentThreadId();
    
    return (currentThread == activeThreadID.ToInt32());

}
        
private IntPtr GetButtonHandle(IntPtr handle)
{
    //First time

    if(handle == IntPtr.Zero)
    {
        //Get Handle for class with name "#32770"

        IntPtr parent = Win32API.FindWindow(BROWSE_FOR_FOLDER_CLASS_NAME,null);

        //If the window we found is in the same thread we are running

        //then it is The 'Browse For Folder' Dialog, otherwise keep searching

        if(!isFromTheSameThread(parent))
        {
            //Keep searching from this point

            return GetButtonHandle(parent);
        }
        else
        {
            return   Win32API.FindWindowEx(parent,IntPtr.Zero,"Button", null);
        }
    }
    else
    {
        //Find next window

        IntPtr parent = Win32API.FindWindowEx(IntPtr.Zero, 
                        handle,BROWSE_FOR_FOLDER_CLASS_NAME, null);
        if(!isFromTheSameThread(parent))
        {
            return GetButtonHandle(parent);
        }
        else
        {
            //We found the 'Browse For Folder' Dialog handler.

            //Lets return the child handler of 'Maker new Folder' button

            return   Win32API.FindWindowEx(parent,IntPtr.Zero,"Button", null);
        }
    }
}

The last thing I want to show you is how to use this control:

ExtendedFolderBrowser m_ExtendedFolderBrowser = 
                      new ExtendedFolderBrowser();

m_ExtendedFolderBrowser.Description = "Folder Browser";

//Create a hanlder to a function which will check 

//if to show the 'Make New Button' button

ShowNewButtonHandler handler = new ShowNewButtonHandler(IsCDDrive);
//Set the handler

m_ExtendedFolderBrowser.SetNewFolderButtonCondition = handler;

m_ExtendedFolderBrowser.ShowDialog();

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here