O TreeView não exibir hierarquia de objetos
-
06-07-2019 - |
Pergunta
Eu estou tendo alguns problemas graves criando um WPF TreeView com uma ligação de dados de objetos.
A aplicação é Config Editor arquivo. Eu definida uma estrutura de objeto que pode ser serializado para o formato XML correto.
O problema que estou tendo está formatando a instância do objeto na TreeView mostrando a hierarquia correta. O TreeView só irá tornar o nó Channel, e nada mais.
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; }
}
Todas as crianças classes de Channel
contêm propriedades Id
e Name
. A classe Filtros é uma coleção de outros tipos com a mesma definição de propriedade.
Aqui está o 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>
O atrás de código para criar a instância de dados
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;
Eu olhei para inúmeros exemplos, mas eu ainda pode descobrir o que estou fazendo de errado. Obrigado pela ajuda.
Update:
<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>
Sem alterações ao TreeView
. Com esta mudança ainda tenho apenas o Channel
listado, e nada mais.
Solução
Expandindo resposta da @ Gimalay, o problema é que o TreeView
não sabe onde obter os dados para todos os nós filho. Você informa o TreeView
usando um HierarchialDataTemplate
, em vez de um DataTemplate
:
<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>
A principal diferença entre os dois é o atributo ItemsSource
. Esta é uma expressão de ligação que retorna uma coleção de objetos para usar como nós filho.
O problema é que você tem algumas propriedades para obter as crianças de, não apenas um. Você quer necessidade de combiná-los todos em uma propriedade, ou adicionar outra propriedade que retorna todos os nós filho.
Finalmente, você precisa definir uma DataTemplate
para cada tipo de item de criança, de modo que o TreeView
sabe como exibi-los (você pode usar um HierarchialDataTemplate
para as crianças, bem como, se eles por sua vez, tem nós filhos).
Outras dicas
Eu não acho que você precisa deste modelo, como objectos nunca vêm na imagem em sua TreeView.
<HierarchicalDataTemplate
DataType="{x:Type ConfigurationEditor:Objects}"
ItemsSource="{Binding Path=Channels}"/>
Você definir isso no XAML, que mostra os canais .. perfeito!
<TreeView
Margin="12,12,12,96" Name="treeView1"
ItemsSource="{Binding Source={StaticResource data}, Path=Channels}">
</TreeView>
Agora você quer o leitor a ser mostrado também. Mas só se pode especificar os objetos do tipo IEnumerable como um ItemsSource , de modo que o modelo abaixo é incorreta que especifica "Reader" como o ItemsSource.
<HierarchicalDataTemplate
DataType="{x:Type ConfigurationEditor:Channel}"
ItemsSource="{Binding Path=Reader}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
Então, se você quiser nome leitor a ser mostrado, bem como, utilizar o modelo como mostrado abaixo.
<DataTemplate
DataType="{x:Type ConfigurationEditor:Channel}" >
<StackPanel>
<TextBlock Text="{Binding Path=Name}"/>
<TextBlock Text="{Binding Path=Reader.Name}"/>
<StackPanel>
</DataTemplate>
Apenas para o bem dela, modelos de dados hierárquicos são usados ??quando a fonte de itens realmente tem uma hierarquia dentro de si. Ou seja, os próprios objetos têm alguma hierarquia, com os pais objetos manter uma lista de objetos filho.
Como exemplo, pode ser a cada canal tem uma propriedade subcanais como abaixo.
public class Channel
{
public string Id { get; set; }
public string Name { get; set; }
public ObservableCollection<Channel> SubChannels { get; }
}
Em seguida, poderíamos ter usado um modelo como este.
<HierarchicalDataTemplate
DataType="{x:Type ConfigurationEditor:Channel}"
ItemsSource="{Binding Path=SubChannels}">
<TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>
Agora, a estrutura do objeto poderia ser profundo multinível com cada subcanal novamente tendo subcanais.
Além disso, fazer nota que, no exemplo eu usei apenas o mesmo tipo, Channel, para criar uma hierarquia de subcanais. Poderíamos ter usado outro tipo, dizem que cada canal ter uma lista de leitores.
Ok, depois de muita experimentação um erro eu era capaz de chegar a este trabalho como eu queria. Aqui está como eu fiz isso.
Em meu objeto Channel
eu adicionei uma nova propriedade que era uma coleção de todas as outras propriedades que eu queria exibir na 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;
}
}
}
}
}
Então meu XAML é a seguinte para exibir a árvore.
<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>
Graças Andy por me colocar no caminho certo.