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

时间:2014-08-15 21:54:28

标签: c# wpf xaml recursion treeview

我在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。然后,Icon中的MyComputerNode属性会在无限递归中被一遍又一遍地调用。我知道这是一次无限递归,因为在几秒钟之后,该过程停止了StackoverFlowException.所有我能想到的是Image控件不喜欢我的URI理由并不断尝试加载它。

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

对于我的生活,我不知道这个递归电话的来源。我甚至将Icon属性的类型更改为字符串,但它仍然会发生。

我做错了什么?

1 个答案:

答案 0 :(得分:1)

我已经解决了这个问题。

问题发生在BindingContentPresenter的{​​{1}}。它只是设置为HeirarchicalDataTemplate,这是错误的。

我已更改<Binding />

HeirarchicalDataTemplate

这可以在不抛出StackOverflowException的情况下工作。