当绑定TreeView控件到文件系统时,无限递归循环

本文关键字:无限 递归 循环 文件系统 绑定 TreeView 控件 | 更新日期: 2023-09-27 18:18:08

我正在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对象被实例化,以及MyComputerNodeNetworkNode。然后MyComputerNode中的Icon属性在无限递归中被一遍又一遍地调用。我知道这是一个无限递归,因为几秒钟后,进程停止了StackoverFlowException.,我能想到的是Image控件不喜欢我的URI,因为某种原因,并一直试图加载它。

是的,我确信URI指向资源中的真实图像。

无论如何我都不明白这个递归调用是从哪里来的。我甚至将Icon属性的类型更改为字符串,它仍然发生。

我做错了什么?

当绑定TreeView控件到文件系统时,无限递归循环

我已经修复了这个问题。

HeirarchicalDataTemplate中的ContentPresenterBinding中出现了问题。它只是设置为<Binding />,这是错误的。

我已经改变了HeirarchicalDataTemplate:

<HierarchicalDataTemplate DataType="{x:Type vm:FileSystemNode}"
                          ItemsSource="{Binding Children}">
    <StackPanel Orientation="Horizontal">
        <Image Name="PART_Image"
               Height="35"
               Source="Resources/computer.png"
               Width="35" />
        <TextBlock Margin="5,0"
                   Name="PART_Content"
                   Text="{Binding Path=Name}" />
    </StackPanel>
</HierarchicalDataTemplate>