在Windows窗体应用程序窗体中嵌入文件资源管理器实例

时间:2009-02-12 17:16:40

标签: c# winforms explorer

我的(C#,.NET 3.5)应用程序生成文件,除了引发可以捕获和响应的事件之外,我还希望在表单中向用户显示目标文件夹。文件列表以与其他信息相同的形式显示。

我正在使用WebBrowser控件(System.Windows.Forms.WebBrowser)的实例,然后导航到该文件夹​​。这显示了资源管理器窗口的一些默认视图,左侧是文件摘要面板,“Tiles”(大图标和文本)视图中的文件。

例如,

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

我想压缩面板并在详细信息视图中查看文件列表。用户可以通过右键单击上下文菜单来实现此目的,但我希望它能自动生成。

我宁愿不必构建自己的TreeView,DataGridView等等; WebBrowser控件执行所有更新和重新排序,以及“免费”。

有更好的方法吗?要使用的其他控件还是其他一些参数传递给控件?

如果我可以捕获事件(例如,选择/重命名/双击文件等),那就更好了!

8 个答案:

答案 0 :(得分:9)

警告:包含大量代码的长帖。

将Web浏览器控件导航到文件系统文件夹时,Web浏览器控件会托管一个shell视图窗口,该窗口依次托管资源管理器列表视图。实际上,这与Explorer进程以及文件对话框和Internet Explorer完全相同。这个shell窗口不是一个控件,因此没有可以在其上调用的方法或可以订阅的事件,但它可以接收Windows消息,并且可以进行子类化。

事实证明,你的问题中涉及将视图自动设置为详细信息的部分实际上非常简单。在Web浏览器控件的Navigated事件中,只需找到shell视图窗口的句柄,并向其发送带有特定shell常量(SHVIEW_REPORT)的WM_COMMAND消息。这是一个未记录的命令,但它在所有Windows平台上都受支持,包括Windows 2008,几乎肯定会在Windows 7上。有些代码要添加到Web浏览器的表单中,这表明了这一点:

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

每次Web浏览器导航到新窗口(包括从资源管理器视图中打开文件夹时),都会创建一个新的shell视图窗口,因此必须在每个Navigated事件中将消息重新发送到新窗口。

对于问题的第二部分,您希望从资源管理器列表视图中接收事件。这比第一部分困难得多。为此,您需要对列表视图窗口进行子类化,然后监视Windows消息以查找您感兴趣的消息(例如WM_LBUTTONDBLCLK)。为了对窗口进行子类化,您需要创建从NativeWindow类派生的自己的类,并为其分配需要监视的窗口的句柄。然后,您可以覆盖其Window过程并根据需要处理各种消息。下面是创建双击事件的示例 - 它相对简单,但是对资源管理器列表视图的广泛访问可能涉及比您愿意做的更多的工作。

将此添加到您的表单:

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

这两行到Navigated事件处理程序(在SendMessage之后):

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

然后添加以下类:

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

这可以让您了解您需要做什么。如果你想要的不仅仅是相当简单的事件,你可能想要寻找一个替代控件,虽然从我在免费和低成本领域看到的有一些相当不错的控件,但他们都有一些怪癖,不会给无缝的探险家经验。

请记住,此代码可以相当快速地放在一起而不会出现错误处理或注释,并忽略多个问题,例如多个选定项目,因此请将其用作指南,风险自负。

答案 1 :(得分:4)

为了处理重命名,删除和进行其他自定义,您需要编写自己的文件资源管理器。 WebBrowser控件不适合您的需要。它只是ActiveX组件的包装器 你应该检查一下this codeproject article。它包含文件资源管理器的实现。文件浏览器的样本数量较少:
one
two

答案 2 :(得分:3)

LogicNP软件有两个控件(FileView和ShComboBox)可以满足您的需求: http://www.ssware.com/fldrview.htm

您可以从他们的页面下载试用版,但许可证大约为130美元。

答案 3 :(得分:3)

我写了一个可以帮助你的图书馆。您可以在http://gong-shell.sourceforge.net/

找到它

您正在寻找的控件是ShellView。那里有关于如何仅在几行中创建一个简单的Windows资源管理器克隆的教程。

.NET 4.0用户注意事项:Gong-shell目前已破4.0。该框架引入了Interop中的更改,它将构建得很好但在与shell32接口时会导致不同的问题(特别是shellicon api,导致非托管空指针取消引用)。

答案 4 :(得分:1)

如果您对仅使用Windows Vista并包装COM控件感到满意,IExplorerBrowser可能是您可以接受的。

This The Code Project article显示了它在MFC程序中的使用,但at least one other person似乎已经在C#中使用了它。

较新的API暴露出比简单截取消息更多的可编程性,但对于旧平台来说(显然)无用。

答案 5 :(得分:1)

查看这篇文章here,它展示了如何在.NET和WinForms中执行此操作。这样做可以完全控制用户看到的内容。

我在我的一个应用程序中使用它并且它的效果非常好。您可以显示图标/详细信息/列表视图,它会阻止用户移动到其他目录(这通常是显示标准文件/目录对话框的问题。

我用它来显示below http://img7.imageshack.us/img7/7647/screenshotbaf.png下方的屏幕:

答案 6 :(得分:1)

您可能需要查看ExplorerBrowser对象。

有关详细信息,请参阅http://blogs.msdn.com/ieinternals/archive/2009/12/30/Windows-7-Web-Browser-Control-will-not-browse-file-system.aspx

答案 7 :(得分:0)

如果要打开另一个窗口以显示目标文件夹的内容,可以使用System.Windows.Forms.OpenFileDialog或SaveFileDialog,或从FileDialog继承并扩展它。

要允许用户选择文件夹,您可以使用FolderBrowserDialog,但作为用户我不喜欢该控件。

这是否有帮助,或者您必须在表单中嵌入控件?

阿萨夫