Вопрос

У меня возникли серьезные проблемы с созданием WPF TreeView с привязкой объекта к данным.

Приложение является редактором конфигурационных файлов.Я определил структуру объекта, которая может быть сериализована в правильный формат XML.

Проблема, с которой я сталкиваюсь, заключается в форматировании экземпляра объекта в TreeView, показывающем правильную иерархию.TreeView будет отображать только узел канала, и ничего больше.

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 не знает, где взять данные для каких-либо дочерних узлов.Вы информируете TreeView используя HierarchialDataTemplate, а не как 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>

Основное различие между ними заключается в ItemsSource атрибут.Это выражение привязки, которое возвращает коллекцию объектов для использования в качестве дочерних узлов.

Проблема в том, что у вас есть несколько свойств для получения дочерних элементов, а не только одно.Вам нужно либо объединить их все в одно свойство, либо добавить другое свойство, которое возвращает все дочерние узлы.

Наконец, вам нужно будет определить 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, таким образом, приведенный ниже шаблон неверен, который указывает "Reader" в качестве 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>

Просто ради этого иерархические шаблоны данных используются, когда источник элементов действительно имеет иерархию внутри себя.То есть сами объекты имеют некоторую иерархию, при этом родительские объекты хранят список дочерних объектов.

В качестве примера, может быть, что каждый канал имеет свойство SubChannels, как показано ниже.

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, для создания иерархии подканалов.Мы могли бы использовать другой тип, скажем, у каждого канала есть список читателей.

Итак, после долгих проб и ошибок я смог заставить это работать так, как я хотел.Вот как я это сделал.

В моем Channel object Я добавил новое свойство, которое представляло собой набор всех других свойств, которые я хотел отобразить в 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 выглядит следующим образом для отображения treeview.

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

Спасибо Энди, что наставил меня на правильный путь.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top