English 中文(简体)
嵌入文件资源管理器实例于Windows表单应用程序表单中
原标题:
  • 时间:2009-02-12 17:16:40
  •  标签:

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

我正在使用 WebBrowser 控件的一个实例(System.Windows.Forms.WebBrowser),然后浏览到该文件夹。这将显示资源管理器窗口的默认视图,左侧是文件摘要面板,右侧以平铺(view)方式(大图标和文本)显示文件。

例如,

wb.Navigate(@"c:path	ofolder");

我想隐藏面板,并以详细视图查看文件列表。用户可以通过右键单击上下文菜单访问此功能,但我希望它自动弹出。

我宁愿不自己构建TreeView、DataGridView或其他控件;WebBrowser控件可以免费完成所有更新、重新排序等操作。

有没有更好的方法?使用不同的控件或传递给控件的其他参数?

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

最佳回答

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

问题回答

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

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

结果证明,你问题中涉及自动设置视图为详细信息其实非常简单。在你的Web浏览器控件的导航事件中,只需找到操作窗口的句柄,并向其发送特定的 shell 常量(SHVIEW_REPORT)的 WM_COMMAND 消息即可。这是一个未经记录的命令,但它在包括 Windows 2008 及 Windows 7 在内的所有 Windows 平台上都得到了支持。以下是一些可添加到你的 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 类派生的自定义类,并将其分配给需要监视的窗口的句柄。然后,您可以覆盖其窗口过程并根据您的愿望处理各种消息。下面是一个创建双击事件的示例 - 它相对简单,但要获得对资源管理器列表视图的广泛访问可能需要比您愿意做的更多的工作。

将此项添加到您的表格中:

    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 后面): 并将这两行代码添加到导航事件处理程序中 (在 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;
    }
}

这应该让你知道你需要做什么。如果你想要比相当简单的事件更多,你可能想要寻找另一种控制方式,尽管我已经在免费和低成本领域看到了一些非常不错的控制方式,但它们都有一些怪癖,并且不会提供无缝的浏览器体验。

请记住,该代码是相当快地组合而成,没有错误处理或注释,并忽略了若干问题,例如多个选定项目,因此请将其用作指南,并承担自己的风险。

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

你可以从他们的页面下载试用版,但是许可证的价格大约是130美元。

我写了一个图书馆,也许可以帮助你。你可以在这里找到它:http://gong-shell.sourceforge.net/

您正在寻找的控件是ShellView。那里有教程,可以在几行代码中创建一个简单的Windows资源管理器克隆版。

注意:对于.NET 4.0用户,Gong-shell目前无法正常工作。框架引入了Interop的更改,可以建立一个良好的框架,但在与shell32进行接口时会引起不同的问题(特别是shellicon API,导致一个未经处理的空指针dereference)。

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

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

我用它来显示像下面这个链接所示的屏幕:http://img7.imageshack.us/img7/7647/screenshotbaf.png

你可能想要查看ExplorerBrowser对象。

请参阅http://blogs.msdn.com/ieinternals/archive/2009/12/30/Windows-7-Web-Browser-Control-will-not-browse-file-system.aspx以获取更多细节。

如果您只使用Windows Vista,并且包装一个COM控件让您感到满意,那么IExplorerBrowser可能适合您的需求。

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

新版API暴露出的可编程性远远超过简单地拦截消息,但它对于老平台(显然)是无用的。

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

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

这对你有帮助吗?还是你一定要在你的表格中嵌入控件?

Asaf:阿萨夫





相关问题
热门标签