2009-03-05 24 views
29

Tôi có một TreeView sử dụng HierarchicalDataTemplate để ràng buộc dữ liệu của nó.Cách lấy TreeViewItem từ mục HierarchicalDataTemplate?

Nó trông như thế này:

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

Bây giờ, từ code-behind của nói cửa sổ chính, tôi muốn để có được những lựa chọn TreeViewItem hiện hành. Tuy nhiên, nếu tôi sử dụng:

this.mainTreeList.SelectedItem; 

Mục được chọn là loại MyTreeViewItemViewModel. Nhưng tôi muốn có được 'cha mẹ' hoặc 'bị ràng buộc' TreeViewItem. Tôi không chuyển cho đối tượng TreeViewItemModel của mình (thậm chí sẽ không biết cách).

Tôi làm cách nào để thực hiện việc này?

+0

@romkyns: Cái gì? Câu hỏi này có * không có gì * để làm với việc theo dõi mục đã chọn. –

+0

@romkyns: Vâng, bạn không nên lấy TreeViewItem ngay từ đầu, vì vậy nó không thực sự quan trọng nếu nó khó làm hay không. –

+0

@ H.B. Đó cũng có thể là như vậy ... có thể [bạn có thể giúp tôi làm điều này mà không có TreeViewItem] (http://stackoverflow.com/questions/9761336/whats-the-wpf-way-to-mark-a-command-as- không có sẵn-chỉ-nếu-cha mẹ-of-a-tree)? –

Trả lời

20

Từ Bea Stollnitz 's blog entry về vấn đề này, hãy thử

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

Đủ tốt cho tôi, cảm ơn. (Mặc dù vậy, CurrentItem trả về một đối tượng và ContainerFromIndex lấy một số nguyên, vì vậy tôi phải sử dụng CurrentIndex hoặc một cái gì đó, nhưng cũng tốt) – Razzie

+0

Ah, tôi vừa sao chép từ blog của Bea và sau đó thay đổi một vài thứ. Tôi không nhận thấy lỗi đó :) Tuy nhiên, tôi sẽ cập nhật câu trả lời của mình. –

+7

'CurrentPosition' luôn là 0 đối với tôi, bất kể những gì được chọn. Mục nhập blog bạn liên kết đến là về danh sách chứ không phải cây - có lẽ đó là lý do. Không có 'HierarchicalDataTemplate' nào có liên quan đến bài đăng trên blog được liên kết. –

0

Bạn cần TreeViewItem vì bạn sẽ sửa đổi những gì đang được hiển thị? Nếu đúng như vậy, tôi khuyên bạn nên sử dụng Style để thay đổi cách mục được hiển thị thay vì sử dụng code-behind thay vì trực tiếp sửa đổi TreeViewItem. Nó hy vọng sẽ được sạch hơn.

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

KHÔNG LÀM VIỆC (cho tôi) như mainTreeList.Items.CurrentPosition trong một TreeView sử dụng một HierarchicalDataTemplate sẽ luôn là -1.

NEITHER DO bên dưới như mainTreeList.Items.CurrentItem trong chế độ xem tre bằng cách sử dụng một HierarchicalDataTemplate sẽ luôn là rỗng.

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

THAY tôi phải thiết lập một sự lựa chọn TreeViewItem cuối cùng trong trường hợp TreeViewItem.Selected chuyển mà bong bóng lên đến màn hình cây (những bản thân của TreeViewItem không tồn tại vào thời điểm thiết kế như chúng ta đang sử dụng một HierarchicalDataTemplate) .

Sự kiện này có thể được chụp trong XAML như vậy:

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

Sau đó TreeViewItem cuối cùng được lựa chọn có thể được thiết lập trong trường hợp như vậy:

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

Cách duy nhất cho CurrentItem bằng null (hoặc CurrentIndex bằng -1) là nếu không có mục nào trong cây. Hãy chắc chắn rằng có các mục trước khi cố gắng để có được các vật phẩm chứa. –

+0

CurrentItem không giống với SelectedItem. ContainerFromItem trả về null nếu nó không thể tìm thấy container, và null có thể được cast vào một TreeViewItem tốt mà không cần sử dụng như là cast. Bạn thậm chí có chạy mã không? –

+2

Vấn đề tôi nghĩ là bạn đang sử dụng mainTreeList.Items sẽ chỉ làm việc cho các mục ở cấp độ đầu tiên của cây tre - mỗi nút xem cây (TreeViewItem) có bộ sưu tập Mặt hàng riêng của nó - không giống như ví dụ của Bea mà bạn trích dẫn đang sử dụng ListBox và chỉ có một cấp mục. Sau đó, bạn cần lấy tham chiếu TreeViewItem được chọn hiện tại mà cách duy nhất tôi tìm thấy để làm như vậy khi sử dụng một HierarchicalDataTemplate đang nắm bắt sự kiện TreeViewItem.Selected như trong câu trả lời của tôi. – markmnl

6

Tôi chạy vào vấn đề này giống nhau. Tôi cần đến TreeViewItem để có thể chọn nó. Sau đó tôi nhận ra rằng tôi chỉ có thể thêm một thuộc tính IsSelected vào ViewModel của tôi, mà sau đó tôi ràng buộc với TreeViewItems IsSelectedProperty. này có thể đạt được với 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> 

Bây giờ nếu tôi muốn có một mục trong TreeView chọn, tôi chỉ cần gọi IsSelected trên lớp ViewModel của tôi trực tiếp.

Hy vọng nó sẽ giúp ai đó.

+2

Theo tôi, đây là cách tốt nhất để sử dụng treeview. Các mục _need_ là dạng xem để bạn có thể làm việc với thuộc tính đó. Nếu không, bạn sẽ phải làm tất cả các loại shenanigans để có được những gì nên được hành vi đơn giản để làm việc một cách chính xác. –

+1

Tôi đồng ý, tuy nhiên câu hỏi là làm thế nào để có được _TreeViewItem_ không phải là đối tượng mà nó kết thúc tốt đẹp. – markmnl

+0

Vâng, đây chắc chắn là cách bạn phải làm. Nhưng có một ViewModel cho _evvverything_ là một PITA khá, đặc biệt là đối với các UI cơ bản nhỏ. –

4

Lấy cảm hứng từ câu trả lời của Fëanor, tôi đã cố gắng làm cho TreeViewItem dễ dàng truy cập được đối với mọi mục dữ liệu mà TreeViewItem đã được tạo.

Ý tưởng là để thêm một lĩnh vực -typed TreeViewItem với mô hình xem, cũng tiếp xúc thông qua một giao diện, và có TreeView tự động cư nó bất cứ khi nào container TreeViewItem được tạo ra.

Điều này được thực hiện bằng cách phân lớp TreeView và đính kèm sự kiện vào ItemContainerGenerator, ghi lại TreeViewItem bất cứ khi nào được tạo. Gotchas bao gồm một thực tế là TreeViewItem s được tạo ra một cách lười biếng, do đó, có thể thực sự không phải là có sẵn tại một số thời điểm nhất định.

Kể từ khi đăng câu trả lời này, tôi đã phát triển thêm và sử dụng nó trong một thời gian dài trong một dự án. Không có vấn đề cho đến nay, khác với thực tế là điều này vi phạm MVVM (nhưng cũng giúp bạn tiết kiệm một tấn boilerplate cho các trường hợp đơn giản). Nguồn here.

Cách sử dụng

Chọn phụ huynh của mục đã chọn và sụp đổ nó, đảm bảo rằng đó là trong giao diện:

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

khai báo:

<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

Kiểm tra con đường này đã được rất intresting, và hình thành về cách thức hoạt động wpf (aka black magic), nhưng câu trả lời với tiền thưởng đã dẫn tôi làm thế nào để làm điều đó một cách chính xác. Đây không phải là cách tốt để làm điều đó (với sự tôn trọng) vì các mục được tạo lại và bạn tiếp tục thêm các trình xử lý, ngoài ra bạn sẽ không bao giờ loại bỏ các trình xử lý đó, vì không có trạng thái máy phát "bị xóa". Chúng tôi đã định tuyến các sự kiện và chúng tôi phải học và sử dụng chúng. –

+0

@ L.Trabacchin Tôi chắc chắn sẽ đồng ý rằng cách tiếp cận của tôi là hacky. TBH, đối với các UI phức tạp, đặt cược tốt nhất gần như chắc chắn là từ bỏ ý tưởng truy cập TreeViewItems hoàn toàn; đó không phải là cách WPF được cho là được sử dụng. Chúng ta phải truy cập vào các cá thể ViewModel của chúng ta. Tôi không nhớ tại sao câu trả lời bountied đã không cắt nó cho tôi, nhưng nó trông ít hacky hơn tôi. –

+0

Chắc chắn, một cách tốt để làm điều này là sử dụng chế độ xem, nhưng nó phụ thuộc vào những gì chúng tôi đang cố gắng đạt được, cho tôi sự kiện định tuyến hoạt động tốt hơn vì tôi đã cố gắng mở rộng treeview để đạt được số lượt xem nhiều lựa chọn, nhưng tôi muốn ít mã hơn, bởi vì điều đó có thể dẫn đến việc bảo trì và lỗi, và muốn nó hoạt động độc lập trên mô hình nào được sử dụng, tôi chủ yếu làm, tôi có một số lỗ hổng ngay bây giờ, phơi bày các mục được chọn để truy cập từ các điều khiển khác. .. tôi muốn tôi biết nhiều hơn :) cảm ơn anyway! –

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

Làm thế nào về

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

này hoạt động tốt hơn cho tôi.

+0

Bạn thực sự có thể đơn giản hóa ví dụ cuối cùng. Chỉ cần sử dụng '... ItemGenerator.ContainerFromItem (mainTreeList.SelectedItem)'. Điều này cũng hoạt động nếu bạn đang sử dụng một hoặc nhiều 'HierarchicalDataTemplate', trong khi ví dụ đầu tiên thì không. Tôi cho phép bản thân mình chỉnh sửa câu trả lời của bạn. Tôi hy vọng điều đó ổn với bạn. –

0

Nếu bạn phải tìm mục ở trẻ em trẻ em bạn có thể phải sử dụng đệ quy như thế này

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

Đây là giải pháp. rtvEsa là treeview. HierarchicalDataTemplate là mẫu treeview Thẻ thực hiện tiêu thụ thực của mục hiện tại. Đây không phải là mục được chọn nó là mục hiện tại trong điều khiển cây sử dụng HierarchicalDataTemplate.

Items.CurrentItem là một phần của colion cây nội bộ. Bạn không thể nhận được nhiều dữ liệu khác nhau. Ví dụ Items.ParenItem quá.

<HierarchicalDataTemplate ItemsSource="{Binding ChildItems}"> 

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

2

Hãy thử một cái gì đó như thế này:

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; 
    } 
Các vấn đề liên quan