web-dev-qa-db-ja.com

コードからTreeViewアイテムを選択する方法

3レベルのツリービューがあります。コードから第3レベルのアイテムを選択するにはどうすればよいですか?多くのブログやスタックオーバーフローで言及されている方法を試しましたが、それは第1レベルでのみ機能するようです(第1レベルより下のアイテムではdbObjectはnullです)。

TreeViewItemを選択するために使用しているコードは次のとおりです。私は何かを逃していますか?

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 { }
}
26
Sergej Andrejev

別のオプションは、バインディングを使用することです。バインディングを使用して各TreeViewItemのテキストを取得するオブジェクトがある場合(たとえば)、IsSelectedプロパティもバインドするスタイルを作成できます。

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

これは、バインドされたオブジェクトがIsSelectedタイプのboolプロパティを持っていることを前提としています。次に、対応するオブジェクトのTreeViewItemIsSelectedに設定することにより、trueを選択できます。

同じアプローチをIsExpandedプロパティで使用して、TreeViewItemが展開または縮小されるタイミングを制御できます。

32
Andy

次のTreeView拡張機能を使用できます。これはより簡単な解決策だと思います。

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

詳しくは、こちらのブログ記事をご覧ください。 http://decompile.it/blog/2008/12/11/selecting-an-item-in-a-treeview-in-wpf/

7
kbisang

別の解決策を試した後、私は this サイトに行きました。 Zhou Yongは、TreeViewのすべてのノードをプログラムで展開する方法を示しています。彼の方法には2つの主要なアイデアがあります。

  • ContainerFromItemは、アイテムが要素の直接の子である場合にのみコンテナを返します。 TreeViewでは、最初のレベルの子コンテナーのみが返され、次のレベルからコンテナーを取得するには子TreeViewItemでContainerFromItemを呼び出す必要があることを意味します
  • ContainerFromItemを機能させるには、TreeViewItemビジュアルの子を作成する必要があります。これは、TreeViewItemが展開されている場合にのみ発生します。つまり、TreeViewItemを選択するには、必要なアイテムの前のすべてのアイテムを展開する必要があります。実際には、アイテムだけでなく、選択するアイテムへのパスを指定する必要があります。

これが私が終わったコードです

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);
        }
    }
}
4
Sergej Andrejev

私の場合(同じ問題がありました)が、DataオブジェクトのIsSelectedプロパティへのバインドを使用するのは不適切であり、ツリーアイテムへのパスを簡単に取得できなかったため、次のコードで完全に機能しました。

  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;
    }
2
Mladen Nikolov

私の答えでパーティーに非常に遅れますが、純粋なMVVMソリューションが必要な場合は、イベントトリガー(ユーザーが新しいアイテムを選択したときにバインディングを更新する)とデータトリガー(値が選択されたアイテムを更新するバインディング変更の)。

これを機能させるには、メインのViewModelにアイテム、現在選択されているアイテムのプロパティ、および現在選択されているアイテムが変更されたときに呼び出されるコマンドプロパティが必要です。

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); }
    }
}

TreeViewは、選択が変更されたときにSelectedNodeChangedCommandを呼び出すイベントトリガーと、コードでCurrentNodeの値がプログラムによって変更されたときにコントロール項目が選択されるように、TreeViewItemスタイルのDataTriggerを必要とします。

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

DataTriggerは、CurrentNodeの値が現在のリストアイテムのNodeと一致することを検出することで機能します。残念ながら、DataTriggersはそれらの値をバインドできないため、代わりにEqualityConverterでテストする必要があります。簡単な比較:

    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();
    }
}
0
Mark Feldman

ええ、直接の親TreeViewItemから呼び出した場合でも、ContainerFromItemメソッドは何も返しません。

少し再設計する必要があるかもしれません。すべてを明示的なTreeViewItemとして作成する場合、それへの参照を保持し、それにIsSelectedを設定できるはずです。

0
RandomEngy