警告:本文内容较长,包含大量代码。
当您在 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;
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;
}
}
这应该让您对需要做什么有一个想法。如果您想要更复杂的事件,您可能需要寻找其他控件,尽管在免费和低成本领域中,我所看到的一些相当不错的控件都有一些怪癖,并且不能提供无缝的浏览体验。
请记住,此代码是相当快速地组合而成的,没有错误处理或注释,并忽略了多个选定项等一些问题,因此请把它作为指南,并自行承担风险。