我正在使用WPF应用程序中的自定义对话框,该对话框允许用户在网络中的任何位置选择用户可以看到的文件夹。我使用了this CodeProject关于枚举网络资源的文章改编的代码。
原始文章中的代码会在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 );
}
}
当用户点击&#34;整个网络&#34;对于网络节点,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;
}
在计算机节点下面会有&#34;分享&#34;节点。当他们点击&#34;分享&#34; 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
时,用户界面无响应。我的用户界面必须保持响应,所以我使用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;
}
代码会生成一个包含“整个网络”的树。节点。当您展开它时,我会获得&#34; Microsoft终端服务&#34;,&#34; Microsoft Windows网络&#34;以及&#34; Web客户端网络&#34;的条目。当我展开其中任何一个时,WNetOpenEnum
失败,返回57作为结果。
我做错了什么?代码看起来非常简单,但显然我错过了一些东西。
答案 0 :(得分:0)
我终于让我的对话框按照我的意愿工作,但没有使用原始问题中发布的任何代码。相反,我用CodeProject上另外两篇文章中的代码替换了所有代码。以下是我的应用程序中代码的注释以及代码来自何处的信息:
The `ComputerEnumerator` class originated as the `NetworkBrowser.cs` class from the
"Retrieving a List of Network Computer Names Using C#" article, which can be found at:
http://www.codeproject.com/Articles/16113/Retreiving-a-list-of-network-computer-names-using
The ShareEnumerator class originated as the ServerEnum.cs class from the
"Network Shares and UNC paths" article, which can be found at:
http://www.codeproject.com/Articles/2939/Network-Shares-and-UNC-paths
第一个类使用NetServerEnum
函数来检索网络上可用的所有计算机,而第二个类使用NetShareEnum
方法检索特定服务器上可用的共享。第一篇文章中的代码正常工作。不过,第二篇文章中的代码存在一些问题。
最大的问题出现在DllImport
NetApiBufferFree
函数中。当我在调试器中调用它时我的代码调用它时,该函数必须有错误或者某些东西,因为调试器永远不会重新获得控制,并且恢复鼠标光标的方法中的代码永远不会运行。当我用第一篇文章中的那个替换API的定义时,一切正常。
不太严重的问题是,第二篇文章创建了一个名为ShareType
的枚举类型,该类型使用[Flags]
属性进行了修饰。这个问题是枚举的值取自LMShares.h文件,是序列值,而不是2的幂。这样的枚举不能作为一组标志。例如,应该没有值为3的枚举标识符,因为该值应该通过逐位OR运算1和1的枚举值来形成。 2在一起。但是IPC类型的枚举标识符肯定是3。
如果您尝试使用此枚举中的值来过滤返回的共享,则此问题会(并且确实)会导致问题。为了解决后一个问题,我从该类型&amp;中删除了[Flags]
属性。不要使用任何按位操作。
令人惊讶的是,这些函数返回值的速度有多快。我确定他们已在某个地方缓存,在某些情况下他们可能需要更长时间才能返回值。但无论如何我都有代码工作,因为我希望它可以工作。