programing

WPF 트리 보기에서 항목을 프로그래밍 방식으로 선택하는 방법은 무엇입니까?

oldcodes 2023. 4. 29. 09:53
반응형

WPF 트리 보기에서 항목을 프로그래밍 방식으로 선택하는 방법은 무엇입니까?

WPF에서 방식으로 합니까?TreeViewItemsControl모델이 그것을 막는 것 같습니다.

여전히 이 문제에 대한 올바른 해결책을 찾고 있는 사람들을 위해 아래에 있는 것이 있습니다.저는 DaWanderer의 코드 프로젝트 기사 "WPF TreeView Selection" http://www.codeproject.com/KB/WPF/TreeView_SelectionWPF.aspx 에 대한 댓글에서 이것을 발견했습니다.그것은 2008년 11월 25일 켄래에 의해 게시되었습니다.이것은 저에게 아주 효과적이었습니다.켄래 감사합니다!

그의 게시물은 다음과 같습니다.

트리를 걷는 대신 자신의 데이터 개체에 IsSelected 속성이 있어야 합니다(IsExpanded 속성도 권장).트리뷰에 대한 스타일 정의TreeView에서 속성을 바인딩하는 TreeView의 ItemContainerStyle 속성을 사용하는 트리의 항목데이터 개체의 항목입니다.이와 같은 것:

<Style x:Key="LibraryTreeViewItemStyle"
               TargetType="{x:Type TreeViewItem}">
            <Setter Property="IsExpanded"
                        Value="{Binding IsExpanded, Mode=TwoWay}" />
            <Setter Property="IsSelected"
                        Value="{Binding IsSelected, Mode=TwoWay}" />
            <Setter Property="FontWeight"
                        Value="Normal" />
            <Style.Triggers>
                  <Trigger Property="IsSelected"
                              Value="True">
                        <Setter Property="FontWeight"
                                    Value="Bold" />
                  </Trigger>
            </Style.Triggers>
      </Style>

<TreeView ItemsSource="{Binding Path=YourCollection}"
               ItemContainerStyle="{StaticResource LibraryTreeViewItemStyle}"
               ItemTemplate={StaticResource YourHierarchicalDataTemplate}/>

어떤 이상한 이유로 정말 고통스럽습니다. 컨테이너를 가져오려면 컨테이너 프롬 아이템을 사용한 다음 선택 메소드를 호출해야 합니다.

//  selectedItemObject is not a TreeViewItem, but an item from the collection that 
//  populated the TreeView.

var tvi = treeView.ItemContainerGenerator.ContainerFromItem(selectedItemObject) 
          as TreeViewItem;

if (tvi != null)
{
    tvi.IsSelected = true;
}

여기서 어떻게 하는지에 대한 블로그 엔트리가 있었지만, 링크는 이제 비활성화되었습니다.

당신은 그것을 얻을 필요가 있습니다.TreeViewItem에 그음에다를 합니다.IsSelectedtrue.

이 코드로 성공했습니다.

public static TreeViewItem FindTviFromObjectRecursive(ItemsControl ic, object o) {
  //Search for the object model in first level children (recursively)
  TreeViewItem tvi = ic.ItemContainerGenerator.ContainerFromItem(o) as TreeViewItem;
  if (tvi != null) return tvi;
  //Loop through user object models
  foreach (object i in ic.Items) {
    //Get the TreeViewItem associated with the iterated object model
    TreeViewItem tvi2 = ic.ItemContainerGenerator.ContainerFromItem(i) as TreeViewItem;
    tvi = FindTviFromObjectRecursive(tvi2, o);
    if (tvi != null) return tvi;
  }
  return null;
}

용도:

var tvi = FindTviFromObjectRecursive(TheTreeView, TheModel);
if (tvi != null) tvi.IsSelected = true;

보기만큼 간단하지 않습니다. Steven이 제공하는 링크에는 2008년에 게시된 솔루션이 있습니다. 이 솔루션은 여전히 작동할 수 있지만 가상화된 TreeView는 처리하지 않습니다.게다가 그 기사의 댓글에는 많은 다른 문제들이 언급되어 있습니다.범죄는 없지만, 저 또한 같은 문제에 갇혀 완벽한 해결책을 찾을 수 없습니다.다음은 제게 큰 도움이 된 기사/게시물의 링크입니다.

트리 보기에서 항목을 확장하려면 어떻게 해야 합니까?파트 III: http://bea.stollnitz.com/blog/ ?p=59

TreeView에서 프로그래밍 방식으로 항목 선택: http://blog.quantumbitdesigns.com/2008/07/22/programmatically-selecting-an-item-in-a-treeview/ #respond

트리뷰, 트리뷰항목 및 선택됨: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/7e368b93-f509-4cd6-88e7-561e8d3246ae/

확장 방법을 작성했습니다.

using System.Windows.Controls;

namespace Extensions
{
    public static class TreeViewEx
    {
        /// <summary>
        /// Select specified item in a TreeView
        /// </summary>
        public static void SelectItem(this TreeView treeView, object item)
        {
            var tvItem = treeView.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;
            if (tvItem != null)
            {
                tvItem.IsSelected = true;
            }
        }
    }
}

다음과 같이 사용할 수 있습니다.

if (_items.Count > 0)
    _treeView.SelectItem(_items[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;
}

는 메소드를 .VisualTreeExt.GetDescendants<T>지정한 유형과 일치하는 요소의 열거형 컬렉션을 반환합니다.

public static class VisualTreeExt
{
  public static IEnumerable<T> GetDescendants<T>(DependencyObject parent) where T : DependencyObject
  {
    var count = VisualTreeHelper.GetChildrenCount(parent);
    for (var i = 0; i < count; ++i)
    {
       // Obtain the child
       var child = VisualTreeHelper.GetChild(parent, i);
       if (child is T)
         yield return (T)child;

       // Return all the descendant children
       foreach (var subItem in GetDescendants<T>(child))
         yield return subItem;
    }
  }
}

당신이 요청할 때VisualTreeHelperExt.GetDescendants<TreeViewItem>(MyAmazingTreeView)당신은 모든 것을 얻을 것입니다.TreeViewItem 코드할 수 .다음 코드를 사용하여 특정 값을 선택할 수 있습니다.

var treeViewItem = VisualTreeExt.GetDescendants<TreeViewItem>(MyTreeView).FirstOrDefault(tvi => tvi.DataContext == newValue);
if (treeViewItem != null)
  treeViewItem.IsSelected = true;

가상화된 TreeView를 사용하는 경우에는 실제 시각적 요소의 존재 여부에 따라 달라지기 때문에 이 솔루션은 다소 더러운 솔루션(가장 효율적이지 않을 수도 있음)이며 작동하지 않습니다.하지만 내 상황에 맞는...

뒤에 있는 코드를 통해 다음과 같이 할 수 있습니다.

if (TreeView1.Items.Count > 0)
        (TreeView1.Items[0] as TreeViewItem).IsSelected = true;

제안된 답변이 작동하지 않습니다.@판두산토의 대답은 아마도 효과가 있겠지만, 더 간단하게 만들 수 있습니다.이것이 제가 생각할 수 있는 가장 간단한 답입니다.

    private static void DeselectTreeViewItem(IEnumerable<TreeViewItem> treeViewItems)
    {
        foreach (var treeViewItem in treeViewItems)
        {
            if (treeViewItem.IsSelected)
            {
                treeViewItem.IsSelected = false;
                return;
            }

            DeselectTreeViewItem(treeViewItem.Items.Cast<TreeViewItem>());
        }
    }

용도:

    private void ClearSelectedItem()
    {
        if (AssetTreeView.SelectedItem != null)
        {
            DeselectTreeViewItem(AssetTreeView.Items.Cast<TreeViewItem>());
        }
    }

이걸로 해보세요.

    /// <summary>
    /// Selects the tree view item.
    /// </summary>
    /// <param name="Collection">The collection.</param>
    /// <param name="Value">The value.</param>
    /// <returns></returns>
    private TreeViewItem SelectTreeViewItem(ItemCollection Collection, String Value)
    {
        if (Collection == null) return null;
        foreach(TreeViewItem Item in Collection)
        {
            /// Find in current
            if (Item.Header.Equals(Value))
            {
                Item.IsSelected = true;
                return Item;
            }
            /// Find in Childs
            if (Item.Items != null)
            {
                TreeViewItem childItem = this.SelectTreeViewItem(Item.Items, Value);
                if (childItem != null)
                {
                    Item.IsExpanded = true;
                    return childItem;
                }
            }
        }
        return null;
    }

참조: http://amastaneh.blogspot.com/2011/06/wpf-selectedvalue-for-treeview.html

제가 제안한 해결책에 동의할 거라고 생각했어요. 만약 이게 누구에게 도움이 될 수 있다면요.이 작업을 수행하는 가장 좋은 방법은 kuninl의 답변에 따라 'Is Selected'와 같은 바인딩된 속성을 사용하는 것이지만, 저의 경우 MVVM을 따르지 않는 레거시 애플리케이션이었기 때문에 다음과 같은 결과를 얻었습니다.

private void ChangeSessionSelection()
{
    foreach (SessionContainer item in this.treeActiveSessions.Items)
    {
        var treeviewItem = this.treeActiveSessions.ItemContainerGenerator.ContainerFromItem(item) as TreeViewItem;

        if (item.Session == this.selectedSession.Session)
        {
            treeviewItem.IsSelected = true;
            treeviewItem.IsExpanded = true;
        }
        else
        {
            treeviewItem.IsSelected = false;
            treeviewItem.IsExpanded = false;
        }
    }            
}

이렇게 하면 UI에서 선택한 데이터 항목을 나타내는 트리 보기 항목을 선택하고 확장할 수 있습니다.이 작업의 목적은 동일한 창에서 항목 컨트롤에서 사용자 선택이 변경될 때 트리 보기에서 선택이 변경되도록 하는 것이었습니다.

이게 제 해결책입니다.다른 사람들은 다양한 방법으로 저를 위해 실패했습니다.트리를 위에서 아래로 이동하고 각 레벨에서 트리 항목을 찾아 레이아웃을 확장하고 업데이트해야 합니다.

이 함수는 스택의 첫 번째 노드가 최상위 노드이고 스택의 각 후속 노드가 이전 상위 노드의 자식 노드인 노드 스택을 사용합니다.두 번째 인수는 TreeView입니다.

각 항목이 발견되면 해당 항목이 확장되고 호출자가 선택할 수 있는 최종 항목이 반환됩니다.

    TreeViewItem FindTreeViewItem( Stack<object> nodeStack, TreeView treeView )
    {
        ItemsControl itemsControl = treeView;

        while (nodeStack.Count > 0) {
            object node = nodeStack.Pop();
            bool found = false;

            foreach (object item in itemsControl.Items) {
                if (item == node) {
                    found = true;

                    if (itemsControl.ItemContainerGenerator.ContainerFromItem( item ) is TreeViewItem treeViewItem) {
                        if (nodeStack.Count == 0) {
                            return treeViewItem;
                        }

                        itemsControl = treeViewItem;
                        treeViewItem.IsExpanded = true;
                        treeViewItem.UpdateLayout();
                        break;
                    }
                }
            }

            if (!found) {
                return null;
            }
        }

        return null;
    }

호출 방법의 예:

    // Build nodeStack here from your data

    TreeViewItem treeViewItem = FindTreeViewItem( nodeStack, treeView );

    if (treeViewItem != null) {
        treeViewItem.IsSelected = true;
        treeViewItem.BringIntoView();
    }

MVVM과 lazy loaded 아이템을 지원하는 Helper 클래스를 작성했습니다.

public class TreeViewHelper<TModel>
{
    public TreeViewHelper(TreeView treeView, Func<TModel, TModel> getParent, Func<TModel, IList<TModel>> getSubItems)
    {
        TreeView = treeView;
        GetParent = getParent;
        GetSubItems = getSubItems;
    }

    public TreeView TreeView { get; }
    public Func<TModel, TModel> GetParent { get; }
    public Func<TModel, IList<TModel>> GetSubItems { get; }

    public void SelectItemWhileLoaded(TModel node, IList<TModel> rootNodes)
    {
        if (TreeView.IsLoaded)
        {
            SelectItem(node, rootNodes);
        }
        else
        {
            TreeView.Loaded += TreeView_Loaded;
            void TreeView_Loaded(object sender, System.Windows.RoutedEventArgs e)
            {
                TreeView.Loaded -= TreeView_Loaded;
                SelectItem(node, rootNodes);
            }
        }
    }


    public void SelectItem(TModel node, IList<TModel> rootNodes)
    {
        Stack<TModel> nodes = new Stack<TModel>();
        //push into stack
        while (!rootNodes.Contains(node))
        {
            nodes.Push(node);
            node = GetParent(node);
        }
        TreeViewItem treeViewItem = TreeView.ItemContainerGenerator
            .ContainerFromItem(node) as TreeViewItem;
        if (nodes.Count == 0)
        {
            //Top level
            treeViewItem.IsSelected = true;
            treeViewItem.BringIntoView();
            return;
        }
        Expanded(true);
        void Expanded(bool top)
        {
            if (!top)
            {
                treeViewItem = treeViewItem.ItemContainerGenerator
                    .ContainerFromItem(node) as TreeViewItem;
                if (nodes.Count == 0)
                {
                    treeViewItem.IsSelected = true;
                    treeViewItem.BringIntoView();
                    return;
                }
            }
            node = nodes.Pop();
            treeViewItem.IsExpanded = true;
            if (treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                Expanded(true);
            }
            else
            {
                //Lazy
                treeViewItem.ItemContainerGenerator.StatusChanged += ItemContainerGenerator_StatusChanged;
            }
        }
        void ItemContainerGenerator_StatusChanged(object sender, EventArgs e)
        {
            if (treeViewItem.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
            {
                treeViewItem.ItemContainerGenerator.StatusChanged -= ItemContainerGenerator_StatusChanged;
                Expanded(false);
            }
        }
    }
}

네.. 그 질문이 있은 후로 여러 해가 지난 것은 알지만..여전히 이 문제에 대한 빠른 해결책은 없습니다.그래서:

다음은 OP가 요청한 것을 수행할 것입니다.

제가 기본적으로 한 일은 이 페이지의 모든 답변을 읽고 모든 관련 링크를 따라 이 짜증나는 문제에 대한 유일한 해결책을 만드는 것이었습니다.

이점:

  • 또한 TreeView 가상화도 지원합니다.
  • 행동 기술을 사용해서 XAML은 매우 쉽습니다.
  • 선택한 TreeView 항목에 바인딩할 수 있도록 종속성 속성을 추가합니다.

이 부분은 복사해야 하는 유일한 코드이며, 다른 부분은 예제를 완성하는 데 도움이 됩니다.

public static class TreeViewSelectedItemExBehavior
{
    private static List<TreeView> isRegisteredToSelectionChanged = new List<TreeView>();

    public static readonly DependencyProperty SelectedItemExProperty =
        DependencyProperty.RegisterAttached("SelectedItemEx",
            typeof(object),
            typeof(TreeViewSelectedItemExBehavior),
            new FrameworkPropertyMetadata(new object(), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnSelectedItemExChanged, null));

    #region SelectedItemEx

    public static object GetSelectedItemEx(TreeView target)
    {
        return target.GetValue(SelectedItemExProperty);
    }

    public static void SetSelectedItemEx(TreeView target, object value)
    {
        target.SetValue(SelectedItemExProperty, value);
        var treeViewItemToSelect = GetTreeViewItem(target, value);
        if (treeViewItemToSelect == null)
        {
            if (target.SelectedItem == null)
                return;
            var treeViewItemToUnSelect = GetTreeViewItem(target, target.SelectedItem);
            treeViewItemToUnSelect.IsSelected = false;
        }
        else
            treeViewItemToSelect.IsSelected = true;
    }

    public static void OnSelectedItemExChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        var treeView = depObj as TreeView;
        if (treeView == null)
            return;
        if (!isRegisteredToSelectionChanged.Contains(treeView))
        {
            treeView.SelectedItemChanged += TreeView_SelectedItemChanged;
            isRegisteredToSelectionChanged.Add(treeView);
        }
    }
    
    #endregion

    private static void TreeView_SelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
    {
        var treeView = (TreeView)sender;
        SetSelectedItemEx(treeView, e.NewValue);
    }

    #region Helper Structures & Methods

    public class MyVirtualizingStackPanel : VirtualizingStackPanel
    {
        /// <summary>
        /// Publically expose BringIndexIntoView.
        /// </summary>
        public void BringIntoView(int index)
        {
            BringIndexIntoView(index);
        }
    }

    /// <summary>Recursively search for an item in this subtree.</summary>
    /// <param name="container">The parent ItemsControl. This can be a TreeView or a TreeViewItem.</param>
    /// <param name="item">The item to search for.</param>
    /// <returns>The TreeViewItem that contains the specified item.</returns>
    private static TreeViewItem GetTreeViewItem(ItemsControl container, object item)
    {
        if (container != null)
        {
            if (container.DataContext == item)
            {
                return container as TreeViewItem;
            }

            // Expand the current container
            if (container is TreeViewItem && !((TreeViewItem)container).IsExpanded)
            {
                container.SetValue(TreeViewItem.IsExpandedProperty, true);
            }

            // Try to generate the ItemsPresenter and the ItemsPanel.
            // by calling ApplyTemplate.  Note that in the 
            // virtualizing case even if the item is marked 
            // expanded we still need to do this step in order to 
            // regenerate the visuals because they may have been virtualized away.

            container.ApplyTemplate();
            ItemsPresenter itemsPresenter =
                (ItemsPresenter)container.Template.FindName("ItemsHost", container);
            if (itemsPresenter != null)
            {
                itemsPresenter.ApplyTemplate();
            }
            else
            {
                // The Tree template has not named the ItemsPresenter, 
                // so walk the descendents and find the child.
                itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                if (itemsPresenter == null)
                {
                    container.UpdateLayout();

                    itemsPresenter = FindVisualChild<ItemsPresenter>(container);
                }
            }

            Panel itemsHostPanel = (Panel)VisualTreeHelper.GetChild(itemsPresenter, 0);


            // Ensure that the generator for this panel has been created.
            UIElementCollection children = itemsHostPanel.Children;

            MyVirtualizingStackPanel virtualizingPanel =
                itemsHostPanel as MyVirtualizingStackPanel;

            for (int i = 0, count = container.Items.Count; i < count; i++)
            {
                TreeViewItem subContainer;
                if (virtualizingPanel != null)
                {
                    // Bring the item into view so 
                    // that the container will be generated.
                    virtualizingPanel.BringIntoView(i);

                    subContainer =
                        (TreeViewItem)container.ItemContainerGenerator.
                        ContainerFromIndex(i);
                }
                else
                {
                    subContainer =
                        (TreeViewItem)container.ItemContainerGenerator.
                        ContainerFromIndex(i);

                    // Bring the item into view to maintain the 
                    // same behavior as with a virtualizing panel.
                    subContainer.BringIntoView();
                }

                if (subContainer != null)
                {
                    // Search the next level for the object.
                    TreeViewItem resultContainer = GetTreeViewItem(subContainer, item);
                    if (resultContainer != null)
                    {
                        return resultContainer;
                    }
                    else
                    {
                        // The object is not under this TreeViewItem
                        // so collapse it.
                        subContainer.IsExpanded = false;
                    }
                }
            }
        }

        return null;
    }

    /// <summary>Search for an element of a certain type in the visual tree.</summary>
    /// <typeparam name="T">The type of element to find.</typeparam>
    /// <param name="visual">The parent element.</param>
    /// <returns></returns>
    private static T FindVisualChild<T>(Visual visual) where T : Visual
    {
        for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
        {
            Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
            if (child != null)
            {
                T correctlyTyped = child as T;
                if (correctlyTyped != null)
                {
                    return correctlyTyped;
                }

                T descendent = FindVisualChild<T>(child);
                if (descendent != null)
                {
                    return descendent;
                }
            }
        }
        return null;
    }
    
    #endregion
}

다음은 XAML에서 TreeView 라인이 어떻게 보이는지 보여주는 예입니다.

<TreeView x:Name="trvwSs"
    Grid.Column="2" Grid.Row="1" Margin="4" ItemsSource="{Binding ItemsTreeViewSs}"
    behaviors:TreeViewSelectedItemExBehavior.SelectedItemEx="{Binding SelectedItemTreeViewSs}" />

걱정해야 할 것은 선택됨에 바인딩하려는 뷰 모델 속성을 확인하는 것입니다.ItemEx가 null이 아닙니다.하지만 그것은 특별한 경우가 아닙니다.사람들이 혼란스러워 할까봐 방금 언급했습니다.

public class VmMainContainer : INotifyPropertyChanged
{
    private object selectedItemTreeViewSs = new object();
    private ObservableCollection<object> selectedItemsTreeViewSs = new ObservableCollection<object>();
    private ObservableCollection<VmItem> itemsTreeViewSs = new ObservableCollection<VmItem>();

    public object SelectedItemTreeViewSs
    {
        get
        {
            return selectedItemTreeViewSs;
        }
        set
        {
            selectedItemTreeViewSs = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItemTreeViewSs)));
        }
    }

    public ObservableCollection<object> SelectedItemsTreeViewSs
    {
        get
        {
            return selectedItemsTreeViewSs;
        }
        set
        {
            selectedItemsTreeViewSs = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(SelectedItemsTreeViewSs)));
        }
    }

    public ObservableCollection<VmItem> ItemsTreeViewSs
    {
        get { return itemsTreeViewSs; }
        set
        {
            itemsTreeViewSs = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ItemsTreeViewSs)));
        }
    }
}

그리고 마지막으로..프로그래밍 방식으로 선택하기 예: MainWindow.xaml의 핸들러에서 단추를 만들었습니다.

private void Button_Click(object sender, RoutedEventArgs e)
{
    TreeViewSelectedItemExBehavior.SetSelectedItemEx(trvwSs, trvwSs.Items[3]);
    //TreeViewSelectedItemExBehavior.SetSelectedItemEx(trvwSs, null);
}

이것이 누군가에게 도움이 되기를 바랍니다 :)

이것이 가장 간단한 해결책이라고 생각합니다.

private void MouseDownEventProcessing(TreeNodeMouseClickEventArgs e)
{
    tvEmployeeDirectory.SelectedNode = e.Node;
}

언급URL : https://stackoverflow.com/questions/413890/how-to-programmatically-select-an-item-in-a-wpf-treeview

반응형