2

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>();
    }
}

プレゼンテーション層では、ツリービューで表示するためにさらにプロパティが必要です。Folder クラスを変更できません。

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

    public bool IsExpanded { get; set; }
}

この例では、ルートレベルのテスト データを生成する ViewModel クラスを使用しています。タスクを介して、3 秒後にテスト データの変化をシミュレートします。1 つのルート フォルダーと 1 つの子フォルダーを追加します。

// 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 であるすべてのフォルダーを追跡するための辞書を実装し、1 つのフォルダーまたは 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 には、2 つの TreeView しかありません。最初のものは、元のフォルダー (TreeViewFolder ではなく) を使用するとすべてが機能することを示すためのものです。2 番目の 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 をフォルダーの子のカウントにバインドします。

ここでの問題は、2 番目の TreeView で、TextBlock がエントリ "2" の 3 秒後に Count=1 を表示することです。ただし、TreeView には [+] は表示されません。[+] はエントリ「3」にあります。けれど。上部の TreeView はうまく機能します。

ここで何が問題なのか教えてください。一番下の TreeView の 2 番目のエントリに [+] を表示するにはどうすればよいですか?

TreeViewFolder のプロパティ (継承、リフレクションなど) を使用してフォルダーを展開する他の方法があることは知っていますが、実際のアプリには適していません。ここでの主な問題は、対応する TreeViewFolder が異なるプロパティで n 回存在するアプリケーション全体に対して、1 つのフォルダーが 1 回だけ存在することです...

前もってありがとうソコ

4

2 に答える 2

1

ここでの問題は、WPF が ObservableCollection を ObservableCollection に変換できないことのようです。Convert() メソッドの IEnumerable の targetType は、その問題に対する良いヒントを与えてくれます。ObservableCollection を要求するように WPF に指示する方法が見つかりませんでした。ObservableCollection を返しても、WPF はそれを 1 つのように扱いません...

そのため、多くの試行錯誤の末、問題を解決する回避策を思いつきました。基本的に、コレクションの 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 メソッドが呼び出されます。

于 2012-06-13T00:56:57.253 に答える
0

あなたが言った ...

コンバーターは特別なものではありません。すでにTreeViewFolderであるすべてのフォルダーを追跡するための辞書を実装し、1つのフォルダーまたはIEnumerableをTreeViewFolderまたはIEnumerableに変換できます。逆変換は実装されていません。

しかし、それがすべての原因ですよね!監視可能なコレクションを辞書(監視不可能IEnumerable)に変換することにより、WPFGUIの継続的な更新機能が失われます。

INotifyCollectionChangedインターフェイスを使用してカスタムディクショナリを実装する必要があります。次に、Folders.CollectionChangedイベントを処理するときに、監視可能なコレクションで変更された内容に従ってディクショナリを同期し、独自のCustomDictionarty.CollectionChangedイベントを発生させます。

于 2012-06-08T06:55:12.387 に答える