Embedding a File Explorer instance in a Windows Forms application form
Asked Answered
B

8

13

My (C#, .NET 3.5) application generates files and, in addition to raising events that can be caught and reacted to, I want to display the target folder to the user in a form. The file-list is being shown within the same form as other information.

I'm using an instance of the WebBrowser control (System.Windows.Forms.WebBrowser), then navigating to the folder. This shows some default view of the explorer window, with the file summary panel on the left and the files in the 'Tiles' (large icon and text) view.

For example,

wb.Navigate(@"c:\path\to\folder\");

I'd like to suppress the panel and to view the file list in the Details view. The user can get to this via a right-click, context menu, but I'd like it to come up automatically.

I'd rather not have to build my own TreeView, DataGridView or whatever; the WebBrowser control does all the updating and re-sorting and whatnot 'for free'.

Is there a better way? A different control to use or some additional arguments to pass to the control?

And if I could trap events (for example, files being selected/renamed/double-clicked, etc.) then all the better!

Butyl answered 12/2, 2009 at 17:16 Comment(1)
What I found useful is the (commercial) ShellBrowser component.Subliminal
M
6

In order to handle renaming, deleting and make other customization you need to write your own file explorer. WebBrowser control is not suitable for your needs. It's just a wrapper over ActiveX component.
You should check this codeproject article. It contains an implementation of file explorer. There are few more samples of file browser:
one
two

Martensite answered 21/2, 2009 at 10:5 Comment(1)
That's what I was worrying about - needing to add lots of my own code. I hoped that the web-browser might be able to have arguments passed to save my lazy bones the effort!Butyl
S
11

WARNING: Long post with lots of code.

When you navigate the web browser control to a file system folder the web browser control hosts a shell view window that in turn hosts the explorer list view. In fact this is exactly the same thing that the Explorer process does as well as the file dialogs and Internet Explorer. This shell window is not a control so there are no methods that can be called on it or events that can be subscribed to but it can receive windows messages and it can be sub-classed.

It turns out that the part of your question dealing with setting the view to Details automatically is actually quite easy. In your web browser control's Navigated event simply find the handle to the shell view window and send it a WM_COMMAND message with a particular shell constant (SHVIEW_REPORT). This is an undocumented command but it is supported on all Windows platforms up to and including Windows 2008 and almost certainly will be on Windows 7. Some code to add to your web browser's form demonstrates this:

    private delegate int EnumChildProc(IntPtr hwnd, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
        IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true)]
    private static extern int EnumChildWindows(IntPtr hWndParent,
        EnumChildProc lpEnumFunc, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern int GetClassName(IntPtr hWnd, StringBuilder lpClassName,
        int nMaxCount);

    private const int WM_COMMAND = 0x0111;
    private const int SHVIEW_REPORT = 0x702C;
    private const string SHELLVIEW_CLASS = "SHELLDLL_DefView";

    private IntPtr m_ShellView;

    void webBrowser1_Navigated(object sender, WebBrowserNavigatedEventArgs e)
    {
        m_ShellView = IntPtr.Zero;
        EnumChildWindows(webBrowser1.Handle, EnumChildren, IntPtr.Zero);
        if (m_ShellView != IntPtr.Zero)
        {
            SendMessage(m_ShellView, WM_COMMAND, (IntPtr)SHVIEW_REPORT, (IntPtr)0);
        }
    }

    private int EnumChildren(IntPtr hwnd, IntPtr lParam)
    {
        int retval = 1;

        StringBuilder sb = new StringBuilder(SHELLVIEW_CLASS.Length + 1);
        int numChars = GetClassName(hwnd, sb, sb.Capacity);
        if (numChars == SHELLVIEW_CLASS.Length)
        {
            if (sb.ToString(0, numChars) == SHELLVIEW_CLASS)
            {
                m_ShellView = hwnd;
                retval = 0;
            }
        }

        return retval;
    }

Every time the web browser navigates to a new window (including when a folder is opened from within the explorer view) a new shell view window is created so the message must be re-sent to the new window in every Navigated event.

For the second part of your question you would like to receive events from the explorer list view. This is quite a bit more difficult than the first part. To do this you would need to sub-class the list view window and then monitor the windows messages for ones that interest you (such as WM_LBUTTONDBLCLK). In order to sub-class a window you would need to create your own class derived from the NativeWindow class and assign it the handle of the window that you need to monitor. You can then override its Window procedure and handle the various messages as you wish. Below is an example of creating a double click event - it is relatively simple but to get extensive access to the explorer list view may involve a lot more work than you are willing to do.

Add this to your form:

    private ExplorerListView m_Explorer;

    void OnExplorerItemExecuted(object sender, ExecuteEventArgs e)
    {
        string msg = string.Format("Item to be executed: {0}{0}{1}", 
            Environment.NewLine, e.SelectedItem);
        e.Cancel = (MessageBox.Show(msg, "", MessageBoxButtons.OKCancel) 
            == DialogResult.Cancel);
    }

and these two lines to the Navigated event handler (right after the SendMessage):

    m_Explorer = new ExplorerListView(m_ShellView);
    m_Explorer.ItemExecuted += OnExplorerItemExecuted;

Then add the following classes:

class ExplorerListView : NativeWindow
{

    public event EventHandler<ExecuteEventArgs> ItemExecuted;

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr SendMessage(IntPtr hWnd, int Msg,
        IntPtr wParam, IntPtr lParam);

    [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
    private static extern IntPtr FindWindowEx(IntPtr hwndParent,
        IntPtr hwndChildAfter, string lpszClass, string lpszWindow);

    private const int WM_LBUTTONDBLCLK = 0x0203;

    private const int LVM_GETNEXTITEM = 0x100C;
    private const int LVM_GETITEMTEXT = 0x1073;

    private const int LVNI_SELECTED = 0x0002;

    private const string EXPLORER_LISTVIEW_CLASS = "SysListView32";

    public ExplorerListView(IntPtr shellViewHandle)
    {
        base.AssignHandle(FindWindowEx(shellViewHandle, IntPtr.Zero, 
            EXPLORER_LISTVIEW_CLASS, null));
        if (base.Handle == IntPtr.Zero)
        {
            throw new ArgumentException("Window supplied does not encapsulate an explorer window.");
        }
    }


    protected override void WndProc(ref Message m)
    {
        switch (m.Msg)
        {
            case WM_LBUTTONDBLCLK:
                if (OnItemExecution() != 0) return;
                break;
            default:
                break;
        }
        base.WndProc(ref m);
    }

    private int OnItemExecution()
    {
        int cancel = 0;
        ExecuteEventArgs args = new ExecuteEventArgs(GetSelectedItem());
        EventHandler<ExecuteEventArgs> temp = ItemExecuted;
        if (temp != null)
        {
            temp(this, args);
            if (args.Cancel) cancel = 1;
        }
        return cancel;
    }

    private string GetSelectedItem()
    {
        string item = null;

        IntPtr pStringBuffer = Marshal.AllocHGlobal(2048);
        IntPtr pItemBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(LVITEM)));

        int selectedItemIndex = SendMessage(base.Handle, LVM_GETNEXTITEM, (IntPtr)(-1), (IntPtr)LVNI_SELECTED).ToInt32();
        if (selectedItemIndex > -1)
        {
            LVITEM lvi = new LVITEM();
            lvi.cchTextMax = 1024;
            lvi.pszText = pStringBuffer;
            Marshal.StructureToPtr(lvi, pItemBuffer, false);
            int numChars = SendMessage(base.Handle, LVM_GETITEMTEXT, (IntPtr)selectedItemIndex, pItemBuffer).ToInt32();
            if (numChars > 0)
            {
                item = Marshal.PtrToStringUni(lvi.pszText, numChars);
            }
        }

        Marshal.FreeHGlobal(pStringBuffer);
        Marshal.FreeHGlobal(pItemBuffer);

        return item;
    }

    struct LVITEM
    {
        public int mask;
        public int iItem;
        public int iSubItem;
        public int state;
        public int stateMask;
        public IntPtr pszText;
        public int cchTextMax;
        public int iImage;
        public IntPtr lParam;
        public int iIndent;
        public int iGroupId;
        int cColumns; // tile view columns
        public IntPtr puColumns;
        public IntPtr piColFmt;
        public int iGroup;

    }
}

public class ExecuteEventArgs : EventArgs
{
    public string SelectedItem { get; private set; }
    public bool Cancel { get; set; }

    internal ExecuteEventArgs(string selectedItem)
    {
        SelectedItem = selectedItem;
    }
}

This should give you an idea of what you would need to do. If you want more than fairly simple events you may want to look for a alternative control, though from what I have seen in the free and low cost areas there are some pretty decent controls but they all have some quirks and will not give a seamless explorer experience.

Remember this code was put together fairly quickly without error handling or comments and ignoring several issues such as multiple selected items, so use it as a guideline and at your own risk.

Smalley answered 22/2, 2009 at 3:4 Comment(2)
Kudos for the effort in the reply, but the accepted answer should go to the solution I actually used. So if I could have split the bounty I would have done, but I'm actually using the project cited in the other reply ...Butyl
If it works for you that's great. Though, if this is not for personal use, I would strongly recommend you use the LogicNP solution suggested by uzbones. The code project item is a valiant effort but it is far from robust to say the least.Smalley
M
6

In order to handle renaming, deleting and make other customization you need to write your own file explorer. WebBrowser control is not suitable for your needs. It's just a wrapper over ActiveX component.
You should check this codeproject article. It contains an implementation of file explorer. There are few more samples of file browser:
one
two

Martensite answered 21/2, 2009 at 10:5 Comment(1)
That's what I was worrying about - needing to add lots of my own code. I hoped that the web-browser might be able to have arguments passed to save my lazy bones the effort!Butyl
C
3

LogicNP Software has two controls (FileView and ShComboBox) that do what your looking for: http://www.ssware.com/fldrview.htm

You can download a trial from their page, however it's ~130$ for the license.

Costello answered 22/2, 2009 at 6:44 Comment(0)
E
3

I have written a library that might be able to help you. You can find it at: http://gong-shell.sourceforge.net/

The control you're looking for is the ShellView. There's tutorials there on how to create a simple Windows Explorer clone in only a few lines too.

Note for .NET 4.0 users: Gong-shell is currently broken for 4.0. The framework introduced changes in Interop and it will build just fine but cause different issues when interfacing with shell32 (notably the shellicon api, leading to an unmanaged null pointer dereference).

Evvoia answered 28/4, 2009 at 1:41 Comment(1)
I have used this and I have had some problems. The biggest problem is that I cannot double click on a file and have it start the default application for the file. If I try to rename a file, the delete key will not work to delete characters of the existing filename. I have to use backspace. If I type the letter 'i' while renaming, the rename operations ceases! I haven't spent much time debugging, but they are very frustrating problems.Ghassan
F
1

If you are happy being Windows Vista only and wrapping a COM control, IExplorerBrowser might be acceptable for you needs.

This The Code Project article shows its use within an MFC program but at least one other person seems to have got it to work in C# after some effort.

The newer API exposes considerably more programmability than simply intercepting messages, but it is (obviously) useless for older platforms.

Flagstaff answered 24/2, 2009 at 0:18 Comment(0)
M
1

Check out this article here, it shows how to do this in .NET and WinForms. Doing it this way gives full-control over what the user sees.

I've used it in one of my applications and it works really well. You can show icon/details/list view and it stops the user moving to other directories (which is often the problem of showing the standard file/directory dialogs.

I use it to show the screen like the one below below http://img7.imageshack.us/img7/7647/screenshotbaf.png:

Maxi answered 22/4, 2009 at 11:24 Comment(0)
M
1

You may want to look at the ExplorerBrowser object.

See http://blogs.msdn.com/ieinternals/archive/2009/12/30/Windows-7-Web-Browser-Control-will-not-browse-file-system.aspx for more details.

Mariejeanne answered 30/12, 2009 at 23:45 Comment(0)
W
0

If you want to open a different window to display the target folder's content you can use System.Windows.Forms.OpenFileDialog, or SaveFileDialog, or inherit from FileDialog and extend it.

To allow the user to select a folder you can use FolderBrowserDialog, though as a user I don't like that control.

Does this help or you absolutely have to embed a control in your form?

Asaf

Wenona answered 21/2, 2009 at 10:5 Comment(3)
Ggg.. how you'll integrate it in your form? And Unsliced wants here to display a list of generated files instead of opening files or selecting target folder.Martensite
I did start and end by asking if it has to be in the same form. From the question it isn't clear to me if it's embedded in a form because it's implemented with a WebBrowser, or because the file view is alongside something else. There's no straight forward way to embed a dialog in a form.Wenona
It does rather have to be in the same form as the information needs to be shown alongside other progress and status updates. It's surprising that we have open/save file/create folders dialog, but none to explicitly browse ...Butyl

© 2022 - 2024 — McMap. All rights reserved.