【发布时间】:2014-10-09 16:21:09
【问题描述】:
我正在我的 WPF 应用程序中构建一个文件夹选择器对话框。我已经定义了以下类用作树中的节点:
public class FileSystemNode : ViewModelBase {
protected static readonly FileSystemNode DummyNode = new FileSystemNode( null, false );
public ObservableCollection<FileSystemNode> Children { get; private set; }
public bool IsExpanded {
get { return iIsExpanded; }
set {
SetAndNotify( "IsExpanded", ref iIsExpanded, value );
// Expand all the way up to the root.
if ( iIsExpanded && Parent != null )
Parent.IsExpanded = true;
// Lazy load the child items, if necessary.
if ( HasDummyNode ) {
Children.Remove( DummyNode );
LoadChildren();
}
}
}
private bool iIsExpanded = false;
public bool IsSelected {
get { return iIsSelected; }
set { SetAndNotify( "IsSelected", ref iIsSelected, value ); }
}
private bool iIsSelected = false;
public bool HasDummyNode {
get { return Children.Count == 1 && Children[ 0 ] == DummyNode; }
}
public virtual Uri Icon {
get { return null; }
}
public string Name { get; protected set; }
public FileSystemNode Parent { get; protected set; }
public string Path { get; protected set; }
public FileSystemNode( FileSystemNode theParent, bool lazyLoadChildren ) {
Parent = theParent;
Children = new ObservableCollection<FileSystemNode>();
if ( lazyLoadChildren ) {
Children.Add( DummyNode );
}
}
public override void Dispose() {
while ( Children.Count > 0 ) {
FileSystemNode node = Children[ 0 ];
Children.Remove( node );
node.Dispose();
}
GC.SuppressFinalize( this );
}
/// <summary>
/// Helper method that encapsulates the code needed to expand a node that can
/// contain folders. Prevents us from duplicating this code in several child
/// classes.
/// </summary>
protected void ExpandFolders() {
try {
foreach ( string folder in Directory.EnumerateDirectories( Path ) ) {
Children.Add( new FolderNode( new DirectoryInfo( folder ), this ) );
}
} catch ( Exception ) { }
}
protected virtual void LoadChildren() {
// Does nothing by default.
}
}
/// <summary>
/// Represents a Disk Drive in the file system tree view.
/// </summary>
public class DiskDriveNode : FileSystemNode {
public override Uri Icon {
get { return new Uri( "pack://CustomControls:,,,/Resources/diskdrive.png", UriKind.Relative ); }
}
public DiskDriveNode( string drive, FileSystemNode parent )
: base( parent, true ) {
Name = drive;
Path = drive;
}
protected override void LoadChildren() {
ExpandFolders();
}
}
/// <summary>
/// Represents a folder in the file system tree view. Lazy loads its <see cref="Children"/> collection
/// with its child folders and documents, if the <see cref="LoadDocuments"/> property is true.
/// </summary>
public class FolderNode : FileSystemNode {
public override Uri Icon {
get { return new Uri( "pack://CustomControls:,,,/Resources/folder.png", UriKind.Relative ); }
}
public FolderNode( DirectoryInfo folder, FileSystemNode parent )
: base( parent, true ) {
Name = folder.Name;
Path = folder.FullName;
}
protected override void LoadChildren() {
ExpandFolders();
}
}
/// <summary>
/// Represents the "My Computer" node in the file system tree view.
/// </summary>
public class MyComputerNode : FileSystemNode {
public const string MYCOMPUTER_PATH = "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}";
public override Uri Icon {
get { return new Uri( "pack://CustomControls:,,,/Resources/computer.png", UriKind.Relative ); }
}
public MyComputerNode( FileSystemNode parent )
: base( parent, true ) {
// Populate its fields.
Name = Car.FolderPickerDialog_Computer;
Path = MYCOMPUTER_PATH;
}
protected override void LoadChildren() {
try {
foreach ( string driveName in Directory.GetLogicalDrives() ) {
Children.Add( new DiskDriveNode( driveName, this ) );
}
} catch ( IOException ) {
} catch ( UnauthorizedAccessException ) {
}
}
}
/// <summary>
/// Represents the Entire Network node of the file system tree view. Lazy loads the servers
/// that are visible to the user on the network into its Children collection.
/// </summary>
public class NetworkNode : FileSystemNode {
public override Uri Icon {
get { return new Uri( "pack://CustomControls:,,,/Resources/network.png", UriKind.Relative ); }
}
public NetworkNode( FileSystemNode theParent )
: base( theParent, true ) {
Name = Car.FolderPickerDialog_Network;
Path = string.Empty;
}
protected override void LoadChildren() {
try {
ComputerEnumerator enumerator = new ComputerEnumerator {
ComputerFilter = ComputerEnumerator.ServerTypes.SV_TYPE_ALL
};
foreach ( Share server in enumerator ) {
Children.Add( new ServerNode( server, this ) );
}
} catch ( Exception ) { }
}
}
/// <summary>
/// Represents the root of the entire file system tree view. Does not lazy load its
/// children. Does not display in the TreeView control.
/// </summary>
public class RootNode : FileSystemNode {
public RootNode()
: base( null, false ) {
Name = Path = string.Empty;
// Create the MyComputer node and add it to this node's children.
Children.Add( new MyComputerNode( this ) );
// Create the Entire Network node and add it to this node's children.
Children.Add( new NetworkNode( this ) );
}
}
/// <summary>
/// Represents a server in the tree view. Lazy loads the shares exposed by the server
/// into its children collection.
/// </summary>
public class ServerNode : FileSystemNode {
public override Uri Icon {
get { return new Uri( "pack://CustomControls:,,,/Resources/computer.png", UriKind.Relative ); }
}
public ServerNode( Share share, FileSystemNode parent )
: base( parent, true ) {
Name = share.Name;
Path = share.UNCPath;
}
protected override void LoadChildren() {
ShareEnumerator enumerator = new ShareEnumerator {
Server = Name
};
foreach ( Share share in enumerator ) {
Children.Add( new ShareNode( share, this ) );
}
}
}
/// <summary>
/// Represents a single share in the file system tree view. Lazy loads the folders in the share
/// into its children collection.
/// </summary>
public class ShareNode : FileSystemNode {
public override Uri Icon {
get { return new Uri( "pack://CustomControls:,,,/Resources/share.png", UriKind.Relative ); }
}
public ShareNode( Share share, FileSystemNode parent )
: base( parent, true ) {
Name = share.Name;
Path = share.UNCPath;
}
protected override void LoadChildren() {
ExpandFolders();
}
}
}
这是TreeView 控件的 XAML:
<TreeView BorderThickness="2"
FontSize="20"
FontWeight="Bold"
Grid.Column="1"
Grid.Row="3"
ItemsSource="{Binding Path=Children}"
Margin="5"
Name="FolderTree"
SelectedItemChanged="FolderTree_SelectedItemChanged">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}" />
<Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" />
<Setter Property="FontWeight" Value="Bold" />
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="FontWeight" Value="ExtraBold" />
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type vm:FileSystemNode}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal">
<Image Name="PART_Image"
Height="{Binding ElementName=PART_Content, Path=ActualHeight}"
Source="{Binding Path=Icon}"
Width="{Binding ElementName=PART_Content, Path=ActualHeight}" />
<ContentPresenter Content="{Binding}"
Margin="5,0"
Name="PART_Content" />
</StackPanel>
</HierarchicalDataTemplate>
</TreeView.Resources>
</TreeView>
整个窗口的DataContext 在代码隐藏中设置为RootNode 类的实例。
当我单步执行代码时,我可以看到RootNode 对象以及MyComputerNode 和NetworkNode 被实例化。然后MyComputerNode 中的Icon 属性在无限递归中被一遍又一遍地调用。我知道这是一个无限递归,因为几秒钟后,进程以StackoverFlowException. 停止我所能想到的是Image 控件由于某种原因不喜欢我的URI,并一直试图加载它。
是的,我确信 URI 指向资源中的真实图像。
在我的一生中,我看不到这个递归调用的来源。我什至将Icon 属性的类型更改为字符串,但它仍然会发生。
我做错了什么?
【问题讨论】:
-
那是一大堆代码。几次调用 Icon 后检查堆栈跟踪。图标本身似乎不是问题。
-
今天早上,我尝试从 XAML 中注释掉整个
<TreeView.Resources>部分,问题就消失了。不管是什么,似乎和HierarchicalDataTemplate有关。
标签: c# wpf xaml recursion treeview