2009-06-02 2 views
22

Tôi có một chế độ xem ba cấp độ. Làm cách nào để chọn bất kỳ mục nào ở cấp độ thứ ba từ mã? Tôi đã thử một phương pháp được đề cập trong nhiều blog và trên stackoverflow nhưng nó dường như chỉ làm việc cho cấp độ đầu tiên (dbObject là null cho các mục ở dưới mức đầu tiên).Cách chọn mục TreeView từ mã

Đây là mã tôi đang sử dụng để chọn TreeViewItem. Tôi có nhớ điều gì không?

public static void SetSelectedItem(this TreeView control, object item) 
{ 
    try 
    { 
     var dObject = control.ItemContainerGenerator.ContainerFromItem(item); 

     //uncomment the following line if UI updates are unnecessary 
     ((TreeViewItem)dObject).IsSelected = true; 

     MethodInfo selectMethod = typeof(TreeViewItem).GetMethod("Select", 
      BindingFlags.NonPublic | BindingFlags.Instance); 

     selectMethod.Invoke(dObject, new object[] { true }); 
    } 
    catch { } 
} 
+12

WPF's TreeView hy sinh * mỗi bit cuối cùng của khả năng sử dụng để đạt được các tính năng mà hầu hết mọi người không dường như cần hầu hết thời gian ... –

Trả lời

31

Một lựa chọn khác là sử dụng ràng buộc. Nếu bạn có một đối tượng mà bạn đang sử dụng ràng buộc với để có được văn bản của mỗi TreeViewItem (ví dụ), bạn có thể tạo ra một phong cách mà còn liên kết với các IsSelected tài sản:

<TreeView> 
    <TreeView.Resources> 
     <Style TargetType="TreeViewItem"> 
      <Setter Property="IsSelected" 
        Value="{Binding Path=IsSelected, Mode=TwoWay}" /> 
     </Style> 
    </TreeView.Resources> 
</TreeView> 

này giả định rằng các đối tượng ràng buộc có IsSelected thuộc tính loại bool. Sau đó, bạn có thể chọn TreeViewItem bằng cách đặt IsSelected thành true cho đối tượng tương ứng của nó.

Phương pháp tương tự có thể được sử dụng với thuộc tính IsExpanded để kiểm soát thời điểm TreeViewItem được mở rộng hoặc thu gọn.

+0

Vâng, tôi đã biết điều này. Nhưng có vẻ như nó giới thiệu khớp nối mã. Dù sao thì cũng tốt khi bạn có câu trả lời ở đây. Những người sẽ đến trang này có thể thích cách của bạn hơn tôi –

+0

@Andy: Làm thế nào tôi có thể làm điều này trong Silverlight? Tôi thử mã này có lỗi 'Không thể đặt thuộc tính chỉ đọc' '.'. –

+1

@Navid Tôi không chắc chắn nếu bạn có thể. Tôi đã không làm được nhiều với Silverlight, nhưng tôi không nghĩ rằng TreeViewItem.IsSelected là một DependencyProperty trong Silverlight. Bạn không thể sử dụng ràng buộc cho các thuộc tính không phải là DependencyProperty. – Andy

0

Vâng, phương thức ContainerFromItem không trả lại bất cứ điều gì, ngay cả khi bạn gọi nó từ cha mẹ trực tiếp TreeViewItem.

Bạn có thể cần thực hiện một chút thiết kế lại. Nếu bạn tạo tất cả mọi thứ như là một TreeViewItem rõ ràng, bạn sẽ có thể giữ một tham chiếu đến nó và thiết lập IsSelected trên nó.

4

Sau khi thử các sollutions khác nhau, tôi đã đến trang this. Zhou Yong cho thấy cách lập trình mở rộng tất cả các nút của TreeView. Có hai ý tưởng chính trong phương pháp của mình:

  • ContainerFromItem sẽ chỉ trả lại vùng chứa nếu mục là con trực tiếp của phần tử. Trong TreeView có nghĩa là chỉ có container con cấp đầu tiên sẽ được trả về và bạn phải gọi ContainerFromItem trên TreeViewItem con để lấy container từ cấp độ tiếp theo
  • Đối với ContainerFromItem để làm việc TreeViewItem, hình ảnh con nên được tạo và điều này xảy ra chỉ khi TreeViewItem được mở rộng. Điều đó có nghĩa rằng để chọn TreeViewItem, tất cả các mục trước mục bắt buộc phải được mở rộng. Trong thực tế, điều đó có nghĩa là chúng tôi sẽ phải cung cấp đường dẫn đến mục mà chúng tôi muốn chọn thay vì chỉ mục đó.

Đây là mã tôi đã kết thúc với

public static void SelectItem(this ItemsControl parentContainer, List<object> path) 
{ 
    var head = path.First(); 
    var tail = path.GetRange(1, path.Count - 1); 
    var itemContainer = parentContainer.ItemContainerGenerator.ContainerFromItem(head) as TreeViewItem; 

    if (itemContainer != null && itemContainer.Items.Count == 0) 
    { 
     itemContainer.IsSelected = true; 

     var selectMethod = typeof(TreeViewItem).GetMethod("Select", BindingFlags.NonPublic | BindingFlags.Instance); 
     selectMethod.Invoke(itemContainer, new object[] { true }); 
    } 
    else if (itemContainer != null) 
    { 
     itemContainer.IsExpanded = true; 

     if (itemContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) 
     { 
      itemContainer.ItemContainerGenerator.StatusChanged += delegate 
      { 
       SelectItem(itemContainer, tail); 
      }; 
     } 
     else 
     { 
      SelectItem(itemContainer, tail); 
     } 
    } 
} 
+0

Dường như tôi phải * có * mục tôi muốn chọn. Tuy nhiên, thông thường đối tượng đó không có sẵn; bạn chỉ có một số ID của đối tượng đó, phải không? Tôi không chắc chắn làm thế nào để gọi 'ContainerFromItem' mà không có thể hiện mục thực tế. –

0

Trong trường hợp của tôi (tôi đã gặp vấn đề tương tự) nhưng không phù hợp để sử dụng liên kết với thuộc tính IsSelected của đối tượng Dữ liệu và tôi cũng không thể lấy đường dẫn đến mục cây, vì vậy mã sau đã thực hiện công việc một cách hoàn hảo:

private void SelectTreeViewItem(object item) 
    { 
     try 
     { 
      var tvi = GetContainerFromItem(this.MainRegion, item); 

      tvi.Focus(); 
      tvi.IsSelected = true; 

      var selectMethod = 
       typeof(TreeViewItem).GetMethod("Select", 
       System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); 

      selectMethod.Invoke(tvi, new object[] { true }); 
     } 
     catch { } 
    } 

    private TreeViewItem GetContainerFromItem(ItemsControl parent, object item) 
    { 
     var found = parent.ItemContainerGenerator.ContainerFromItem(item); 
     if (found == null) 
     { 
      for (int i = 0; i < parent.Items.Count; i++) 
      { 
       var childContainer = parent.ItemContainerGenerator.ContainerFromIndex(i) as ItemsControl; 
       TreeViewItem childFound = null; 
       if (childContainer != null && childContainer.ItemContainerGenerator.Status != GeneratorStatus.ContainersGenerated) 
       { 
        childContainer.ItemContainerGenerator.StatusChanged += (o, e) => 
         { 
          childFound = GetContainerFromItem(childContainer, item); 
         }; 
       } 
       else 
       { 
        childFound = GetContainerFromItem(childContainer, item);        
       } 
       if (childFound != null) 
        return childFound;     
      } 
     } 
     return found as TreeViewItem; 
    } 
3

Bạn có thể sử dụng phần mở rộng TreeView sau, mà tôi tìm thấy là một giải pháp đơn giản hơn:

public static class TreeViewExtension 
{ 
    public static bool SetSelectedItem(this TreeView treeView, object item) 
    { 
     return SetSelected(treeView, item); 
    } 

    private static bool SetSelected(ItemsControl parent, object child) 
    { 
     if (parent == null || child == null) 
      return false; 

     TreeViewItem childNode = parent.ItemContainerGenerator 
     .ContainerFromItem(child) as TreeViewItem; 

     if (childNode != null) 
     { 
      childNode.Focus(); 
      return childNode.IsSelected = true; 
     } 

     if (parent.Items.Count > 0) 
     { 
      foreach (object childItem in parent.Items) 
      { 
      ItemsControl childControl = parent 
       .ItemContainerGenerator 
       .ContainerFromItem(childItem) 
       as ItemsControl; 

      if (SetSelected(childControl, child)) 
       return true; 
      } 
     } 

     return false; 
    } 
} 

Mọi chi tiết, đọc bài viết trên blog này; http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/

+0

Hoạt động và không yêu cầu thay đổi cấu trúc dữ liệu nhưng không hiệu quả chút nào. –

0

Rất muộn đối với câu trả lời của tôi nhưng đối với những người muốn có giải pháp MVVM thuần túy, có thể thực hiện với Trình kích hoạt sự kiện (để cập nhật liên kết khi người dùng chọn mục mới) và Trình kích hoạt dữ liệu (để cập nhật) mục đã chọn khi giá trị của các ràng buộc thay đổi).

Đối với điều này để làm việc ViewModel chính cần các mặt hàng, một tài sản cho mục đang được chọn và sở hữu một lệnh đó sẽ được gọi khi thay đổi mục đang được chọn:

public class MainViewModel : ViewModelBase 
{ 
    // the currently selected node, can be changed programmatically 
    private Node _CurrentNode; 
    public Node CurrentNode 
    { 
     get { return this._CurrentNode; } 
     set { this._CurrentNode = value; RaisePropertyChanged(() => this.CurrentNode); } 
    } 

    // called when the user selects a new node in the tree view 
    public ICommand SelectedNodeChangedCommand { get { return new RelayCommand<Node>(OnSelectedNodeChanged); } } 
    private void OnSelectedNodeChanged(Node node) 
    { 
     this.CurrentNode = node; 
    } 

    // list of items to display in the tree view 
    private ObservableCollection<Node> _Items; 
    public ObservableCollection<Node> Items 
    { 
     get { return this._Items; } 
     set { this._Items = value; RaisePropertyChanged(() => this.Items); } 
    } 
} 

Các TreeView cần một kích hoạt sự kiện để gọi SelectedNodeChangedCommand khi những thay đổi lựa chọn, và một DataTrigger theo phong cách TreeViewItem để các hạng mục kiểm soát được chọn khi giá trị của CurrentNode được thay đổi programatically trong các mã:

<TreeView x:Name="treeView" ItemsSource="{Binding Items}" 
      xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" 
      xmlns:cmd ="http://www.galasoft.ch/mvvmlight"> 
     <TreeView.Resources> 

      <conv:EqualityConverter x:Key="EqualityConverter" /> 

      <Style TargetType="TreeViewItem"> 
       <Setter Property="IsExpanded" Value="True" /> 
       <Setter Property="IsSelected" Value="False" /> 
       <Style.Triggers> 
        <!-- DataTrigger updates TreeViewItem selection when vm code changes CurrentNode --> 
        <DataTrigger Value="True"> 
         <DataTrigger.Binding> 
          <MultiBinding Converter="{StaticResource EqualityConverter}"> 
           <Binding RelativeSource="{RelativeSource FindAncestor, AncestorType={x:Type TreeView}}" Path="DataContext.CurrentNode" /> 
           <Binding /> 
          </MultiBinding> 
         </DataTrigger.Binding> 
         <Setter Property="IsSelected" Value="True" /> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 


      <!-- *** HierarchicalDataTemplates go here *** --> 

     </TreeView.Resources> 

     <!-- EventTrigger invokes SelectedNodeChangedCommand when selection is changed by user interaction --> 
     <i:Interaction.Triggers> 
      <i:EventTrigger EventName="SelectedItemChanged"> 
       <cmd:EventToCommand Command="{Binding SelectedNodeChangedCommand}" CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=TreeView}, Path=SelectedItem}" /> 
      </i:EventTrigger> 
     </i:Interaction.Triggers> 

    </TreeView> 

các DataTrig ger hoạt động bằng cách phát hiện khi giá trị của CurrentNode khớp với nút cho mục danh sách hiện tại. Thật không may DataTriggers không thể ràng buộc giá trị của họ, do đó, nó phải kiểm tra với một EqualityConverter thay vì mà chỉ cần làm một so sánh đơn giản:

public class EqualityConverter : IMultiValueConverter 
{ 
    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) 
    { 
     return values[0] == values[1]; 
    } 

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