【发布时间】:2014-08-13 21:00:37
【问题描述】:
我正在我的 WPF 应用程序中处理一个自定义对话框,该对话框将允许用户选择网络中用户可以看到的任何位置的文件夹。我使用的代码改编自 thisCodeProject 关于枚举网络资源的文章。
原文章中的代码在EnumerateServers对象被实例化后立即枚举所有网络资源,每当找到容器时递归调用自身,并将找到的每个节点添加到ArrayList中。这是低效的:
- 在完全遍历网络树之前,您无法开始枚举任何内容。
- 枚举可能需要大量时间,具体取决于网络流量和当前用户可以看到的节点数量。
我在 WPF TreeView 控件中显示网络资源,因此我只想遍历作为用户展开的节点的直接子节点的节点。
考虑到这一点,我对代码进行了一些更改。具体来说,我对其进行了修改,以便在发生错误时抛出异常并删除递归。现在的代码如下所示:
[StructLayout( LayoutKind.Sequential )]
internal class NetResource {
public ResourceScope Scope = 0;
public ResourceType Type = 0;
public ResourceDisplayType DisplayType = 0;
public ResourceUsage Usage = 0;
public string LocalName = null;
public string RemoteName = null;
public string Comment = null;
public string Provider = null;
};
public enum ResourceDisplayType {
Generic,
Domain,
Server,
Share,
File,
Group,
Network,
Root,
ShareAdmin,
Directory,
Tree,
NdsContainer
};
public enum ResourceScope {
Connected = 1,
GlobalNet,
Remembered,
Recent,
Context
};
public enum ResourceType {
Any,
Disk,
Print,
Reserved
};
[Flags]
public enum ResourceUsage {
Connectible = 0x00000001,
Container = 0x00000002,
NoLocalDevice = 0x00000004,
Sibling = 0x00000008,
Attached = 0x00000010,
All = Connectible | Container | Attached,
};
public class Share {
public string Comment { get; private set; }
public ResourceDisplayType DisplayType { get; private set; }
public string Name { get; private set; }
public string Provider { get; private set; }
public ResourceScope Scope { get; private set; }
public ResourceType ShareType { get; private set; }
public ResourceUsage Usage { get; private set; }
public string UNCPath { get; private set; }
internal Share( NetResource netResource ) {
DisplayType = netResource.DisplayType;
Scope = netResource.Scope;
ShareType = netResource.Type;
Usage = netResource.Usage;
if ( !string.IsNullOrWhiteSpace( netResource.Comment ) ) {
char[] commentChars = new char[ netResource.Comment.Length ];
netResource.Comment.CopyTo( 0, commentChars, 0, netResource.Comment.Length );
Comment = new string( commentChars );
}
if ( !string.IsNullOrWhiteSpace( netResource.LocalName ) ) {
char[] localNameChars = new char[ netResource.LocalName.Length ];
netResource.LocalName.CopyTo( 0, localNameChars, 0, netResource.LocalName.Length );
Name = new string( localNameChars );
}
if ( !string.IsNullOrWhiteSpace( netResource.Provider ) ) {
char[] providerChars = new char[ netResource.Provider.Length ];
netResource.Provider.CopyTo( 0, providerChars, 0, netResource.Provider.Length );
Provider = new string( providerChars );
}
if ( !string.IsNullOrWhiteSpace( netResource.RemoteName ) ) {
char[] remoteNameChars = new char[ netResource.RemoteName.Length ];
netResource.RemoteName.CopyTo( 0, remoteNameChars, 0, netResource.RemoteName.Length );
UNCPath = new string( remoteNameChars );
}
}
}
public class ShareEnumerator : IEnumerable<Share> {
public string Comment { get; set; }
public ResourceDisplayType DisplayType { get; set; }
public string Provider { get; set; }
public string ResourceName { get; set; }
public string ResourcePath { get; set; }
public ResourceScope Scope { get; set; }
public ResourceType ShareType { get; set; }
public ResourceUsage Usage { get; set; }
public ShareEnumerator() { }
public ShareEnumerator( Share aShare ) {
Comment = aShare.Comment;
DisplayType = aShare.DisplayType;
Provider = aShare.Provider;
ResourceName = aShare.Name;
ResourcePath = aShare.UNCPath;
Scope = aShare.Scope;
ShareType = aShare.ShareType;
Usage = aShare.Usage;
}
public IEnumerator<Share> GetEnumerator() {
NetResource netResource = new NetResource {
Comment = this.Comment,
DisplayType = this.DisplayType,
LocalName = this.ResourceName,
Provider = this.Provider,
RemoteName = this.ResourcePath,
Scope = this.Scope,
Type = this.ShareType,
Usage = this.Usage
};
uint bufferSize = 16384;
IntPtr buffer = IntPtr.Zero;
uint cEntries = 1;
IntPtr handle = IntPtr.Zero;
ErrorCodes result;
try {
buffer = Marshal.AllocHGlobal( (int) bufferSize );
result = WNetOpenEnum( Scope, ShareType, Usage, netResource, out handle );
if ( result != ErrorCodes.NO_ERROR ) {
throw new InvalidOperationException( string.Format( "The call to WNetOpenEnum failed: the result code was {0:x}", (int) result ) );
}
try {
do {
result = WNetEnumResource( handle, ref cEntries, buffer, ref bufferSize );
if ( result == ErrorCodes.NO_ERROR ) {
// It was. Marshal the buffer into the NetResource object.
Marshal.PtrToStructure( buffer, netResource );
if ( netResource.DisplayType == DisplayType || netResource.DisplayType == ResourceDisplayType.Domain ) {
// We do. Convert it into a Share & enumerate it.
yield return new Share( netResource );
}
} else if ( result == ErrorCodes.ERROR_NO_MORE_ITEMS ) {
break;
} else {
throw new InvalidOperationException( string.Format( "The call to WNetEnumResource failed: the result code was {0:x}", (int) result ) );
}
} while ( result == ErrorCodes.NO_ERROR );
} finally {
WNetCloseEnum( (IntPtr) buffer );
}
} finally {
if ( buffer != IntPtr.Zero ) {
// VERY IMPORTANT! Deallocate the buffer to prevent memory leaks!!
Marshal.FreeHGlobal( buffer );
}
}
}
System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
return GetEnumerator();
}
private enum ErrorCodes {
NO_ERROR = 0,
ERROR_NO_MORE_ITEMS = 259
};
[DllImport( "Mpr.dll", EntryPoint = "WNetOpenEnumA", CallingConvention = CallingConvention.Winapi )]
private static extern ErrorCodes WNetOpenEnum( ResourceScope dwScope, ResourceType dwType, ResourceUsage dwUsage, NetResource p, out IntPtr lphEnum );
[DllImport( "Mpr.dll", EntryPoint = "WNetCloseEnum", CallingConvention = CallingConvention.Winapi )]
private static extern ErrorCodes WNetCloseEnum( IntPtr hEnum );
[DllImport( "Mpr.dll", EntryPoint = "WNetEnumResourceA", CallingConvention = CallingConvention.Winapi )]
private static extern ErrorCodes WNetEnumResource( IntPtr hEnum, ref uint lpcCount, IntPtr buffer, ref uint lpBufferSize );
}
}
当用户单击TreeView 中的“整个网络”节点以获取网络节点时,此代码运行:
private void NetworkExpanded( object sender, RoutedEventArgs e ) {
if ( !ShareScanner.IsBusy ) {
OriginalCursor = LayoutRoot.Cursor;
LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;
TreeViewItem networkItem = sender as TreeViewItem;
if ( networkItem.Items.Count == 1 && networkItem.Items[ 0 ] == dummyNode ) {
networkItem.Items.Clear();
ShareScanner.RunWorkerAsync( new ShareScannerArgs( networkItem, networkItem.Tag as Share, ShareScannerTypes.Computers ) );
}
}
e.Handled = true;
}
整个网络节点下方是用户可以看到的特定计算机的节点。当他们展开其中一个节点时,将运行以下代码:
private void NetworkComputerExpanded( object sender, RoutedEventArgs e ) {
if ( !ShareScanner.IsBusy ) {
OriginalCursor = LayoutRoot.Cursor;
LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;
TreeViewItem computerItem = sender as TreeViewItem;
if ( computerItem.Items.Count == 1 && computerItem.Items[ 0 ] == dummyNode ) {
computerItem.Items.Clear();
ShareScanner.RunWorkerAsync( new ShareScannerArgs( computerItem, computerItem.Tag as Share, ShareScannerTypes.Shares ) );
}
}
e.Handled = true;
}
在计算机节点下方会有“共享”节点。当他们单击TreeView 中的“共享”节点时,此代码运行:
private void NetworkShareExpanded( object sender, RoutedEventArgs e ) {
if ( !ShareScanner.IsBusy ) {
OriginalCursor = LayoutRoot.Cursor;
LayoutRoot.Cursor = Mouse.OverrideCursor = Cursors.Wait;
TreeViewItem shareItem = sender as TreeViewItem;
if ( shareItem.Items.Count == 1 && shareItem.Items[ 0 ] == dummyNode ) {
shareItem.Items.Clear();
ShareScanner.RunWorkerAsync( new ShareScannerArgs( shareItem, shareItem.Tag as Share, ShareScannerTypes.Folders ) );
}
}
e.Handled = true;
}
共享下方会有各个文件夹的节点,但我还没有为该级别编写任何代码,因为我正在尝试让其他级别工作。
从我发布的代码中可以看出,我使用BackgroundWorker 来实际完成这项工作。在执行对 WNetOpenEnum 和 WNetEnumResource 的调用时,UI 变得无响应。我的 UI 必须保持响应,因此我使用 BackgroundWorker 来等待并保持 UI 响应。
这是BackgroundWorker's DoWork 事件处理程序:
private void ShareScanner_DoWork( object sender, DoWorkEventArgs e ) {
BackgroundWorker worker = sender as BackgroundWorker;
ShareScannerArgs info = e.Argument as ShareScannerArgs;
ShareEnumerator enumerator = null;
switch ( info.WhatToScanFor ) {
case ShareScannerTypes.Computers:
enumerator = new ShareEnumerator {
DisplayType = ResourceDisplayType.Network, // Retrieve Servers only
Scope = ResourceScope.GlobalNet, // Retrieve only objects the user can see
ShareType = ResourceType.Disk, // Retrieve only Disk shares
Usage = ResourceUsage.All // Retrieve all Connectible, Container & Attached nodes.
};
break;
case ShareScannerTypes.Shares:
case ShareScannerTypes.Folders:
enumerator = new ShareEnumerator( info.ParentShare );
break;
default:
// Should never get here!
throw new InvalidOperationException( string.Format( "Unknown ShareScannerType: {0}", info.WhatToScanFor ) );
}
try {
foreach ( Share share in enumerator ) {
if ( worker.CancellationPending ) {
e.Cancel = true;
return;
}
worker.ReportProgress( 0, new NodeArgs( info.Parent, share, info.WhatToScanFor ) );
}
} catch ( Exception ) { }
}
这是ProgressChanged 事件处理程序的代码:
private void ShareScanner_ProgressChanged( object sender, ProgressChangedEventArgs e ) {
NodeArgs nodeArgs = e.UserState as NodeArgs;
Share parentShare = nodeArgs.Tag as Share;
TreeViewItem item = new TreeViewItem {
Header = parentShare.UNCPath,
Tag = nodeArgs.Tag
};
switch ( nodeArgs.NodeToBuild ) {
case ShareScannerTypes.Computers:
item.Items.Add( dummyNode );
item.Expanded += new RoutedEventHandler( NetworkComputerExpanded );
break;
case ShareScannerTypes.Shares:
item.Items.Add( dummyNode );
item.Expanded += new RoutedEventHandler( NetworkShareExpanded );
break;
case ShareScannerTypes.Folders:
break;
default:
// Should never get here!
throw new InvalidOperationException( string.Format( "Unknown ShareScannerType: : {0}", nodeArgs.NodeToBuild ) );
}
nodeArgs.Parent.Items.Add( item );
}
最后,RunWorkerCompleted 事件的代码:
private void ShareScanner_RunWorkerCompleted( object sender, RunWorkerCompletedEventArgs e ) {
Mouse.OverrideCursor = null;
LayoutRoot.Cursor = OriginalCursor;
}
代码生成一棵带有“整个网络”节点的树。当您展开它时,我会看到“Microsoft 终端服务”、“Microsoft Windows 网络”和“Web 客户端网络”的条目。当我展开其中任何一个时,WNetOpenEnum 失败,结果返回 57。
我做错了什么?代码看起来很简单,但显然我错过了一些东西。
【问题讨论】:
-
您是否考虑过使用不同的方法,例如使用
WMI -
我从未使用过 WMI。也不知道是什么。你能指点我一些文章吗?
-
我不相信 WMI 会让您枚举 UNC 共享。看看这个问题和答案——所以似乎有很多 C#-enumerate network share-UNC 问题正在进行:stackoverflow.com/questions/8918477/…
标签: c# wpf networking treeview