12

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

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

例如,

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

我想抑制面板并在详细信息视图中查看文件列表。用户可以通过右键单击上下文菜单来访问它,但我希望它自动出现。

我宁愿不必构建自己的 TreeView、DataGridView 或其他任何东西;WebBrowser 控件“免费”完成所有更新和重新排序以及诸如此类的事情。

有没有更好的办法?要使用的不同控件或要传递给控件的一些附加参数?

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

4

8 回答 8

9

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

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

事实证明,处理自动将视图设置为 Details 的问题部分实际上非常简单。在 Web 浏览器控件的 Navigated 事件中,只需找到 shell 视图窗口的句柄并向其发送带有特定 shell 常量 (SHVIEW_REPORT) 的 WM_COMMAND 消息。这是一个未记录的命令,但它在包括 Windows 2008 在内的所有 Windows 平台上都受支持,并且几乎可以肯定会在 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 事件中将消息重新发送到新窗口。

对于您问题的第二部分,您希望从资源管理器列表视图中接收事件。这比第一部分要困难得多。为此,您需要对列表视图窗口进行子类化,然后监视您感兴趣的窗口消息(例如 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;
    }
}

这应该让你知道你需要做什么。如果您想要的不仅仅是相当简单的事件,您可能需要寻找替代控件,尽管从我在免费和低成本区域中看到的情况来看,有一些相当不错的控件,但它们都有一些怪癖并且不会提供无缝的资源管理器经验。

请记住,此代码很快就组合在一起,没有错误处理或注释,并且忽略了多个问题,例如多个选定的项目,因此将其用作指南,风险自负。

于 2009-02-22T03:04:53.043 回答
5

为了处理重命名、删除和进行其他自定义,您需要编写自己的文件资源管理器。WebBrowser 控件不适合您的需要。它只是 ActiveX 组件的包装器。
您应该查看此 codeproject 文章。它包含文件资源管理器的实现。文件浏览器示例不多:
一二

于 2009-02-21T10:05:46.503 回答
3

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

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

于 2009-02-22T06:44:12.797 回答
3

我写了一个库,也许可以帮助你。您可以在以下网址找到它: http: //gong-shell.sourceforge.net/

您要查找的控件是 ShellView。那里也有关于如何用几行代码创建一个简单的 Windows 资源管理器克隆的教程。

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).

于 2009-04-28T01:41:05.653 回答
1

如果您很高兴仅使用 Windows Vista 并包装COM控件,则IExplorerBrowser可能可以满足您的需要。

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

较新的 API 提供了比简单地拦截消息更多的可编程性,但它(显然)对旧平台毫无用处。

于 2009-02-24T00:18:56.217 回答
1

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

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

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

于 2009-04-22T11:24:30.913 回答
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.

于 2009-12-30T23:45:18.837 回答
0

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

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

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

阿萨夫

于 2009-02-21T10:05:23.010 回答