如何以分层方式将ObservableCollection绑定到TreeView?

时间:2017-09-19 12:45:41

标签: c# wpf treeview observablecollection

我知道这可能是重复但没有解决方案适合我。所以我有一个类Technik,它具有以下属性:

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>中,如:

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

现在我无处可去,所以也许你们那里的人可以帮助我。 实际我的树视图如下所示:

<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不是正确的集合,这是正确的集合,还有一些代码我可以更改集合。

1 个答案:

答案 0 :(得分:0)

我对这种设计持怀疑态度。为什么您觉得用户提供单个对象及其属性值对于它有用且有用,就像该对象具有某种层次结构一样?

如果您要尝试做的只是在用户界面上强加一些可视结构,那么在不使用TreeView的情况下即可轻松完成。例如:

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类一起使用。例如:

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>

这种替代方案依赖于三个关键类:

  • ConvertingObservableCollection<T> - 这是根据原始集合的当前状态观察原始集合并呈现转换项目的工作。
  • ConvertingCollectionConverter - 这会将原始集合转换为ConvertingObservableCollection<T>对象,以便绑定到TreeView
  • TableItemHierarchicalConverter - 这会将单个原始项目对象转换为适合在TreeView中显示的对象层次结构。

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

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