c# - 使用WPF選擇虛擬化TreeView中的節點




wpf treeview (3)

有沒有辦法手動選擇虛擬化TreeView中的節點,然後將其放入視圖中?

我用我的TreeView使用的數據模型是基於VM-MV模型實現的。 每個TreeViewItem的IsSelected屬性綁定到ViewModel中的相應屬性。 我也為TreeView的ItemSelected事件創建了一個監聽器,我為所選的TreeViewItem調用BringIntoView()。

這種方法的問題似乎是ItemSelected事件不會被引發,直到創建實際的TreeViewItem。 因此,在啟用虛擬化的情況下,節點選擇在TreeView足夠滾動之前不會執行任何操作,然後在最終引發事件時,它會“神奇地”跳轉到所選節點。

我真的很喜歡使用虛擬化,因為我的樹中有數千個節點,而且在啟用虛擬化時已經看到了相當可觀的性能改進。


這是從MSDN採取的一個例子問題 public void ScrollToItem(int index)

    {

        Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Background,

            (System.Windows.Threading.DispatcherOperationCallback)delegate(object arg)

            {

                int N = fileList.Items.Count;

                if (N == 0)

                    return null;

                if (index < 0)

                {

                    fileList.ScrollIntoView(fileList.Items[0]); // scroll to first

                }

                else

                {

                    if (index < N)

                    {

                        fileList.ScrollIntoView(fileList.Items[index]); // scroll to item

                    }

                    else

                    {

                        fileList.ScrollIntoView(fileList.Items[N - 1]); // scroll to last

                    }

                }

                return null;

            }, null);

    }

我用一個附加的屬性來解決這個問題。

public class TreeViewItemBehaviour
{
    #region IsBroughtIntoViewWhenSelected

    public static bool GetIsBroughtIntoViewWhenSelected(TreeViewItem treeViewItem)
    {
        return (bool)treeViewItem.GetValue(IsBroughtIntoViewWhenSelectedProperty);
    }

    public static void SetIsBroughtIntoViewWhenSelected(
      TreeViewItem treeViewItem, bool value)
    {
        treeViewItem.SetValue(IsBroughtIntoViewWhenSelectedProperty, value);
    }

    public static readonly DependencyProperty IsBroughtIntoViewWhenSelectedProperty =
        DependencyProperty.RegisterAttached(
        "IsBroughtIntoViewWhenSelected",
        typeof(bool),
        typeof(TreeViewItemBehaviour),
        new UIPropertyMetadata(false, OnIsBroughtIntoViewWhenSelectedChanged));

    static void OnIsBroughtIntoViewWhenSelectedChanged(
      DependencyObject depObj, DependencyPropertyChangedEventArgs e)
    {
        TreeViewItem item = depObj as TreeViewItem;
        if (item == null)
            return;

        if (e.NewValue is bool == false)
            return;

        if ((bool)e.NewValue)
        {
            item.Loaded += item_Loaded;
        }
        else
        {
            item.Loaded -= item_Loaded;
        }
    }

    static void item_Loaded(object sender, RoutedEventArgs e)
    {
        TreeViewItem item = e.OriginalSource as TreeViewItem;
        if (item != null)
            item.BringIntoView();
    }

    #endregion // IsBroughtIntoViewWhenSelected

}

而在我的TreeViewItem的XAML風格,我只是將屬性設置為true

<Setter Property="Behaviours:TreeViewItemBehaviour.IsBroughtIntoViewWhenSelected" Value="True" />

HTH


我通過為TreeViewTreeViewItemVirtualizingStackPanel創建自定義控件來解決這個問題。 解決方案的一部分來自http://code.msdn.microsoft.com/Changing-selection-in-a-6a6242c8

每個TreeItem(綁定項)都需要知道它的父項(由ITreeItem強制執行)。

public interface ITreeItem {
    ITreeItem Parent { get; }
    IList<ITreeItem> Children { get; }
    bool IsSelected { get; set; }
    bool IsExpanded { get; set; }
}

當在任何TreeItem上設置IsSelected通知視圖模型並引發一個事件。 視圖中相應的事件偵聽器在TreeView上調用BringItemIntoView

TreeView找到所選項目路徑上的所有TreeViewItems並將其引入到視圖中。

在這裡代碼的其餘部分:

public class SelectableVirtualizingTreeView : TreeView {
    public SelectableVirtualizingTreeView() {
        VirtualizingStackPanel.SetIsVirtualizing(this, true);
        VirtualizingStackPanel.SetVirtualizationMode(this, VirtualizationMode.Recycling);
        var panelfactory = new FrameworkElementFactory(typeof(SelectableVirtualizingStackPanel));
        panelfactory.SetValue(Panel.IsItemsHostProperty, true);
        var template = new ItemsPanelTemplate { VisualTree = panelfactory };
        ItemsPanel = template;
    }

    public void BringItemIntoView(ITreeItem treeItemViewModel) {
        if (treeItemViewModel == null) {
            return;
        }
        var stack = new Stack<ITreeItem>();
        stack.Push(treeItemViewModel);
        while (treeItemViewModel.Parent != null) {
            stack.Push(treeItemViewModel.Parent);
            treeItemViewModel = treeItemViewModel.Parent;
        }
        ItemsControl containerControl = this;
        while (stack.Count > 0) {
            var viewModel = stack.Pop();
            var treeViewItem = containerControl.ItemContainerGenerator.ContainerFromItem(viewModel);
            var virtualizingPanel = FindVisualChild<SelectableVirtualizingStackPanel>(containerControl);
            if (virtualizingPanel != null) {
                var index = viewModel.Parent != null ? viewModel.Parent.Children.IndexOf(viewModel) : Items.IndexOf(treeViewItem);
                virtualizingPanel.BringIntoView(index);
                Focus();
            }
            containerControl = (ItemsControl)treeViewItem;
        }
    }

    protected override DependencyObject GetContainerForItemOverride() {
        return new SelectableVirtualizingTreeViewItem();
    }

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item) {
        base.PrepareContainerForItemOverride(element, item);
        ((TreeViewItem)element).IsExpanded = true;
    }

    private static T FindVisualChild<T>(Visual visual) where T : Visual {
        for (var i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++) {
            var child = (Visual)VisualTreeHelper.GetChild(visual, i);
            if (child == null) {
                continue;
            }
            var correctlyTyped = child as T;
            if (correctlyTyped != null) {
                return correctlyTyped;
            }
            var descendent = FindVisualChild<T>(child);
            if (descendent != null) {
                return descendent;
            }
        }
        return null;
    }
}

public class SelectableVirtualizingTreeViewItem : TreeViewItem {
    public SelectableVirtualizingTreeViewItem() {
        var panelfactory = new FrameworkElementFactory(typeof(SelectableVirtualizingStackPanel));
        panelfactory.SetValue(Panel.IsItemsHostProperty, true);
        var template = new ItemsPanelTemplate { VisualTree = panelfactory };
        ItemsPanel = template;
        SetBinding(IsSelectedProperty, new Binding("IsSelected"));
        SetBinding(IsExpandedProperty, new Binding("IsExpanded"));
    }

    protected override DependencyObject GetContainerForItemOverride() {
        return new SelectableVirtualizingTreeViewItem();
    }

    protected override void PrepareContainerForItemOverride(DependencyObject element, object item) {
        base.PrepareContainerForItemOverride(element, item);
        ((TreeViewItem)element).IsExpanded = true;
    }
}

public class SelectableVirtualizingStackPanel : VirtualizingStackPanel {
    public void BringIntoView(int index) {
        if (index < 0) {
            return;
        }
        BringIndexIntoView(index);
    }
}

public abstract class TreeItemBase : ITreeItem {
    protected TreeItemBase() {
        Children = new ObservableCollection<ITreeItem>();
    }

    public ITreeItem Parent { get; protected set; }

    public IList<ITreeItem> Children { get; protected set; }

    public abstract bool IsSelected { get; set; }

    public abstract bool IsExpanded { get; set; }

    public event EventHandler DescendantSelected;

    protected void RaiseDescendantSelected(TreeItemViewModel newItem) {
        if (Parent != null) {
            ((TreeItemViewModel)Parent).RaiseDescendantSelected(newItem);
        } else {
            var handler = DescendantSelected;
            if (handler != null) {
                handler.Invoke(newItem, EventArgs.Empty);
            }
        }
    }
}

public class MainViewModel : INotifyPropertyChanged {
    private TreeItemViewModel _selectedItem;

    public MainViewModel() {
        TreeItemViewModels = new List<TreeItemViewModel> { new TreeItemViewModel { Name = "Item" } };
        for (var i = 0; i < 30; i++) {
            TreeItemViewModels[0].AddChildInitial();
        }
        TreeItemViewModels[0].IsSelected = true;
        TreeItemViewModels[0].DescendantSelected += OnDescendantSelected;
    }

    public event EventHandler DescendantSelected;

    public event PropertyChangedEventHandler PropertyChanged;

    public List<TreeItemViewModel> TreeItemViewModels { get; private set; }

    public TreeItemViewModel SelectedItem {
        get {
            return _selectedItem;
        }
        set {
            if (_selectedItem == value) {
                return;
            }
            _selectedItem = value;
            var handler = PropertyChanged;
            if (handler != null) {
                handler.Invoke(this, new PropertyChangedEventArgs("SelectedItem"));
            }
        }
    }

    private void OnDescendantSelected(object sender, EventArgs eventArgs) {
        var handler = DescendantSelected;
        if (handler != null) {
            handler.Invoke(sender, eventArgs);
        }
    }
}

public partial class MainWindow {
    public MainWindow() {
        InitializeComponent();
        var mainViewModel = (MainViewModel)DataContext;
        mainViewModel.DescendantSelected += OnMainViewModelDescendantSelected;
    }

    private void OnAddButtonClick(object sender, RoutedEventArgs e) {
        var mainViewModel = (MainViewModel)DataContext;
        var treeItemViewModel = mainViewModel.SelectedItem;
        if (treeItemViewModel != null) {
            treeItemViewModel.AddChild();
        }
    }

    private void OnMainViewModelDescendantSelected(object sender, EventArgs eventArgs) {
        _treeView.BringItemIntoView(sender as TreeItemViewModel);
    }

    private void OnTreeViewSelectedItemChanged(object sender, RoutedPropertyChangedEventArgs<object> e) {
        if (e.OldValue == e.NewValue) {
            return;
        }
        var treeView = (TreeView)sender;
        var treeItemviewModel = treeView.SelectedItem as TreeItemViewModel;
        var mainViewModel = (MainViewModel)DataContext;
        mainViewModel.SelectedItem = treeItemviewModel;
    }
}

而在XAML中:

<controls:SelectableVirtualizingTreeView x:Name="_treeView" ItemsSource="{Binding TreeItemViewModels}" Margin="8" 
        SelectedItemChanged="OnTreeViewSelectedItemChanged">
    <controls:SelectableVirtualizingTreeView.ItemTemplate>
        <HierarchicalDataTemplate ... />
    </controls:SelectableVirtualizingTreeView.ItemTemplate>
</controls:SelectableVirtualizingTreeView>




treeview