自定义TreeView,多列带有标头,支持多选绑定,带有虚拟化技术

 

根据需求需要制作一颗树,多列显示,带有标头,支持多选绑定,另外带有一些选中、鼠标移动的效果。实现效果如下:

 

样式资源代码如下,

这里遇到一个问题,如果修改了TextBlock的Foreground,那么在Trigger里面再修改Foreground,是没有效果的。

这个控件有两个附加依赖项属性,一个是解决多选问题,一个是解决滚动scrollView列表头也一起滚动的问题

还有一个解决展开速度的问题,采用了虚拟化技术,主要代码下面我已经标红

<Window x:Class="TreeGrid.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:TreeGrid"
        xmlns:my="clr-namespace:TreeGrid"
        Title="MainWindow" Height="400" Width="525">
    <Window.Resources>
        <local:TreeViewLineConverter x:Key="LineConverter"/>
        <local:LevelToMarginConverter x:Key="LevelToIndentConverter"/>
        <Style x:Key="ExpandCollapseToggleStyle" TargetType="ToggleButton">
            <Setter Property="Focusable" Value="False"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="ToggleButton">
                        <Grid x:Name="Root" Background="Transparent" >
                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup x:Name="CommonStates">
                                    <VisualState x:Name="Normal" />
                                    <VisualState x:Name="MouseOver">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="OuterBorder"
                                                                     Storyboard.TargetProperty="Opacity"
                                                                     To="1" Duration="0:0:0.1" />
                                        </Storyboard>
                                    </VisualState>
                                    <VisualState x:Name="Disabled">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="Root"
                                                                     Storyboard.TargetProperty="Opacity"
                                                                     To="0.5"
                                                                     Duration="0" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                                <VisualStateGroup x:Name="CheckStates">
                                    <VisualState x:Name="Unchecked" />
                                    <VisualState x:Name="Checked">
                                        <Storyboard>
                                            <DoubleAnimation Storyboard.TargetName="UncheckedVisual"
                                                                     Storyboard.TargetProperty="Opacity"
                                                                     To="0" Duration="0" />
                                            <DoubleAnimation Storyboard.TargetName="CheckedVisual"
                                                                     Storyboard.TargetProperty="Opacity"
                                                                     To="1" Duration="0" />
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>
                            <Border x:Name="OuterBorder"
                                                BorderBrush="{StaticResource MyBrushNormalBorder}"
                                                CornerRadius="3"
                                                BorderThickness="1" Opacity="0.5"
                                                    Background="{StaticResource MyBrushNormalBevelBackground}"/>
                            <Grid Margin="3" >
                                <Path x:Name="UncheckedVisual" UseLayoutRounding="False"
                                              HorizontalAlignment="Center"  Stretch="Uniform"
                                              Data="M0,4 L4,4 L4,0 L6,0 L6,4 L10,4 L10,6 L6,6 L6,10 L4,10 L4,6 L0,6 z"
                                              VerticalAlignment="Center"
                                              Opacity="1" Fill="#FFB5BD24">
                                </Path>
                                <Path x:Name="CheckedVisual" UseLayoutRounding="False"
                                              HorizontalAlignment="Center" Stretch="Uniform"
                                              Data="M0,4 L10,4 L10,6 L0,6 z"
                                              VerticalAlignment="Center"
                                              Opacity="0" Fill="#FFB5BD24">
                                </Path>
                            </Grid>
                        </Grid>

                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <DataTemplate x:Key="CellTemplate_Name">
            <DockPanel >
                <ToggleButton x:Name="Expander" 
                      Style="{StaticResource ExpandCollapseToggleStyle}" 
                      Margin="{Binding Level,Converter={StaticResource LevelToIndentConverter}}"
                      IsChecked="{Binding Path=IsExpanded,RelativeSource={RelativeSource AncestorType= {x:Type TreeViewItem}}}"
                      ClickMode="Press"/>
                <TextBlock Text="{Binding Name}"/>
            </DockPanel>
            <DataTemplate.Triggers>
                <DataTrigger Binding="{Binding Path=HasItems,
                               RelativeSource={RelativeSource 
                               AncestorType={x:Type TreeViewItem}}}" 
                     Value="False">
                    <Setter TargetName="Expander"
                  Property="Visibility"
                  Value="Hidden"/>
                </DataTrigger>
            </DataTemplate.Triggers>
        </DataTemplate>

        <GridViewColumnCollection x:Key="gvcc">
            <GridViewColumn Header="Name"  
                      CellTemplate="{StaticResource CellTemplate_Name}" Width="Auto"/>
            <GridViewColumn Header="JobTitle" 
                      DisplayMemberBinding="{Binding JobTitle}" Width="60"/>
            <GridViewColumn Header="Age" 
                      DisplayMemberBinding="{Binding Age}" Width="60" />
            <GridViewColumn Header="Sex" 
                      DisplayMemberBinding="{Binding Sex}" Width="60"/>
        </GridViewColumnCollection>

        <Style TargetType="{x:Type TreeViewItem}">
            <Setter Property="MinWidth" Value="20"></Setter>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TreeViewItem}">
                        <StackPanel>
                            <Border Name="Bd" 
                      Background="{TemplateBinding Background}"
                      BorderBrush="{TemplateBinding BorderBrush}"
                      BorderThickness="{TemplateBinding BorderThickness}"
                      Padding="0,2,0,2">
                                <GridViewRowPresenter x:Name="PART_Header" 
                                      Content="{TemplateBinding Header}" 
                                      Columns="{StaticResource gvcc}" />
                            </Border>
                            <ItemsPresenter x:Name="ItemsHost" />
                        </StackPanel>


                        <ControlTemplate.Triggers>
                            <Trigger Property="IsExpanded"
                       Value="false">
                                <Setter TargetName="ItemsHost"
                        Property="Visibility"
                        Value="Collapsed"/>
                            </Trigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="HasHeader"
                             Value="false"/>
                                    <Condition Property="Width"
                             Value="Auto"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="PART_Header"
                        Property="MinWidth"
                        Value="75"/>
                            </MultiTrigger>
                            <MultiTrigger>
                                <MultiTrigger.Conditions>
                                    <Condition Property="HasHeader"
                             Value="false"/>
                                    <Condition Property="Height"
                             Value="Auto"/>
                                </MultiTrigger.Conditions>
                                <Setter TargetName="PART_Header"
                        Property="MinHeight"
                        Value="19"/>
                            </MultiTrigger>

                            <Trigger Property="IsEnabled"
                       Value="false">
                                <Setter Property="Foreground"
                        Value="{DynamicResource 
                          {x:Static SystemColors.GrayTextBrushKey}}"/>
                            </Trigger>
                            <Trigger Property="IsMouseOver" Value="True">
                                <Setter Property="Foreground" Value="Red"></Setter>

 

                            </Trigger>

 

                            <Trigger Property="my:TreeViewExtensions.IsSelected" Value="true">
                                <Setter Property="Background">
                                    <Setter.Value>
                                        <LinearGradientBrush EndPoint="0.5,1" MappingMode="RelativeToBoundingBox" StartPoint="0.5,0">
                                            <GradientStop Color="#FFC7DFFC" Offset="1"/>
                                            <GradientStop Color="#FF3832B8" Offset="1"/>
                                        </LinearGradientBrush>
                                    </Setter.Value>
                                </Setter>
                            </Trigger>
                        </ControlTemplate.Triggers>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>

        <Style TargetType="{x:Type TreeView}">
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="{x:Type TreeView}">
                        <Border BorderBrush="{TemplateBinding BorderBrush}"
                    BorderThickness="{TemplateBinding BorderThickness}">
                            <DockPanel>
                              <ScrollViewer DockPanel.Dock="Top" HorizontalScrollBarVisibility="Hidden"  VerticalScrollBarVisibility ="Disabled" 
                                      Focusable="False"  Core:ScrollSynchronizer.HorizontalScrollGroup="H1">
                                    <GridViewHeaderRowPresenter Columns="{StaticResource gvcc}"  DockPanel.Dock="Top" />
                                </ScrollViewer>
                                <ScrollViewer HorizontalScrollBarVisibility="Auto" CanContentScroll="{TemplateBinding ScrollViewer.CanContentScroll}"
                                              VerticalScrollBarVisibility="Auto"  Focusable="False"
                                              Core:ScrollSynchronizer.HorizontalScrollGroup="H1">
                                    <VirtualizingStackPanel  x:Name="ItemsHost" IsItemsHost="True" SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" Height="Auto"/>
                                </ScrollViewer>
                            </DockPanel>
                        </Border>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
        <Style x:Key="TextBlockStyle" TargetType="TextBlock">
            <Style.Triggers>
                <Trigger Property="IsMouseOver" Value="True">
                    <Setter Property="Foreground" Value="Red"></Setter>
                </Trigger>
            </Style.Triggers>
        </Style>

    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0" VerticalAlignment="Center" Orientation="Vertical">
            <StackPanel Orientation="Horizontal">
                <Button Height="30" Width="60" Content="test" Click="Button_Click"></Button>
                <Button x:Name="updata" Height="30" Width="60" Content="updata" Click="updata_Click"></Button>
            </StackPanel>
            <ScrollViewer>
                <TextBox x:Name="txt" Height="50"></TextBox>
            </ScrollViewer>
        </StackPanel>
        <TreeView Height="300" Grid.Row="1" Name="_list"  BorderThickness="0" VerticalAlignment="Stretch" Background="Transparent" ItemsSource="{Binding Children}"  ScrollViewer.CanContentScroll="True" VirtualizingStackPanel.IsVirtualizing="True"
                  my:TreeViewExtensions.EnableMultiSelect="true" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto"
                  my:TreeViewExtensions.SelectedItems="{Binding SelectedTreeNodes, Mode=TwoWay, NotifyOnTargetUpdated=True}" Margin="0,0,0,50">
            <TreeView.ItemTemplate>
                <HierarchicalDataTemplate ItemsSource="{Binding Children}">
                    <Border CornerRadius="0" Margin="1"  x:Name="back" MinWidth="70"
                                Background="Transparent" DataContext="{Binding}" >
                        <StackPanel Orientation="Horizontal" Margin="2">
                            <TextBlock Text="{Binding Text}" Margin="2 0"/>
                        </StackPanel>
                    </Border>
                </HierarchicalDataTemplate>
            </TreeView.ItemTemplate>
        </TreeView>
    </Grid>

 

</Window>

两个依赖项属性类如下

多选类

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.ComponentModel;
using System.Collections;
using System.Windows.Controls.Primitives;
using System.Windows.Media;

namespace TreeGrid.Resource
{
    public class TreeViewExtensions : TreeView
    {
        /// <summary>
        /// Gets the value of the dependency property "EnableMultiSelect".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <returns></returns>
        public static bool GetEnableMultiSelect(DependencyObject obj)
        {
            return (bool)obj.GetValue(EnableMultiSelectProperty);
        }

        /// <summary>
        /// Sets the value of the dependency property "EnableMultiSelect".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <param name="value"></param>
        public static void SetEnableMultiSelect(DependencyObject obj, bool value)
        {
            obj.SetValue(EnableMultiSelectProperty, value);
        }

        // Using a DependencyProperty as the backing store for EnableMultiSelect.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty EnableMultiSelectProperty =
            DependencyProperty.RegisterAttached("EnableMultiSelect", typeof(bool), typeof(TreeViewExtensions), new FrameworkPropertyMetadata(false)
            {
                PropertyChangedCallback = EnableMultiSelectChanged,
                BindsTwoWayByDefault = true
            });

        /// <summary>
        /// Gets the value of the dependency property "SelectedItems".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <returns></returns>
        public static IList GetSelectedItems(DependencyObject obj)
        {
            return (IList)obj.GetValue(SelectedItemsProperty);
        }

        /// <summary>
        /// Sets the value of the dependency property "SelectedItems".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <param name="value"></param>
        public static void SetSelectedItems(DependencyObject obj, IList value)
        {
            obj.SetValue(SelectedItemsProperty, value);
        }

        // Using a DependencyProperty as the backing store for SelectedItems.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty SelectedItemsProperty =
            DependencyProperty.RegisterAttached("SelectedItems", typeof(IList), typeof(TreeViewExtensions), new PropertyMetadata(null));

        /// <summary>
        /// Gets the value of the dependency property "AnchorItem".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <returns></returns>
        static TreeViewItem GetAnchorItem(DependencyObject obj)
        {
            return (TreeViewItem)obj.GetValue(AnchorItemProperty);
        }

        /// <summary>
        /// Sets the value of the dependency property "AnchorItem".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <param name="value"></param>
        static void SetAnchorItem(DependencyObject obj, TreeViewItem value)
        {
            obj.SetValue(AnchorItemProperty, value);
        }

        // Using a DependencyProperty as the backing store for AnchorItem.  This enables animation, styling, binding, etc...
        static readonly DependencyProperty AnchorItemProperty =
            DependencyProperty.RegisterAttached("AnchorItem", typeof(TreeViewItem), typeof(TreeViewExtensions), new PropertyMetadata(null));

        /// <summary>
        /// Gets the value of the dependency property "IsSelected".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <returns></returns>
        public static bool GetIsSelected(DependencyObject obj)
        {
            return (bool)obj.GetValue(IsSelectedProperty);
        }

        /// <summary>
        /// Sets the value of the dependency property "IsSelected".
        /// </summary>
        /// <param name="obj">Dependency Object</param>
        /// <param name="value"></param>
        public static void SetIsSelected(DependencyObject obj, bool value)
        {
            if (value)
            {
                GradientStopCollection gradientStopCollection = new GradientStopCollection();
                gradientStopCollection.Add(new GradientStop()
                {
                    Color = (Color)ColorConverter.ConvertFromString("#FF303030"),
                    Offset = 1
                });
//                gradientStopCollection.Add(new GradientStop()
//                {
//                    Color = (Color)ColorConverter.ConvertFromString("#FF3832B8"),
//                    Offset = 1
//                });
                LinearGradientBrush brush = new LinearGradientBrush(gradientStopCollection, new Point(0.5, 0), new Point(0.5, 1));
                (obj as TreeViewItem).Background = brush;
            }
            else
            {
                (obj as TreeViewItem).Background = Brushes.Transparent;
            }
            obj.SetValue(IsSelectedProperty, value);
        }

        // Using a DependencyProperty as the backing store for IsSelected.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.RegisterAttached("IsSelected", typeof(bool), typeof(TreeViewExtensions), new PropertyMetadata(false)
            {
                PropertyChangedCallback = RealSelectedChanged
            });

        /// <summary>
        /// "EnableMultiSelect" changed event.
        /// </summary>
        /// <param name="s">Dependency Object</param>
        /// <param name="args">Event parameter</param>
        static void EnableMultiSelectChanged(DependencyObject s, DependencyPropertyChangedEventArgs args)
        {
            TreeView tree = (TreeView)s;
            var wasEnable = (bool)args.OldValue;
            var isEnabled = (bool)args.NewValue;
            if (wasEnable)
            {
                tree.RemoveHandler(TreeViewItem.MouseDownEvent, new MouseButtonEventHandler(ItemClicked));
                tree.RemoveHandler(TreeView.KeyDownEvent, new KeyEventHandler(KeyDown));
            }
            if (isEnabled)
            {
                tree.AddHandler(TreeViewItem.MouseDownEvent, new MouseButtonEventHandler(ItemClicked), true);
                tree.AddHandler(TreeView.KeyDownEvent, new KeyEventHandler(KeyDown));
            }
        }

        /// <summary>
        /// Gets TreeView which contains the TreeViewItem.
        /// </summary>
        /// <param name="item">item</param>
        /// <returns>TreeView</returns>
        static TreeView GetTree(TreeViewItem item)
        {
            Func<DependencyObject, DependencyObject> getParent = (o) => VisualTreeHelper.GetParent(o);
            FrameworkElement currentItem = item;
            while (!(getParent(currentItem) is TreeView))
            {
                currentItem = (FrameworkElement)getParent(currentItem);
            }
            return (TreeView)getParent(currentItem);
        }

        /// <summary>
        /// TreeViewItem seleted changed event.
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="args">event parameter</param>
        static void RealSelectedChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            TreeViewItem item = (TreeViewItem)sender;
            var selectedItems = GetSelectedItems(GetTree(item));
            if (selectedItems != null)
            {
                var isSelected = GetIsSelected(item);
                if (isSelected)
                {
                    try
                    {
                        selectedItems.Add(item.Header);
                    }
                    catch (ArgumentException)
                    {
                    }
                }
                else
                {
                    selectedItems.Remove(item.Header);
                }
            }

        }
        /// <summary>
        /// Key down event.
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">event parameter</param>
        static void KeyDown(object sender, KeyEventArgs e)
        {
            TreeView tree = (TreeView)sender;
            if (e.Key == Key.A && e.KeyboardDevice.Modifiers == ModifierKeys.Control)
            {
                foreach (var item in GetExpandedTreeViewItems(tree))
                {
                    SetIsSelected(item, true);
                }
                e.Handled = true;
            }
        }

        /// <summary>
        /// Item clicked event.
        /// </summary>
        /// <param name="sender">sender</param>
        /// <param name="e">event parameter</param>
        static void ItemClicked(object sender, MouseButtonEventArgs e)
        {
            TreeViewItem item = FindTreeViewItem(e.OriginalSource);
            if (item == null)
            {
                return;
            }
            TreeView tree = (TreeView)sender;

            var mouseButton = e.ChangedButton;
            if (mouseButton != MouseButton.Left)
            {
                if ((mouseButton == MouseButton.Right) && ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) == ModifierKeys.None))
                {
                    if (GetIsSelected(item))
                    {
                        UpdateAnchorAndActionItem(tree, item);
                        return;
                    }
                    MakeSingleSelection(tree, item);
                }
                return;
            }
            if ((Keyboard.Modifiers & (ModifierKeys.Shift | ModifierKeys.Control)) != (ModifierKeys.Shift | ModifierKeys.Control))
            {
                if ((Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control)
                {
                    MakeToggleSelection(tree, item);
                    return;
                }
                if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
                {
                    MakeAnchorSelection(tree, item, true);
                    return;
                }
                MakeSingleSelection(tree, item);
                return;
            }
        }

        /// <summary>
        /// Find TreeViewItem which contains the object.
        /// </summary>
        /// <param name="obj">obj</param>
        /// <returns></returns>
        private static TreeViewItem FindTreeViewItem(object obj)
        {
            DependencyObject dpObj = obj as DependencyObject;
            if (dpObj == null)
            {
                return null;
            }
            if (dpObj is TreeViewItem)
            {
                return (TreeViewItem)dpObj;
            }
            return FindTreeViewItem(VisualTreeHelper.GetParent(dpObj));
        }

        /// <summary>
        /// Gets all expanded TreeViewItems.
        /// </summary>
        /// <param name="tree">TreeView</param>
        /// <returns></returns>
        private static IEnumerable<TreeViewItem> GetExpandedTreeViewItems(ItemsControl tree)
        {
            for (int i = 0; i < tree.Items.Count; i++)
            {
                var item = (TreeViewItem)tree.ItemContainerGenerator.ContainerFromIndex(i);
                if (item == null)
                {
                    continue;
                }
                yield return item;
//                if (item.IsExpanded)
//                {
                    foreach (var subItem in GetExpandedTreeViewItems(item))
                    {
                        yield return subItem;
                    }
//                }
            }
        }

        /// <summary>
        /// Select by Shift key.
        /// </summary>
        /// <param name="tree"></param>
        /// <param name="actionItem"></param>
        /// <param name="clearCurrent"></param>
        private static void MakeAnchorSelection(TreeView tree, TreeViewItem actionItem, bool clearCurrent)
        {
            if (GetAnchorItem(tree) == null)
            {
                var selectedItems = GetSelectedTreeViewItems(tree);
                if (selectedItems.Count > 0)
                {
                    SetAnchorItem(tree, selectedItems[selectedItems.Count - 1]);
                }
                else
                {
                    SetAnchorItem(tree, GetExpandedTreeViewItems(tree).Skip(3).FirstOrDefault());
                }
                if (GetAnchorItem(tree) == null)
                {
                    return;
                }
            }

            var anchor = GetAnchorItem(tree);

            var items = GetExpandedTreeViewItems(tree);
            bool betweenBoundary = false;
            foreach (var item in items)
            {
                bool isBoundary = item == anchor || item == actionItem;
                if (isBoundary)
                {
                    betweenBoundary = !betweenBoundary;
                }
                if (betweenBoundary || isBoundary)
                {
                    SetIsSelected(item, true);
                }
                else
                {
                    if (clearCurrent)
                    {
                        SetIsSelected(item, false);
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        /// <summary>
        /// Gets all selected TreeViewItems.
        /// </summary>
        /// <param name="tree">TreeView</param>
        /// <returns></returns>
        private static List<TreeViewItem> GetSelectedTreeViewItems(TreeView tree)
        {
            return GetExpandedTreeViewItems(tree).Where(i => GetIsSelected(i)).ToList();
        }

        /// <summary>
        /// Select by left mouse button.
        /// </summary>
        /// <param name="tree"></param>
        /// <param name="item"></param>
        private static void MakeSingleSelection(TreeView tree, TreeViewItem item)
        {
            foreach (TreeViewItem selectedItem in GetExpandedTreeViewItems(tree))
            {
                if (selectedItem == null)
                {
                    continue;
                }
                if (selectedItem != item)
                {
                    SetIsSelected(selectedItem, false);
                }
                else
                {
                    SetIsSelected(selectedItem, true);
                }
            }
            UpdateAnchorAndActionItem(tree, item);
        }

        /// <summary>
        /// Select by Ctrl key.
        /// </summary>
        /// <param name="tree">TreeView</param>
        /// <param name="item">TreeViewItem</param>
        private static void MakeToggleSelection(TreeView tree, TreeViewItem item)
        {
            SetIsSelected(item, !GetIsSelected(item));
            UpdateAnchorAndActionItem(tree, item);
        }

        /// <summary>
        /// Update the Anchor TreeViewItem.
        /// </summary>
        /// <param name="tree">TreeView</param>
        /// <param name="item">TreeViewItem</param>
        private static void UpdateAnchorAndActionItem(TreeView tree, TreeViewItem item)
        {
            SetAnchorItem(tree, item);
        }
    }
}
滚动条联动类

 

using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace ScrollViewerSynchronization.Core
{
    public sealed class ScrollSynchronizer
    {
        #region Constant(s)

        private const string VerticalScrollGroupPropertyName = "VerticalScrollGroup";
        private const string HorizontalScrollGroupPropertyName = "HorizontalScrollGroup";
        private const string ScrollSyncTypePropertyName = "ScrollSyncType";

        #endregion

        #region Dependency Propert(y/ies)

        #region Declaration(s)

        public static readonly DependencyProperty HorizontalScrollGroupProperty =
            DependencyProperty.RegisterAttached(HorizontalScrollGroupPropertyName, typeof(string), typeof(ScrollSynchronizer), new PropertyMetadata(string.Empty, OnHorizontalScrollGroupChanged));
        public static readonly DependencyProperty VerticalScrollGroupProperty =
            DependencyProperty.RegisterAttached(VerticalScrollGroupPropertyName, typeof(string), typeof(ScrollSynchronizer), new PropertyMetadata(string.Empty, OnVerticalScrollGroupChanged));
        public static readonly DependencyProperty ScrollSyncTypeProperty =
            DependencyProperty.RegisterAttached(ScrollSyncTypePropertyName, typeof(ScrollSyncType), typeof(ScrollSynchronizer), new PropertyMetadata(ScrollSyncType.None, OnScrollSyncTypeChanged));

        #endregion

        #region Getter(s)/Setter(s)

        public static void SetVerticalScrollGroup(DependencyObject obj, string verticalScrollGroup)
        {
            obj.SetValue(VerticalScrollGroupProperty, verticalScrollGroup);
        }

        public static string GetVerticalScrollGroup(DependencyObject obj)
        {
            return (string)obj.GetValue(VerticalScrollGroupProperty);
        }

        public static void SetHorizontalScrollGroup(DependencyObject obj, string horizontalScrollGroup)
        {
            obj.SetValue(HorizontalScrollGroupProperty, horizontalScrollGroup);
        }

        public static string GetHorizontalScrollGroup(DependencyObject obj)
        {
            return (string)obj.GetValue(HorizontalScrollGroupProperty);
        }

        public static void SetScrollSyncType(DependencyObject obj, ScrollSyncType scrollSyncType)
        {
            obj.SetValue(ScrollSyncTypeProperty, scrollSyncType);
        }

        public static ScrollSyncType GetScrollSyncType(DependencyObject obj)
        {
            return (ScrollSyncType)obj.GetValue(ScrollSyncTypeProperty);
        }

        #endregion

        #region Event Handler(s)

        private static void OnVerticalScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scrollViewer = d as ScrollViewer;
            if (scrollViewer == null)
                return;

            var newVerticalGroupName = (e.NewValue == DependencyProperty.UnsetValue ? string.Empty : (string)e.NewValue);
            var oldVerticalGroupName = (e.NewValue == DependencyProperty.UnsetValue ? string.Empty : (string)e.OldValue);

            removeFromVerticalScrollGroup(oldVerticalGroupName, scrollViewer);
            addToVerticalScrollGroup(newVerticalGroupName, scrollViewer);

            var currentScrollSyncValue = readSyncTypeDPValue(d, ScrollSyncTypeProperty);
            if (currentScrollSyncValue == ScrollSyncType.None)
                d.SetValue(ScrollSyncTypeProperty, ScrollSyncType.Vertical);
            else if (currentScrollSyncValue == ScrollSyncType.Horizontal)
                d.SetValue(ScrollSyncTypeProperty, ScrollSyncType.Vertical);
        }

        private static void OnHorizontalScrollGroupChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scrollViewer = d as ScrollViewer;
            if (scrollViewer == null)
                return;

            var newHorizontalGroupName = (e.NewValue == DependencyProperty.UnsetValue ? string.Empty : (string)e.NewValue);
            var oldHorizontalGroupName = (e.NewValue == DependencyProperty.UnsetValue ? string.Empty : (string)e.OldValue);

            removeFromHorizontalScrollGroup(oldHorizontalGroupName, scrollViewer);
            addToHorizontalScrollGroup(newHorizontalGroupName, scrollViewer);

            var currentScrollSyncValue = readSyncTypeDPValue(d, ScrollSyncTypeProperty);
            if (currentScrollSyncValue == ScrollSyncType.None)
                d.SetValue(ScrollSyncTypeProperty, ScrollSyncType.Horizontal);
            else if (currentScrollSyncValue == ScrollSyncType.Vertical)
                d.SetValue(ScrollSyncTypeProperty, ScrollSyncType.Both);
        }

        private static void OnScrollSyncTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var scrollViewer = d as ScrollViewer;
            if (scrollViewer == null)
                return;

            var verticalGroupName = readStringDPValue(d, VerticalScrollGroupProperty);
            var horizontalGroupName = readStringDPValue(d, HorizontalScrollGroupProperty);

            var scrollSyncType = ScrollSyncType.None;
            try
            {
                scrollSyncType = (ScrollSyncType)e.NewValue;
            }
            catch { }

            switch (scrollSyncType)
            {
                case ScrollSyncType.None:
                    if (!registeredScrollViewers.ContainsKey(scrollViewer))
                        return;

                    removeFromVerticalScrollGroup(verticalGroupName, scrollViewer);
                    removeFromHorizontalScrollGroup(horizontalGroupName, scrollViewer);
                    registeredScrollViewers.Remove(scrollViewer);

                    break;
                case ScrollSyncType.Horizontal:
                    removeFromVerticalScrollGroup(verticalGroupName, scrollViewer);
                    addToHorizontalScrollGroup(horizontalGroupName, scrollViewer);

                    if (registeredScrollViewers.ContainsKey(scrollViewer))
                        registeredScrollViewers[scrollViewer] = ScrollSyncType.Horizontal;
                    else
                        registeredScrollViewers.Add(scrollViewer, ScrollSyncType.Horizontal);

                    break;
                case ScrollSyncType.Vertical:
                    removeFromHorizontalScrollGroup(horizontalGroupName, scrollViewer);
                    addToVerticalScrollGroup(verticalGroupName, scrollViewer);

                    if (registeredScrollViewers.ContainsKey(scrollViewer))
                        registeredScrollViewers[scrollViewer] = ScrollSyncType.Vertical;
                    else
                        registeredScrollViewers.Add(scrollViewer, ScrollSyncType.Vertical);

                    break;
                case ScrollSyncType.Both:
                    if (registeredScrollViewers.ContainsKey(scrollViewer))
                    {
                        if (registeredScrollViewers[scrollViewer] == ScrollSyncType.Horizontal)
                            addToVerticalScrollGroup(verticalGroupName, scrollViewer);
                        else if (registeredScrollViewers[scrollViewer] == ScrollSyncType.Vertical)
                            addToHorizontalScrollGroup(horizontalGroupName, scrollViewer);

                        registeredScrollViewers[scrollViewer] = ScrollSyncType.Both;
                    }
                    else
                    {
                        addToHorizontalScrollGroup(horizontalGroupName, scrollViewer);
                        addToVerticalScrollGroup(verticalGroupName, scrollViewer);

                        registeredScrollViewers.Add(scrollViewer, ScrollSyncType.Both);
                    }

                    break;
            }
        }

        #endregion

        #endregion

        #region Variable(s)

        private static readonly Dictionary<string, OffSetContainer> verticalScrollGroups = new Dictionary<string, OffSetContainer>();
        private static readonly Dictionary<string, OffSetContainer> horizontalScrollGroups = new Dictionary<string, OffSetContainer>();
        private static readonly Dictionary<ScrollViewer, ScrollSyncType> registeredScrollViewers = new Dictionary<ScrollViewer, ScrollSyncType>();

        #endregion

        #region Method(s)

        private static void removeFromVerticalScrollGroup(string verticalGroupName, ScrollViewer scrollViewer)
        {
            if (verticalScrollGroups.ContainsKey(verticalGroupName))
            {
                verticalScrollGroups[verticalGroupName].ScrollViewers.Remove(scrollViewer);
                if (verticalScrollGroups[verticalGroupName].ScrollViewers.Count == 0)
                    verticalScrollGroups.Remove(verticalGroupName);
            }

            scrollViewer.ScrollChanged -= ScrollViewer_VerticalScrollChanged;
        }

        private static void addToVerticalScrollGroup(string verticalGroupName, ScrollViewer scrollViewer)
        {
            if (verticalScrollGroups.ContainsKey(verticalGroupName))
            {
                scrollViewer.ScrollToVerticalOffset(verticalScrollGroups[verticalGroupName].Offset);
                verticalScrollGroups[verticalGroupName].ScrollViewers.Add(scrollViewer);
            }
            else
            {
                verticalScrollGroups.Add(verticalGroupName, new OffSetContainer { ScrollViewers = new List<ScrollViewer> { scrollViewer }, Offset = scrollViewer.VerticalOffset });
            }

            scrollViewer.ScrollChanged += ScrollViewer_VerticalScrollChanged;
        }

        private static void removeFromHorizontalScrollGroup(string horizontalGroupName, ScrollViewer scrollViewer)
        {
            if (horizontalScrollGroups.ContainsKey(horizontalGroupName))
            {
                horizontalScrollGroups[horizontalGroupName].ScrollViewers.Remove(scrollViewer);
                if (horizontalScrollGroups[horizontalGroupName].ScrollViewers.Count == 0)
                    horizontalScrollGroups.Remove(horizontalGroupName);
            }

            scrollViewer.ScrollChanged -= ScrollViewer_HorizontalScrollChanged;
        }

        private static void addToHorizontalScrollGroup(string horizontalGroupName, ScrollViewer scrollViewer)
        {
            if (horizontalScrollGroups.ContainsKey(horizontalGroupName))
            {
                scrollViewer.ScrollToHorizontalOffset(horizontalScrollGroups[horizontalGroupName].Offset);
                horizontalScrollGroups[horizontalGroupName].ScrollViewers.Add(scrollViewer);
            }
            else
            {
                horizontalScrollGroups.Add(horizontalGroupName, new OffSetContainer { ScrollViewers = new List<ScrollViewer> { scrollViewer }, Offset = scrollViewer.HorizontalOffset });
            }

            scrollViewer.ScrollChanged += ScrollViewer_HorizontalScrollChanged;
        }

        private static string readStringDPValue(DependencyObject d, DependencyProperty dp)
        {
            var value = d.ReadLocalValue(dp);
            return (value == DependencyProperty.UnsetValue ? string.Empty : value.ToString());
        }

        private static ScrollSyncType readSyncTypeDPValue(DependencyObject d, DependencyProperty dp)
        {
            var value = d.ReadLocalValue(dp);
            return (value == DependencyProperty.UnsetValue ? ScrollSyncType.None : (ScrollSyncType)value);
        }

        #endregion

        #region Event Handler(s)

        private static void ScrollViewer_VerticalScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var changedScrollViewer = sender as ScrollViewer;
            if (changedScrollViewer == null)
                return;

            if (e.VerticalChange == 0)
                return;

            var verticalScrollGroup = readStringDPValue(sender as DependencyObject, VerticalScrollGroupProperty);
            if (!verticalScrollGroups.ContainsKey(verticalScrollGroup))
                return;

            verticalScrollGroups[verticalScrollGroup].Offset = changedScrollViewer.VerticalOffset;

            foreach (var scrollViewer in verticalScrollGroups[verticalScrollGroup].ScrollViewers)
            {
                if (scrollViewer.VerticalOffset == changedScrollViewer.VerticalOffset)
                    continue;

                scrollViewer.ScrollToVerticalOffset(changedScrollViewer.VerticalOffset);
            }
        }

        private static void ScrollViewer_HorizontalScrollChanged(object sender, ScrollChangedEventArgs e)
        {
            var changedScrollViewer = sender as ScrollViewer;
            if (changedScrollViewer == null)
                return;

            if (e.HorizontalChange == 0)
                return;

            var horizontalScrollGroup = readStringDPValue(sender as DependencyObject, HorizontalScrollGroupProperty);
            if (!horizontalScrollGroups.ContainsKey(horizontalScrollGroup))
                return;

            horizontalScrollGroups[horizontalScrollGroup].Offset = changedScrollViewer.HorizontalOffset;

            foreach (var scrollViewer in horizontalScrollGroups[horizontalScrollGroup].ScrollViewers)
            {
                if (scrollViewer.HorizontalOffset == changedScrollViewer.HorizontalOffset)
                    continue;

                scrollViewer.ScrollToHorizontalOffset(changedScrollViewer.HorizontalOffset);
            }
        }

        #endregion

        #region Class(es)

        private class OffSetContainer
        {
            public double Offset { get; set; }
            public List<ScrollViewer> ScrollViewers { get; set; }
        }

        #endregion
    }

    public enum ScrollSyncType
    {
        Both,
        Horizontal,
        Vertical,
        None
    }
}
 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值