Pregunta

Tengo problemas serios para crear un WPF TreeView con un enlace de datos de objetos.

La aplicación es el editor de archivos de configuración. He definido una estructura de objeto que se puede serializar en el formato XML correcto.

El problema que tengo es formatear la instancia del objeto en TreeView que muestra la jerarquía correcta. TreeView solo representará el nodo del Canal, y nada más.

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 las clases secundarias de Channel contienen propiedades Id y Name . La clase Filtros es una colección de otros tipos con la misma definición de propiedad.

Aquí está el 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>

El código subyacente para crear la instancia de datos

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;

He visto innumerables ejemplos, pero aún puedo entender qué estoy haciendo mal. Gracias por la ayuda.

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>

No hay cambios en TreeView . Con este cambio, solo tengo el Channel en la lista, y nada más.

¿Fue útil?

Solución

Ampliando la respuesta de @ Gimalay, el problema es que TreeView no sabe dónde obtener los datos para los nodos secundarios. Usted informa a TreeView utilizando un HierarchialDataTemplate , en lugar de un 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>

La principal diferencia entre los dos es el atributo ItemsSource . Esta es una expresión de enlace que devuelve una colección de objetos para usar como nodos secundarios.

El problema es que tiene algunas propiedades para obtener hijos, no solo una. Debe combinarlos todos en una propiedad o agregar otra propiedad que devuelva todos los nodos secundarios.

Finalmente, deberá definir una DataTemplate para cada tipo de elemento secundario, de modo que TreeView sepa cómo mostrarlos (puede usar un HierarchialDataTemplate también para los elementos secundarios, si a su vez tienen nodos secundarios).

Otros consejos

No creo que necesite esta plantilla, ya que los Objetos nunca aparecen en la imagen en su TreeView.

<HierarchicalDataTemplate
    DataType="{x:Type ConfigurationEditor:Objects}" 
    ItemsSource="{Binding Path=Channels}"/>

Has configurado esto en XAML, que muestra los canales ... ¡perfecto!

<TreeView 
    Margin="12,12,12,96" Name="treeView1" 
    ItemsSource="{Binding Source={StaticResource data}, Path=Channels}">
</TreeView>

Ahora quiere que se muestre el Reader también. Pero uno solo puede especificar objetos de tipo IEnumerable como ItemsSource , por lo que la plantilla a continuación es incorrecta, que especifica " Reader " como ItemsSource.

<HierarchicalDataTemplate 
    DataType="{x:Type ConfigurationEditor:Channel}" 
    ItemsSource="{Binding Path=Reader}">
    <TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>

Entonces, si desea que también se muestre el nombre del lector, use la plantilla como se muestra a continuación.

<DataTemplate 
    DataType="{x:Type ConfigurationEditor:Channel}" >
    <StackPanel>
        <TextBlock Text="{Binding Path=Name}"/>
        <TextBlock Text="{Binding Path=Reader.Name}"/>
    <StackPanel>
</DataTemplate>

Por el simple hecho de hacerlo, las plantillas de datos jerárquicos se usan cuando la fuente de elementos tiene una jerarquía en sí misma. Es decir, los objetos en sí tienen cierta jerarquía, y los objetos principales mantienen una lista de objetos secundarios.

Como ejemplo, puede ser que cada canal tenga una propiedad SubChannels como se muestra a continuación.

public class Channel 
{
    public string Id { get; set; }
    public string Name { get; set; }
    public ObservableCollection<Channel> SubChannels { get; }
}

Entonces podríamos haber usado una plantilla como esta.

<HierarchicalDataTemplate 
    DataType="{x:Type ConfigurationEditor:Channel}" 
    ItemsSource="{Binding Path=SubChannels}">
    <TextBlock Text="{Binding Path=Name}"/>
</HierarchicalDataTemplate>

Ahora la estructura del objeto podría ser multinivel con cada subcanal nuevamente teniendo subcanales.

Además, tenga en cuenta que en el ejemplo acabo de usar el mismo tipo, Canal, para crear una jerarquía de subcanales. Podríamos haber usado otro tipo, digamos que cada canal tiene una lista de lectores.

Bien, después de mucha prueba y error, pude hacer que funcionara como quería. Así es como lo hice.

En mi objeto Channel agregué una nueva propiedad que era una colección de todas las otras propiedades que quería mostrar en 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;
                }
            }
        }
    }
}

Entonces mi XAML es el siguiente para mostrar la vista de árbol.

    <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>

Gracias Andy por ponerme en el camino correcto.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top