我在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
。然后,Icon
中的MyComputerNode
属性会在无限递归中被一遍又一遍地调用。我知道这是一次无限递归,因为在几秒钟之后,该过程停止了StackoverFlowException.
所有我能想到的是Image
控件不喜欢我的URI理由并不断尝试加载它。
是的,我确定URI指向资源中的真实图像。
对于我的生活,我不知道这个递归电话的来源。我甚至将Icon
属性的类型更改为字符串,但它仍然会发生。
我做错了什么?
答案 0 :(得分:1)
我已经解决了这个问题。
问题发生在Binding
中ContentPresenter
的{{1}}。它只是设置为HeirarchicalDataTemplate
,这是错误的。
我已更改<Binding />
:
HeirarchicalDataTemplate
这可以在不抛出StackOverflowException的情况下工作。