如何以分层方式将ObservableCollection绑定到TreeView? [英] How to bind an ObservableCollection to a TreeView in a hierarchical way?

查看:73
本文介绍了如何以分层方式将ObservableCollection绑定到TreeView?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我知道这可能是重复的,但是没有解决方案对我有用.所以我有一个具有以下属性的类Technik:

I know this may be a duplicate but no solution works for me. So I have a class Technik which has these properties:

public class Technik
{
    public bool checkedTe { get; set; }

    public int TechnikID { get; set; }

    public string anlagengruppe { get; set; }

    public string techniktyp { get; set; }

    public string anlage { get; set; }

    public string bemerkung { get; set; }
}

现在我有一个具有216行的DataTable,每行都进入一个Technik对象,该对象添加到我的ObservableCollection<Technik>中,例如:

Now I have a DataTable with 216 rows and each row is getting into a Technik object that is added into my ObservableCollection<Technik> like:

foreach (DataRow dr in dtTechnik.Rows)
{
     Technik technik = new Technik();

     technik.checkedTe = (bool)dr.ItemArray[0];
     technik.TechnikID = (int)dr.ItemArray[1];
     technik.anlagengruppe = (string)dr.ItemArray[2];
     technik.techniktyp = (string)dr.ItemArray[3];
     technik.anlage = (string)dr.ItemArray[4];
     technik.bemerkung = (string)dr.ItemArray[5];

     TechnikCollection.Add(technik);
}

我想像这样绑定ObservableCollection:

* anlagengruppe
    * techniktyp
          *anlage
             * TechnikID

现在我无处可去,所以也许你们在那里可以帮助我. 实际上我的树状视图看起来像这样:

Right now I'm getting nowhere, so maybe you guys out there can help me. Actual my tree view looks like this:

<TreeView x:Name="treeView" HorizontalAlignment="Left" Height="850" Margin="10,0,0,0" VerticalAlignment="Top" Width="464" 
          ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Visible" ItemsSource="{Binding TechnicTable}">
    <TreeView.ItemTemplate>
        <HierarchicalDataTemplate ItemsSource="{Binding TechnicTable}">
            <TextBlock  Text="{Binding Path=anlagengruppe}" />
            <HierarchicalDataTemplate.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding techniktyp}" />
                </DataTemplate>
            </HierarchicalDataTemplate.ItemTemplate>
        </HierarchicalDataTemplate>
    </TreeView.ItemTemplate>
</TreeView>

也许有些人认为我的树视图ItemsSource不是正确的集合,这是正确的集合,在更改集合的地方还有更多代码.

Maybe some of you think that my tree view ItemsSource is not the correct collection, this is the right one, there is some more code where I change collections.

推荐答案

我对此设计表示怀疑.为什么您认为呈现单个对象及其属性值对用户很有帮助,就像该对象具有某种层次结构一样?

I am skeptical of this design. Why do you feel it's useful and helpful to the user to present a single object and its property values as if that object had some hierarchical structure to it?

如果您要做的只是在用户界面上添加一些 visual 结构,则无需使用TreeView即可轻松完成.例如:

If all you're trying to do is impose some visual structure on the user interface, that's easily done without using TreeView. For example:

class TableItem
{
    public string Property1 { get; set; }
    public string Property2 { get; set; }
    public string Property3 { get; set; }

    public TableItem() { }

    public TableItem(string property1, string property2, string property3)
    {
        Property1 = property1;
        Property2 = property2;
        Property3 = property3;
    }
}

class ViewModel
{
    public ObservableCollection<TableItem> TableItems { get; } = new ObservableCollection<TableItem>();
}

<Window x:Class="TestSO46300831HiearchicalObservable.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:l="clr-namespace:TestSO46300831HiearchicalObservable"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <l:ViewModel>
      <l:ViewModel.TableItems>
        <l:TableItem Property1="Item #1, property #1"
                     Property2="Item #1, property #2"
                     Property3="Item #1, property #3"/>
        <l:TableItem Property1="Item #2, property #1"
                     Property2="Item #2, property #2"
                     Property3="Item #2, property #3"/>
        <l:TableItem Property1="Item #3, property #1"
                     Property2="Item #3, property #2"
                     Property3="Item #3, property #3"/>
      </l:ViewModel.TableItems>
    </l:ViewModel>
  </Window.DataContext>

  <Window.Resources>
    <DataTemplate DataType="{x:Type l:TableItem}">
      <StackPanel>
        <TextBlock Text="{Binding Property1}"/>
        <TextBlock Text="{Binding Property1}" Margin="10,0,0,0"/>
        <TextBlock Text="{Binding Property1}" Margin="20,0,0,0"/>
      </StackPanel>
    </DataTemplate>
  </Window.Resources>

  <StackPanel>
    <ListBox ItemsSource="{Binding TableItems}"/>
  </StackPanel>
</Window>

也就是说,如果您必须使用TreeView,并且希望在修改集合时更新视图,那么在我看来,您可以通过使用实现INotifyCollectionChanged(只需继承ObservableCollection<T>并跟踪原始集合即可轻松完成.需要中间集合,以便可以将项目从原始单对象项目转换为可以与TreeView一起使用的分层项目类型类.例如:

That said, if you must use TreeView and you want for the view to update as the collection is modified, it seems to me you can accomplish that by using an intermediate collection that implements INotifyCollectionChanged (easily done simply by inheriting ObservableCollection<T> and tracking the original collection. The intermediate collection is needed, so that items can be converted from the original single-object item to a hierarchical item type that can be used with the TreeView class. For example:

class HierarchicalTableItem
{
    public string Text { get; }
    public IReadOnlyList<HierarchicalTableItem> Items { get; }

    public HierarchicalTableItem(string text, HierarchicalTableItem child = null)
    {
        Text = text;
        Items = child != null ? new[] { child } : null;
    }
}

class ViewModel
{
    public ICommand AddCommand { get; }
    public ICommand InsertCommand { get; }
    public ICommand RemoveCommand { get; }

    public int Index { get; set; }

    public ObservableCollection<TableItem> TableItems { get; } = new ObservableCollection<TableItem>();

    public ViewModel()
    {
        AddCommand = new DelegateCommand(() => TableItems.Add(_CreateTableItem()));
        InsertCommand = new DelegateCommand(() => TableItems.Insert(Index, _CreateTableItem()));
        RemoveCommand = new DelegateCommand(() => TableItems.RemoveAt(Index));
    }

    private int _itemNumber;

    private TableItem _CreateTableItem()
    {
        _itemNumber = (_itemNumber < TableItems.Count ? TableItems.Count : _itemNumber) + 1;

        return new TableItem(
            $"Item #{_itemNumber}, property #1",
            $"Item #{_itemNumber}, property #2",
            $"Item #{_itemNumber}, property #3");
    }
}

class ConvertingObservableCollection<T> : ObservableCollection<object>
{
    private readonly IValueConverter _converter;
    private readonly ObservableCollection<T> _collection;

    public ConvertingObservableCollection(IValueConverter converter, ObservableCollection<T> collection)
    {
        _converter = converter;
        _collection = collection;
        _ResetItems();
        _collection.CollectionChanged += _OnCollectionChanged;
    }

    private void _OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                _AddItems(e);
                break;
            case NotifyCollectionChangedAction.Move:
                _RemoveItems(e);
                _AddItems(e);
                break;
            case NotifyCollectionChangedAction.Remove:
                _RemoveItems(e);
                break;
            case NotifyCollectionChangedAction.Replace:
                _ReplaceItems(e);
                break;
            case NotifyCollectionChangedAction.Reset:
                _ResetItems();
                break;
        }
    }

    private void _ReplaceItems(NotifyCollectionChangedEventArgs e)
    {
        for (int i = 0; i < e.NewItems.Count; i++)
        {
            this[i] = _Convert(e.NewItems[i]);
        }
    }

    private void _AddItems(NotifyCollectionChangedEventArgs e)
    {
        for (int i = 0; i < e.NewItems.Count; i++)
        {
            Insert(i + e.NewStartingIndex, _Convert(e.NewItems[i]));
        }
    }

    private void _RemoveItems(NotifyCollectionChangedEventArgs e)
    {
        for (int i = e.OldItems.Count - 1; i >= 0; i--)
        {
            RemoveAt(i + e.OldStartingIndex);
        }
    }

    private void _ResetItems()
    {
        Clear();
        foreach (T t in _collection)
        {
            Add(_Convert(t));
        }
    }

    private object _Convert(object value)
    {
        return _converter.Convert(value, typeof(T), null, null);
    }
}

class TableItemHierarchicalConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        TableItem tableItem = value as TableItem;

        if (tableItem == null)
        {
            return Binding.DoNothing;
        }

        return new HierarchicalTableItem(tableItem.Property1,
                    new HierarchicalTableItem(tableItem.Property2,
                        new HierarchicalTableItem(tableItem.Property3)));
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

class ConvertingCollectionConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        IValueConverter converter = parameter as IValueConverter;

        if (converter == null || value == null ||
            value.GetType().GetGenericTypeDefinition() != typeof(ObservableCollection<>))
        {
            return Binding.DoNothing;
        }

        Type resultType = typeof(ConvertingObservableCollection<>).MakeGenericType(value.GetType().GenericTypeArguments);

        return Activator.CreateInstance(resultType, converter, value);
    }

    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        throw new NotImplementedException();
    }
}

<Window x:Class="TestSO46300831HiearchicalObservable.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:l="clr-namespace:TestSO46300831HiearchicalObservable"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
  <Window.DataContext>
    <l:ViewModel>
      <l:ViewModel.TableItems>
        <l:TableItem Property1="Item #1, property #1"
                     Property2="Item #1, property #2"
                     Property3="Item #1, property #3"/>
        <l:TableItem Property1="Item #2, property #1"
                     Property2="Item #2, property #2"
                     Property3="Item #2, property #3"/>
        <l:TableItem Property1="Item #3, property #1"
                     Property2="Item #3, property #2"
                     Property3="Item #3, property #3"/>
      </l:ViewModel.TableItems>
    </l:ViewModel>
  </Window.DataContext>

  <Window.Resources>
    <l:ConvertingCollectionConverter x:Key="convertingCollectionConverter1"/>
    <l:TableItemHierarchicalConverter x:Key="tableItemConverter1"/>
  </Window.Resources>

  <ScrollViewer>
    <StackPanel>
      <UniformGrid Columns="4">
        <Button Content="Add" Command="{Binding AddCommand}"/>
        <Button Content="Insert" Command="{Binding InsertCommand}"/>
        <Button Content="Remove" Command="{Binding RemoveCommand}"/>
        <TextBox Text="{Binding Index}"/>
      </UniformGrid>
      <TreeView ItemsSource="{Binding TableItems,
              Converter={StaticResource convertingCollectionConverter1},
              ConverterParameter={StaticResource tableItemConverter1}}">
        <TreeView.ItemTemplate>
          <HierarchicalDataTemplate ItemsSource="{Binding Items}">
            <TextBlock Text="{Binding Text}"/>
          </HierarchicalDataTemplate>
        </TreeView.ItemTemplate>
      </TreeView>
    </StackPanel>
  </ScrollViewer>
</Window>

此替代方法依赖于三个关键类:

This alternative relies on three key classes:

  • ConvertingObservableCollection<T>—这项工作是监视原始集合并根据该原始集合的当前状态显示转换后的项目.
  • ConvertingCollectionConverter—出于绑定到TreeView的目的,这会将原始集合转换为ConvertingObservableCollection<T>对象.
  • TableItemHierarchicalConverter—这样会将各个原始项目对象转换为适合在TreeView中显示的对象层次.
  • ConvertingObservableCollection<T> — This does the work of watching the original collection and presenting converted items according to the current state of that original collection.
  • ConvertingCollectionConverter — This converts the original collection to the ConvertingObservableCollection<T> object for the purpose of binding to the TreeView.
  • TableItemHierarchicalConverter — This converts the individual original item objects into a hierarchy of objects suitable for display in the TreeView.

当然,还有简单的容器类HierarchicalTableItem,用于表示每个表项的层次结构.

Of course, there is also the simple container class HierarchicalTableItem which is used to represent the hierarchy for each table item.

最后,要记住的关键是,对于TreeView,您必须呈现具有递归性质的项目,以便可以使用单个HierarchicalDataTemplate元素来定义如何当前树的每个级别.这意味着单个数据项必须具有可用于模板ItemsSource的某些属性,该属性本身就是同一类型的数据项的某种类型的集合.

Ultimately, the key to remember is that for a TreeView, you must be presenting items that have a recursive nature, such that a single HierarchicalDataTemplate element can be used to define how to present each level of the tree. This means that a single data item must have some property that can be used for the ItemsSource of the template, which is itself some type of collection of the same type of data item.

这篇关于如何以分层方式将ObservableCollection绑定到TreeView?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆