我的(C#,. NET 3.5)应用程序生成文件,除了引发可以捕获和响应的事件之外,我还想在表单中向用户显示目标文件夹.文件列表以与其他信息相同的形式显示.
我正在使用WebBrowser
control(System.Windows.Forms.WebBrowser
)的实例,然后导航到该文件夹.这将显示资源管理器窗口的一些默认视图,左侧是文件摘要面板,"Tiles"(大图标和文本)视图中的文件.
例如,
wb.Navigate(@"c:\path\to\folder\");
我想压缩面板并在详细信息视图中查看文件列表.用户可以通过右键单击上下文菜单来实现此目的,但我希望它能自动生成.
我宁愿不必构建自己的TreeView,DataGridView或其他任何东西; WebBrowser控件执行所有更新和重新排序,以及"免费".
有没有更好的办法?使用不同的控件或传递给控件的其他参数?
如果我可以捕获事件(例如,文件被选中/重命名/双击等),那就更好了!
警告:包含大量代码的长帖子.
将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 EventHandlerItemExecuted; [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 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; } }
这应该可以让您了解您需要做什么.如果你想要的不仅仅是相当简单的事件,你可能想要寻找一个替代控件,虽然从我在免费和低成本领域看到的有一些相当不错的控件,但他们都有一些怪癖,不会给无缝的探险家经验.
请记住,此代码可以相当快速地放在一起,无需错误处理或注释,并忽略多个问题,例如多个选定项目,因此请将其用作指南,风险自负.