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

13

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

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

例如,

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

我想要隐藏面板并在“详细信息”视图中查看文件列表。用户可以通过右键点击上下文菜单进行此操作,但我希望它自动弹出。

我不想自己构建TreeView、DataGridView或者其他控件;WebBrowser控件可以自动更新和重新排序等等。

是否有更好的方法?可以使用其他控件或传递给控件的其他参数吗?

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


我发现有用的是(商业)ShellBrowser组件 - Uwe Keim
8个回答

11

警告:本文内容较长,包含大量代码。

当您在 Web 浏览器控件中导航到文件系统文件夹时,Web 浏览器控件会托管一个 shell 视图窗口,该窗口又托管资源管理器列表视图。实际上,资源管理器进程、文件对话框和 Internet Explorer 都执行的是完全相同的操作。此 shell 窗口不是控件,因此无法调用任何方法或订阅任何事件,但它可以接收 Windows 消息并可被子类化。

事实证明,关于自动将视图设置为“详细信息”的部分其实非常简单。只需在 Web 浏览器控件的 Navigated 事件中查找 shell 视图窗口的句柄,并向其发送 WM_COMMAND 消息以使用特定 shell 常量(SHVIEW_REPORT)。尽管这是一条未记录的命令,但它支持所有 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;
    }
每次网页浏览器导航到新窗口时(包括从资源管理器视图中打开文件夹时),都会创建一个新的Shell视图窗口,因此必须在每个Navigated事件中重新发送消息以便发送到新窗口。
关于你问题的第二部分,你想从资源管理器列表视图接收事件。这比第一部分要困难得多。为了做到这一点,你需要对列表视图窗口进行子类化,然后监视你感兴趣的Windows消息(例如WM_LBUTTONDBLCLK)。为了对一个窗口进行子类化,你需要创建一个派生自NativeWindow类的自己的类,并为其分配你需要监视的窗口的句柄。然后,你可以重写它的窗口过程并按照自己的意愿处理各种消息。下面是创建一个双击事件的示例——它相对简单,但要获得对资源管理器列表视图的广泛访问可能需要比你愿意做的更多的工作。
将以下内容添加到你的表单中:
    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);
    }

在 SendMessage 后面的 Navigated 事件处理程序中加入这两行代码:

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

这应该让您对需要做什么有一个想法。如果您想要更复杂的事件,您可能需要寻找其他控件,尽管在免费和低成本领域中,我所看到的一些相当不错的控件都有一些怪癖,并且不能提供无缝的浏览体验。

请记住,此代码是相当快速地组合而成的,没有错误处理或注释,并忽略了多个选定项等一些问题,因此请把它作为指南,并自行承担风险。


感谢您的回复和努力,但最佳答案应该给那个我真正使用的解决方案。如果我可以分配悬赏,我本来会这么做,但事实上我正在使用另一个回复中提到的项目... - Unsliced
如果它适用于您,那太好了。但是,如果这不是为个人使用而设计的,我强烈建议您使用uzbones建议的LogicNP解决方案。虽然代码项目是一项英勇的努力,但至少可以说它远非强大。 - Stephen Martin

6
为了处理重命名、删除和进行其他自定义操作,您需要编写自己的文件浏览器。WebBrowser控件不适合您的需求,它只是ActiveX组件的包装器。
您应该查看这篇codeproject文章。它包含了一个文件浏览器的实现。还有几个文件浏览器的示例:
一个
两个

这就是我担心的——需要添加很多自己的代码。我希望Web浏览器能够接收传递的参数,以节省我的时间和精力! - Unsliced

3

我写了一个库,可能能帮到你。你可以在这里找到它:http://gong-shell.sourceforge.net/

你需要的控件是ShellView。那里有教程,可以用几行代码创建一个简单的Windows资源管理器克隆版。

注意.NET 4.0用户:Gong-shell目前无法在4.0版本下使用。框架引入了Interop的更改,虽然可以正常构建,但与shell32进行接口交互时会导致不同的问题(特别是shellicon api,导致非托管空指针解引用)。


我使用过这个程序,但是遇到了一些问题。最大的问题是我无法双击文件并启动文件的默认应用程序。如果我尝试重命名文件,删除键将无法删除现有文件名中的字符。我必须使用退格键。如果我在重命名时输入字母“i”,则重命名操作会停止!我没有花太多时间进行调试,但这些问题非常令人沮丧。 - Chris Dunaway

3
LogicNP软件有两个控件(FileView和ShComboBox),可以实现您所需的功能: http://www.ssware.com/fldrview.htm 您可以从他们的页面下载试用版,但许可证价格为130美元。

1

1

看看这篇文章在这里, 它展示了如何在.NET和WinForms中实现。这种方式可以完全控制用户所看到的内容。

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

我使用它来显示像下面这样的屏幕下面 http://img7.imageshack.us/img7/7647/screenshotbaf.png:


1

如果您满意于仅在Windows Vista上运行并包装COM控件,则IExplorerBrowser可能适合您的需求。

这篇Code Project文章展示了它在MFC程序中的使用,但至少有一个人经过一些努力后似乎已经让它在C#中工作。

新的API比仅拦截消息要更具可编程性,但对于旧平台来说(显然)是无用的。


0
如果您想打开一个不同的窗口来显示目标文件夹的内容,您可以使用System.Windows.Forms.OpenFileDialog或SaveFileDialog,或继承自FileDialog并扩展它。
要允许用户选择文件夹,您可以使用FolderBrowserDialog,尽管作为用户,我不喜欢那个控件。
这有帮助吗?或者您一定要在表单中嵌入一个控件?
Asaf

Ggg.. 你将如何在你的表单中集成它?而 Unsliced 希望在这里显示生成文件的列表,而不是打开文件或选择目标文件夹。 - zihotki
我开始和结束时都问了一句话,即是否必须采用同一形式。从问题中我并不清楚它是否嵌入到一个表单中,因为它是使用WebBrowser实现的,还是因为文件视图与其他内容并排放置。没有直接的方法可以在表单中嵌入对话框。 - Asaf R
它必须以与其他进度和状态更新一起显示所需信息的相同形式呈现,这可能有些出人意料,因为我们有打开/保存文件/创建文件夹的对话框,但没有明确的浏览对话框... - Unsliced

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接