代码之家  ›  专栏  ›  技术社区  ›  Unsliced

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

  •  12
  • Unsliced  · 技术社区  · 15 年前

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

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

    例如

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

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

    我宁愿不必构建自己的TreeView、DataGridView或其他任何东西;WebBrowser控件“免费”执行所有更新和重新排序等操作。

    有更好的办法吗?要使用的其他控件或要传递给该控件的其他参数?

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

    8 回复  |  直到 9 年前
        1
  •  9
  •   Stephen Martin    15 年前

    警告:带有大量代码的长帖子。

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

    事实证明,问题中有关自动将视图设置为详细信息的部分实际上相当简单。在web浏览器控件的导航事件中,只需找到shell视图窗口的句柄,并向其发送带有特定shell常量的WM_命令消息(SHVIEW_报告)。这是一个未记录的命令,但在Windows 2008之前(包括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;
        }
    

    对于问题的第二部分,您希望从资源管理器列表视图接收事件。这比第一部分要难一点。要做到这一点,您需要对列表视图窗口进行子分类,然后监视windows消息中您感兴趣的消息(例如WM_lbuttondblck)。为了对窗口进行子类化,您需要创建从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之后):

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

    这会让你知道你需要做什么。如果您想要的不仅仅是简单的事件,那么您可能需要寻找一个替代控件,尽管从我在免费和低成本领域看到的情况来看,有一些相当不错的控件,但它们都有一些怪癖,无法提供无缝的explorer体验。

        2
  •  5
  •   zihotki    15 年前

    为了处理重命名、删除和进行其他自定义,您需要编写自己的文件资源管理器。WebBrowser控件不适合您的需要。它只是ActiveX组件的包装器。
    this codeproject article . 它包含一个文件资源管理器的实现。还有几个文件浏览器示例:
    one
    two

        3
  •  3
  •   uzbones    15 年前

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

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

        4
  •  3
  •   Community Egal    13 年前

    http://gong-shell.sourceforge.net/

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

    .NET 4.0用户注意事项 :锣壳当前已损坏4.0。该框架引入了互操作方面的更改,它可以很好地构建,但在与shell32接口时会引起不同的问题(特别是shellicon api,导致非托管空指针取消引用)。

        5
  •  1
  •   Matt Warren    15 年前

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

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

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

        7
  •  1
  •   Peter Mortensen John Conde    9 年前

    如果您很高兴只使用WindowsVista并包装 COM 控制 IExplorerBrowser 可能适合您的需要。

    This The Code Project article 显示了它在 MFC 程序但是 at least one other person

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

        8
  •  0
  •   Asaf R    15 年前

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

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

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

    阿萨夫