ツリービューにオブジェクト階層が表示されない
-
06-07-2019 - |
質問
Object databindingを使用してWPF TreeViewを作成すると、深刻な問題が発生します。
アプリケーションは、構成ファイルエディターです。正しいXML形式にシリアル化できるオブジェクト構造を定義しました。
私が抱えている問題は、正しい階層を示すTreeViewのオブジェクトインスタンスをフォーマットすることです。 TreeViewはChannelノードのみをレンダリングし、それ以外は何もレンダリングしません。
public class Objects
{
public List<Channel> Channels { get; set; }
}
public class Channel
{
public string Id { get; set; }
public string Name { get; set; }
public Reader Reader { get; set; }
public Filters Filters { get; set; }
public Router Router { get; set; }
public Persister Persister { get; set; }
}
public class Filters : ArrayList
{
public string StopOnFailure { get; set; }
}
public class Reader
{
public string Id { get; set; }
public string Name { get; set; }
}
Channel
のすべての子クラスには、プロパティ Id
および Name
が含まれます。 Filtersクラスは、同じプロパティ定義を持つ他の型のコレクションです。
XAMLは次のとおりです
<Window.Resources>
<ObjectDataProvider x:Key="data"/>
<DataTemplate DataType="{x:Type ConfigurationEditor:Channel}">
<WrapPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
<TextBlock Text=" [" />
<TextBlock Text="{Binding Path=Id}" />
<TextBlock Text="]" />
</WrapPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type ConfigurationEditor:Reader}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</Window.Resources>
<Grid>
<TreeView Margin="12,12,12,96" Name="treeView1" ItemsSource="{Binding Source={StaticResource data}, Path=Channels}">
</TreeView>
</Grid>
データインスタンスを作成するためのコードビハインド
Objects config;
var serializer = new XmlSerializer(typeof(Objects));
using (var stream = new FileStream(@"C:\test.xml", FileMode.Open))
{
config = (Objects)serializer.Deserialize(stream);
}
var dp = (ObjectDataProvider)FindResource("data");
dp.ObjectInstance = config;
数え切れないほどの例を見てきましたが、私が何を間違っているのかを理解することはできます。助けてくれてありがとう。
更新:
<HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Objects}" ItemsSource="{Binding Path=Channels}"/>
<HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Channel}" ItemsSource="Binding Path=Reader}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type ConfigurationEditor:Reader}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
TreeView
に変更はありません。この変更により、 Channel
のみがリストされ、他には何もありません。
解決
@Gimalayの答えを拡大すると、問題は TreeView
が子ノードのデータをどこから取得するかを知らないことです。 DataTemplate
ではなく、 HierarchialDataTemplate
を使用して、 TreeView
に通知します。
<HierarchialDataTemplate DataType="{x:Type ConfigurationEditor:Channel}"
ItemsSource="...">
<WrapPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
<TextBlock Text=" [" />
<TextBlock Text="{Binding Path=Id}" />
<TextBlock Text="]" />
</WrapPanel>
</HierarchialDataTemplate>
2つの主な違いは、 ItemsSource
属性です。これは、子ノードとして使用するオブジェクトのコレクションを返すバインディング式です。
問題は、1つだけでなく、子を取得するためのプロパティがいくつかあることです。これらをすべて1つのプロパティに結合するか、すべての子ノードを返す別のプロパティを追加する必要があります。
最後に、子アイテムのタイプごとに DataTemplate
を定義する必要があります。これにより、 TreeView
はそれらの表示方法を認識します(子ノードにも子ノードがある場合は、子のHierarchialDataTemplate
も同様です。
他のヒント
TreeViewのオブジェクトがオブジェクトに表示されることはないため、このテンプレートは必要ないと思います。
<HierarchicalDataTemplate
DataType="{x:Type ConfigurationEditor:Objects}"
ItemsSource="{Binding Path=Channels}"/>
XAMLでこれを設定すると、チャンネルが表示されます。完璧!
<TreeView
Margin="12,12,12,96" Name="treeView1"
ItemsSource="{Binding Source={StaticResource data}, Path=Channels}">
</TreeView>
ここで、リーダーも表示するようにします。 ただし、ItemsSourceとして指定できるのはIEnumerable型のオブジェクトのみです。したがって、以下のテンプレートは正しくありません。 ItemsSourceとして。
<HierarchicalDataTemplate
DataType="{x:Type ConfigurationEditor:Channel}"
ItemsSource="{Binding Path=Reader}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
したがって、読者名も表示する場合は、以下に示すテンプレートを使用します。
<DataTemplate
DataType="{x:Type ConfigurationEditor:Channel}" >
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Reader.Name}"/>
<StackPanel>
</DataTemplate>
そのために、アイテムソースが実際にそれ自体の内部に階層を持つ場合、階層データテンプレートが使用されます。つまり、オブジェクト自体には何らかの階層があり、親オブジェクトには子オブジェクトのリストが保持されます。
例として、各チャンネルには以下のようなサブチャンネルのプロパティがあります。
public class Channel
{
public string Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Channel> SubChannels { get; }
}
この場合、このようなテンプレートを使用できます。
<HierarchicalDataTemplate
DataType="{x:Type ConfigurationEditor:Channel}"
ItemsSource="{Binding Path=SubChannels}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
オブジェクト構造は、各サブチャネルが再びサブチャネルを持つようにマルチレベルの深さになる可能性があります。
また、この例では、同じタイプのチャンネルを使用してサブチャンネルの階層を作成したことに注意してください。別のタイプを使用することもできます。たとえば、各チャンネルにはリーダーのリストがあります。
さて、多くの試行錯誤の後、私はこれを望みどおりに動作させることができました。これが私がやった方法です。
Channel
オブジェクトに、TreeViewに表示したい他のすべてのプロパティのコレクションである新しいプロパティを追加しました
public ArrayList Components
{
get { return new ArrayList { Reader, Filters, Router, Persister }; }
set
{
ArrayList list = value;
if (list != null)
{
foreach (var item in list)
{
if (item is Reader)
{
Reader = (Reader)item;
}
else if (item is Router)
{
Router = (Router) item;
}
else if (item is Persister)
{
Persister = (Persister) item;
}
else if (item is Filters)
{
Filters = (Filters) item;
}
}
}
}
}
その後、ツリービューを表示するXAMLは次のとおりです。
<HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Channel}" ItemsSource="{Binding Path=Components}">
<WrapPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" />
<TextBlock Text=" [" />
<TextBlock Text="{Binding Path=Id}" />
<TextBlock Text="]" />
</WrapPanel>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type ConfigurationEditor:Filters}" ItemsSource="{Binding Path=.}">
<TextBlock Text="Filters"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate >
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type ConfigurationEditor:Reader}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type ConfigurationEditor:Router}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<DataTemplate DataType="{x:Type ConfigurationEditor:Persister}">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
私を正しい軌道に乗せてくれたアンディに感謝します。