【问题标题】:ObservableCollection with Converter in TreeView doesn't get updated in WPF在 WPF 中未更新 TreeView 中带有转换器的 ObservableCollection
【发布时间】:2012-06-08 05:55:12
【问题描述】:

我有一个大问题,如果我是 带有 的 ItemSource (),递归 不会更新任何孩子。 我制作了一个小示例 WPF 应用程序来解释令人讨厌的行为。

首先我有一个名为 Folder 的类。文件夹集合是从真实应用程序中的 DataLayer 交付的。这是简单的示例类:

// Represents a Folder from the database
public class Folder
{
    public string Label { get; set; }

    // Data recursion
    public ObservableCollection<Folder> Children { get; set; }

    public Folder()
    {
        Children = new ObservableCollection<Folder>();
    }
}

在表示层中,我需要更多属性才能在树视图中查看它。我无法更改文件夹类。

// Hold additional data to a Folder
public class TreeViewFolder
{
    public Folder Folder { get; set; }

    public bool IsExpanded { get; set; }
}

在我的示例中,我有一个 ViewModel 类,它生成一些根级别的测试数据。通过一个任务,我模拟了 3 秒后测试数据的变化:添加一个根文件夹和一个子文件夹。

// ViewModel to MainWindow.xaml
public class ViewModel
{
    public ObservableCollection<Folder> Folders { get; set; }
    public ObservableCollection<TreeViewFolder> TreeViewFolders { get; set; }

    public ViewModel()
    {
        // childfolder
        var childs = new ObservableCollection<Folder>();
        childs.Add(new Folder{Label="3.1"});

        // Generate some test data
        Folders = new ObservableCollection<Folder>
                      {
                          new Folder {Label = "1."},
                          new Folder {Label = "2."},
                          new Folder {Label = "3.", Children = childs},
                          new Folder {Label = "4."}
                      };

        // Add the test data to new TreeViewFolders
        TreeViewFolders = new ObservableCollection<TreeViewFolder>
                      {
                          new TreeViewFolder{Folder = Folders[0]},
                          new TreeViewFolder{Folder = Folders[1]},
                          new TreeViewFolder{Folder = Folders[2]},
                          new TreeViewFolder{Folder = Folders[3]}
                      };

        // This is the UI Thread
        Execute.InitializeWithDispatcher();

        // Wait for 3 seconds...
        Task.Factory.StartNew(() =>
            System.Threading.Thread.Sleep(3000))
            // ...and then add a new main folder and one child folder
            .ContinueWith((pre) =>
                Execute.InvokeOnUIThread(() => {
                    Folders.Add( new Folder() {Label = "5."});
                    TreeViewFolders.Add(new TreeViewFolder{Folder = Folders[4]});
                    Folders[1].Children.Add(new Folder {Label = "2.1"});
                }));
    }
}

转换器实现了一个字典来跟踪所有已经是 TreeViewFolder 的文件夹,并且能够将一个文件夹或 ObservableCollection 转换为 TreeViewFolder 或 ObservableCollection。没有实现反向转换。

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
    {

        //DictionaryLookUp just holds a Folder <-> TreeViewFolder relation in a Dictionary<Folder, TreeViewFolder> for performance issues

        // Single object
        if (targetType == typeof(TreeViewFolder)) {
            var inputFolder = value as Folder;
            if (inputFolder == null) return null;
            return DictionaryLookUp(inputFolder);
        }

        // Collection of Folders
        // WPF SAYS IT WANTS AN IENUMERABLE??? I GIVE AN OBSERVABLECOLLECTION
        if (targetType == typeof(IEnumerable)) {
            var inputFolders = value as IEnumerable<Folder>;
            if (inputFolders == null) return null;

            var outputFolders = new ObservableCollection<TreeViewFolder>();

            foreach (var folder in inputFolders) {
                outputFolders.Add(DictionaryLookUp(folder));
            }

            return outputFolders;
        }
        //Error!
        return null;
    }

在 MainWindow.xaml 中,我只有两个 TreeView。第一个再次只是为了证明当我使用原始文件夹(而不是 TreeViewFolder)时一切正常。第二个 TreeView 正是我所需要的。

<Window x:Class="TreeViewConverterBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeViewConverterBinding"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Window.Resources>
        <local:FolderToTreeViewFolderConverter x:Key="FtTVFConverter" />
    </Window.Resources>
    <StackPanel >
        <Label Content="TreeView for Folders: Binding without Converter" />
        <TreeView ItemsSource="{Binding Folders}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate 
                    DataType="{x:Type local:Folder}"
                    ItemsSource="{Binding Children}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Label}"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
        <Label Content="TreeView for TreeViewFolders: Binding to same Source with Converter" />
        <TreeView ItemsSource="{Binding TreeViewFolders}">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate 
                    DataType="{x:Type local:TreeViewFolder}"
                    ItemsSource="{Binding Folder.Children, Converter={StaticResource FtTVFConverter}}">
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding Folder.Label}"/>
                        <TextBlock Text=" Folder.Children.Count=" />
                        <TextBlock Text="{Binding Folder.Children.Count}"/>
                    </StackPanel>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </StackPanel>
</Window>

此外,我将一个特殊的 TextBlock 绑定到文件夹的子项计数。

这里的问题是,在第二个 TreeView 中,TextBlock 在条目“2”的 3 秒后显示 Count=1。但树视图中没有出现 [+]。 [+] 在条目“3”中。尽管。上面的 TreeView 效果很好!

谁能告诉我这里有什么问题?如何使 [+] 出现在底部 TreeView 的第二个条目中?

我知道还有其他方法可以使用 TreeViewFolder 中的属性(继承、反射等)扩展文件夹,但不适用于实际应用程序。 这里的主要问题是,一个文件夹对于整个应用程序只存在一次,而相应的 TreeViewFolder 存在 n 次具有不同的属性...

提前致谢 索科

【问题讨论】:

  • 如果我使用 icommand 而不是你的 task.factory 来添加文件夹内容,它对我有用。在这种情况下,我也摆脱了转换器。
  • 感谢您的意见。但是我必须使用转换器,因为我需要 TreeView 的所有级别中的 TreeViewFolder 实例。事实上,如果您只是删除 ItemSource 绑定中的“,Converter={StaticResource FtTVFConverter}”,一切正常 - 我知道这一点。但这不是我需要的......

标签: treeview binding observablecollection converter wpf binding treeview observablecollection converter


【解决方案1】:

这里的问题似乎是 WPF 无法将 ObservableCollection 转换为 ObservableCollection。 Convert() 方法中 IEnumerable 的 targetType 很好地提示了该问题。我发现没有办法告诉 WPF 请求 ObservableCollection。即使我返回 ObservableCollection WPF 也不会将其视为一个...

所以经过大量尝试和错误后,我想出了一个解决问题的解决方法。 基本上它使用集合的 Count 属性来触发 WPF 中的更新。

这是初始帖子的 xaml 中更改的部分:

                <HierarchicalDataTemplate.ItemsSource>
                    <MultiBinding Converter="{StaticResource FtTVFMultiConverter}">
                        <Binding Path="Folder.Children" />
                        <Binding Path="Folder.Children.Count" />
                    </MultiBinding>
                </HierarchicalDataTemplate.ItemsSource>

FtTVFMultiConverter是一个只实现IMultiValueConverter接口的Convert()的类:

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    {
        var inputFolders = values[0] as ObservableCollection<Folder>;
        if (inputFolders == null) return null;

        var outputTreeViewFolders = new ObservableCollection<TreeViewFolder>();

        foreach (var folder in inputFolders) {
            outputTreeViewFolders.Add(new TreeViewFolder { Folder = folder });
        }

        return outputTreeViewFolders;
    }

因此,ViewModel 中源的更改由 Folder.Children.Count 属性的更改触发。因此调用 Convert 方法来传递新添加的 Childs。

【讨论】:

    【解决方案2】:

    你说...

    转换器没什么特别的。它实现了一个字典来保存 跟踪所有已经是 TreeViewFolder 并且能够 将一个文件夹或 IEnumerable 转换为 TreeViewFolder 或 可数的。没有实现反向转换。

    但这就是造成这一切的原因,不是吗!通过将您的可观察集合转换为字典(这是不可观察的IEnumerable),您已经失去了 WPF GUI 的持续更新能力!!

    您需要使用INotifyCollectionChanged 接口实现自定义字典,然后在处理Folders.CollectionChanged 事件时,根据可观察集合中已更改的内容同步字典并引发其自己的CustomDictionarty.CollectionChanged 事件。

    【讨论】:

    • 您好 AngelWPF,感谢您的意见。我在我的问题中包含了 Convert 方法 - 请看一下。当然,我返回一个 ObservableCollection。问题是,调试器告诉我 targetType 是一个 IEnumerable!?字典只是保持一对一的关系。这只是一个性能优化。谢谢索科
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-01-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    相关资源
    最近更新 更多