2009-03-05 2 views
29

У меня есть TreeView, который использует привязку своих данных к HierarchicalDataTemplate.Как получить TreeViewItem из элемента HierarchicalDataTemplate?

Это выглядит следующим образом:

<TreeView x:Name="mainTreeList" ItemsSource="{Binding MyCollection}> 
    <TreeView.Resources> 
    <HierarchicalDataTemplate 
    DataType="{x:Type local:MyTreeViewItemViewModel}" 
    ItemsSource="{Binding Children}"> 
     <!-- code code code --> 
    </HierarchicalDataTemplate> 
    </TreeView.Resources> 
</TreeView> 

Теперь из кода-позади сказать главное окно, я хочу получить текущий выбранный TreeViewItem. Тем не менее, если я использую:

this.mainTreeList.SelectedItem; 

SelectedItem имеет тип MyTreeViewItemViewModel. Но я хочу получить «родительский» или «связанный» TreeViewItem. Я не передаю это моему объекту TreeViewItemModel (даже не знаю, как это сделать).

Как я могу это сделать?

+0

@romkyns: Что? Этот вопрос не имеет значения * для отслеживания выбранного элемента. –

+0

@romkyns: Ну, вы не должны получать TreeViewItem в первую очередь, так что не имеет значения, трудно ли это сделать или нет. –

+0

@ H.B. Это может быть так ... возможно [вы можете помочь мне сделать это без TreeViewItem] (http://stackoverflow.com/questions/9761336/whats-the-wpf-way-to-mark-a-command-as- недоступен только-если-The-родитель-из-а-дерева)? –

ответ

20

Из записи в блоге Bea Stollnitz «сек об этом, попробуйте

TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); 
+0

Достаточно хорошо для меня, спасибо. (это немного отличается, хотя CurrentItem возвращает объект, а ContainerFromIndex принимает целое число, поэтому мне пришлось использовать CurrentIndex или что-то в этом роде, но хорошо) – Razzie

+0

А, я просто скопировал-n-вставил из блога Bea, а затем изменил несколько вещей. Я не заметил этой ошибки :) Тем не менее, я обновлю свой ответ. –

+7

'CurrentPosition' всегда для меня 0, независимо от того, что выбрано. В блоге вы ссылаетесь на списки, а не на деревья - возможно, поэтому. В связанном сообщении блога нет «HierarchicalDataTemplate». –

0

Нужна ли вы в TreeViewItem, потому что вы собираетесь изменить то, что отображается? Если это так, я бы рекомендовал использовать стиль, чтобы изменить способ отображения элемента вместо использования кода вместо прямого изменения TreeViewItem. Надеюсь, он будет чище.

30
TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); 

НЕ РАБОТАЕТ (для меня), как mainTreeList.Items.CurrentPosition в TreeView с использованием HierarchicalDataTemplate всегда будет -1.

НИКАКИЕ ниже как mainTreeList.Items.CurrentItem в древовидной структуре с использованием HierarchicalDataTemplate всегда будет иметь значение null.

TreeViewItem item = (TreeViewItem)mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromItem(mainTreeList.Items.CurrentItem); 

ВМЕСТО Я должен был установить последний выбранный TreeViewItem в разгромленной случае TreeViewItem.Selected, которые пузырьки до представления дерева (TreeViewItem и сами не существует во время разработки, как мы используем HierarchicalDataTemplate) ,

Событие может быть захвачен в XAML, как так:

<TreeView TreeViewItem.Selected="TreeViewItemSelected" .../> 

Тогда последний TreeViewItem выбранный может быть установлен в том случае, как так:

private void TreeViewItemSelected(object sender, RoutedEventArgs e) 
    { 
     TreeViewItem tvi = e.OriginalSource as TreeViewItem; 

     // set the last tree view item selected variable which may be used elsewhere as there is no other way I have found to obtain the TreeViewItem container (may be null) 
     this.lastSelectedTreeViewItem = tvi; 

     ... 
    } 
+0

Единственный способ для CurrentItem равняться null (или CurrentIndex, равный -1), если в дереве нет элементов. Убедитесь, что есть элементы, прежде чем пытаться получить контейнер товаров. –

+0

CurrentItem - это не то же самое, что SelectedItem. ContainerFromItem возвращает null, если он не может найти контейнер, а null может быть отлит в TreeViewItem просто отлично, без необходимости использовать его как cast. Вы даже запустили код? –

+2

Проблема в том, что вы используете mainTreeList.Items, который будет работать только для элементов на первом уровне дерева: каждый узел дерева (TreeViewItem) имеет свою собственную коллекцию Items - в отличие от примера Bea, который вы цитируете, который использует ListBox и имеет только один уровень предметов. Из этого следует, что вам нужно получить ссылку, выбранную в данный момент TreeViewItem, для которой единственным способом, который я нашел для этого при использовании HierarchicalDataTemplate, является захват события TreeViewItem.Selected, как в моем ответе. – markmnl

6

Я столкнулся с этой же проблемой. Мне нужно было добраться до TreeViewItem, чтобы я мог выбрать его. Я тогда понял, что могу просто добавить свойство IsSelected в мою ViewModel, которое затем привязалось к TreeViewItems IsSelectedProperty. Это может быть достигнуто с ItemContainerStyle:

<TreeView> 
      <TreeView.ItemContainerStyle> 
       <Style TargetType="{x:Type TreeViewItem}"> 
        <Setter Property="IsExpanded" Value="{Binding IsExpanded, Mode=TwoWay}"/> 
        <Setter Property="IsSelected" Value="{Binding IsSelected, Mode=TwoWay}" /> 
       </Style> 
      </TreeView.ItemContainerStyle> 
</TreeView> 

Теперь, если я хочу, чтобы элемент в TreeView выбран, я просто называю IsSelected на моем классе ViewModel непосредственно.

Надеюсь, это поможет кому-то.

+2

По-моему, это лучший способ использовать древовидные образы. Элементы _need_ должны быть viewmodels, чтобы вы могли работать с ними. В противном случае вам придется делать всевозможные махинации, чтобы получить правильное поведение, которое должно быть простым. –

+1

Согласен, однако вопрос в том, как получить _TreeViewItem_, а не объект, который он обертывает. – markmnl

+0

Да, это, конечно, так, как вы должны это делать. Но наличие ViewModel для _evvverything_ является довольно PITA, особенно для небольших базовых пользовательских интерфейсов. –

4

Вдохновленный ответом Fëanor, я попытался сделать TreeViewItem доступным для каждого элемента данных, для которого был создан TreeViewItem.

Идея заключается в том, чтобы добавить TreeViewItem -typed поле в модели представления, также подвергаются через интерфейс, и имеют TreeView автоматически заполнит его всякий раз, когда создается TreeViewItem контейнер.

Это делается путем подкласса TreeView и присоединения события к ItemContainerGenerator, в котором записывается TreeViewItem всякий раз, когда он создается. Gotchas включает в себя тот факт, что TreeViewItem s создаются лениво, поэтому в определенное время действительно невозможно быть доступным.

После публикации этого ответа я разработал это и использовал его в течение длительного времени в одном проекте. Пока нет проблем, кроме того, что это нарушает MVVM (но и экономит вам тонну шаблона для простых случаев). Источник here.

Использование

Выберите родителя выбранного элемента и свернуть его, убедившись, что это в представлении:

... 
var selected = myTreeView.SelectedItem as MyItem; 
selected.Parent.TreeViewItem.IsSelected = true; 
selected.Parent.TreeViewItem.IsExpanded = false; 
selected.Parent.TreeViewItem.BringIntoView(); 
... 

Declarations:

<Window ... 
     xmlns:tvi="clr-namespace:TreeViewItems" 
     ...> 
    ... 
    <tvi:TreeViewWithItem x:Name="myTreeView"> 
     <HierarchicalDataTemplate DataType = "{x:Type src:MyItem}" 
            ItemsSource = "{Binding Children}"> 
      <TextBlock Text="{Binding Path=Name}"/> 
     </HierarchicalDataTemplate> 
    </tvi:TreeViewWithItem> 
    ... 
</Window> 
class MyItem : IHasTreeViewItem 
{ 
    public string Name { get; set; } 
    public ObservableCollection<MyItem> Children { get; set; } 
    public MyItem Parent; 
    public TreeViewItem TreeViewItem { get; set; } 
    ... 
} 

Код

public class TreeViewWithItem : TreeView 
{ 
    public TreeViewWithItem() 
    { 
     ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 
    } 

    private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) 
    { 
     var generator = sender as ItemContainerGenerator; 
     if (generator.Status == GeneratorStatus.ContainersGenerated) 
     { 
      int i = 0; 
      while (true) 
      { 
       var container = generator.ContainerFromIndex(i); 
       if (container == null) 
        break; 

       var tvi = container as TreeViewItem; 
       if (tvi != null) 
        tvi.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged; 

       var item = generator.ItemFromContainer(container) as IHasTreeViewItem; 
       if (item != null) 
        item.TreeViewItem = tvi; 

       i++; 
      } 
     } 
    } 
} 

interface IHasTreeViewItem 
{ 
    TreeViewItem TreeViewItem { get; set; } 
} 
+0

Осмотр этой дороги был очень интересным и формирующим, как работает wpf (иначе черная магия), но ответ с щедростью привел меня к тому, как это сделать правильно. Это не очень хороший способ сделать это (с уважением), потому что элементы получаются регенерированными, и вы продолжаете добавлять обработчики, также вы никогда не удаляете этих обработчиков, потому что статус генератора «удален» отсутствует. Мы разбили события, и мы должны учиться и использовать их. –

+0

@ L.Trabacchin Я бы с уверенностью согласился, что мой подход взломан. TBH, для сложных пользовательских интерфейсов лучший выбор - это почти наверняка отказаться от идеи доступа к TreeViewItems вообще; это просто не то, как WPF предполагается использовать. Вместо этого мы должны обращаться к экземплярам ViewModel. Я не помню, почему щекотливый ответ не сократил его для меня, но он выглядит менее взломанным, чем мой. –

+0

Конечно, еще один хороший способ сделать это - использовать viewmodels, но это зависит от того, чего мы пытаемся достичь, для меня маршрутизированные события работали лучше, потому что я пытался расширить treeview для достижения многоэлементного дерева, но я хотел, чтобы он как можно меньше кода, потому что это может привести к обслуживанию и ошибкам, и хотел, чтобы он работал независимо от того, какая модель используется, я в основном делал это, у меня есть некоторые недостатки прямо сейчас, выставляя выбранные элементы для доступа к другим элементам управления. .. я бы хотел узнать больше :) спасибо в любом случае! –

5
TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromIndex(mainTreeList.Items.CurrentPosition)); gives first item in the TreeView because CurrentPosition is always 0. 

Как насчет

TreeViewItem item = (TreeViewItem)(mainTreeList 
    .ItemContainerGenerator 
    .ContainerFromItem(mainTreeList.SelectedItem))); 

Это работает лучше для меня.

+0

Вы можете упростить последний пример. Просто используйте '... ItemGenerator.ContainerFromItem (mainTreeList.SelectedItem)'. Это также работает, если вы используете один или несколько «HierarchicalDataTemplate», тогда как первый пример не будет. Я позволил себе отредактировать ваш ответ. Надеюсь, с тобой все в порядке. –

0

Если вы должны найти пункт в детей детей вы, возможно, придется использовать рекурсию как этот

public bool Select(TreeViewItem item, object select) // recursive function to set item selection in treeview 
{ 
    if (item == null) 
     return false; 
    TreeViewItem child = item.ItemContainerGenerator.ContainerFromItem(select) as TreeViewItem; 
    if (child != null) 
    { 
     child.IsSelected = true; 
     return true; 
    } 
    foreach (object c in item.Items) 
    { 
     bool result = Select(item.ItemContainerGenerator.ContainerFromItem(c) as TreeViewItem, select); 
     if (result == true) 
      return true; 
    } 
    return false; 
} 
0

Вот решение. rtvEsa - это дерево. HierarchicalDataTemplate - шаблон treeview Тег выполняет реальное потребление текущего элемента. Этот элемент не выбран, это текущий элемент в дереве управления, который использует HierarchicalDataTemplate.

Items.CurrentItem является частью внутреннего древовидного объединения. Вы не можете получить много и разных данных. Например, Items.ParenItem тоже.

<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}"> 

    <TextBox Tag="{Binding ElementName=rtvEsa, Path=Items.CurrentItem }" /> 

2

Попробуйте что-то вроде этого:

public bool UpdateSelectedTreeViewItem(PropertyNode dateItem, ItemsControl itemsControl) 
    { 
     if (itemsControl == null || itemsControl.Items == null || itemsControl.Items.Count == 0) 
     { 
      return false; 
     } 
     foreach (var item in itemsControl.Items.Cast<PropertyNode>()) 
     { 
      var treeViewItem = itemsControl.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem; 
      if (treeViewItem == null) 
      { 
       continue; 
      } 
      if (item == dateItem) 
      { 
       treeViewItem.IsSelected = true; 
       return true; 
      } 
      if (treeViewItem.Items.Count > 0 && UpdateSelectedTreeViewItem(dateItem, treeViewItem)) 
      { 
       return true; 
      } 
     } 
     return false; 
    } 
Смежные вопросы