using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Media; namespace UILib { public class TreeViewColumnWidthHelper { public static GridLength? GetAutoWidth(GridViewColumn column) { return (GridLength?)column.GetValue(AutoWidthProperty); } public static void SetAutoWidth(GridViewColumn column, GridLength? length) { column.SetValue(AutoWidthProperty, length); } public static readonly DependencyProperty AutoWidthProperty = DependencyProperty.RegisterAttached("AutoWidth", typeof(GridLength?), typeof(GridViewColumn), new PropertyMetadata(null)); } public class AlternatingRowConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (value is TreeViewItem item) { int globalIndex = GetGlobalIndex(item); return globalIndex % 2 == 0 ? Brushes.White : new SolidColorBrush(Color.FromRgb(245, 245, 245)); } return Brushes.White; } private int GetGlobalIndex(TreeViewItem item) { ItemsControl parent = ItemsControl.ItemsControlFromItemContainer(item); if (parent == null) return 0; // 获取当前item在父容器中的索引 int indexInParent; if (parent is TreeViewItem treeViewItem) { indexInParent = GetGlobalIndex(treeViewItem); } else { indexInParent = 0; } // 父级内索引 * 2 + 当前索引 var parentItems = parent.Items; int currentIndex = parentItems.IndexOf(item.DataContext); return indexInParent * 2 + currentIndex; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) => throw new NotImplementedException(); } /// /// 按钮样式转换 /// public class SimpleTreeListViewButtonConverter : IMultiValueConverter { public static SimpleTreeListViewButtonConverter Instance = new SimpleTreeListViewButtonConverter(); public static readonly double sqrt2 = Math.Sqrt(2); public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values[0] is TreeListViewItem tlvi) { if (values[1] is int level && parameter.ToString() == "Size") { double BaseSize = (double)values[2]; return (level + 1) * BaseSize; } if (values[1] is int level1 && values[2] is int index && values[3] is bool IsExpanded && values[4] is bool IsLastNode && values[5] is bool HasItems && parameter.ToString() == "Content") { double BaseSize = (double)values[6]; double BaseHalfSize = BaseSize / 2; double Length = BaseHalfSize + 1; var res = new GeometryGroup() { FillRule = FillRule.Nonzero }; if (level1 == 0) { // 画上面那根线 res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, 0), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - (Length / 2)), }); // 画右边那根线 res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize + (Length / 2), BaseHalfSize), EndPoint = new Point((level1 * BaseSize) + BaseSize, BaseHalfSize), }); } else { // 画上面那根线 res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, 0), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - (Length / 2)), }); // 画右边那根线 res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize + (Length / 2), BaseHalfSize), EndPoint = new Point((level1 * BaseSize) + BaseSize, BaseHalfSize), }); var parent = tlvi.parent; while (parent != null) { if (!parent.IsLastNode) { res.Children.Add(new LineGeometry { StartPoint = new Point((parent.level * BaseSize) + BaseHalfSize, 0), EndPoint = new Point((parent.level * BaseSize) + BaseHalfSize, BaseSize), }); } parent = parent.parent; } } if (!IsLastNode) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize + (Length / 2)), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseSize), }); } if (HasItems) { //画方 res.Children.Add(new RectangleGeometry(new Rect((level1 * BaseSize) + ((BaseSize - Length) / 2), ((BaseSize - Length) / 2), Length, Length))); res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize - (Length / 2), BaseHalfSize), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize + (Length / 2), BaseHalfSize), }); if (!IsExpanded) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - (Length / 2)), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize + (Length / 2)), }); } } else { if (!IsLastNode) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - (Length / 2)), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize + (Length / 2)), }); } else { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - (Length / 2)), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize), }); } res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize + (Length / 2), BaseHalfSize), }); } tlvi.root.UpdateColumnsWidth(); return res; } } return null; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } /// /// StepList按钮转换 /// public class TreeListViewButtonConverter : IMultiValueConverter { public static TreeListViewButtonConverter Instance = new TreeListViewButtonConverter(); public static readonly double sqrt2 = Math.Sqrt(2); public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) { if (values[0] is TreeListViewItem tlvi) { if (values[1] is int level && parameter.ToString() == "Size") { double BaseSize = (double)values[2]; return (level + 1) * BaseSize; } if (parameter.ToString() == "Index") { int totalIndex = 1; int Traverse(TreeListViewItem item) { if (item != tlvi) { totalIndex++; foreach (var obj in item.Items) { var childItem = item.ItemContainerGenerator.ContainerFromItem(obj) as TreeListViewItem; if (childItem != null) return Traverse(childItem); } } return totalIndex; } foreach (var obj in tlvi.root.Items) { var item_0 = tlvi.root.ItemContainerGenerator.ContainerFromItem(obj) as TreeListViewItem; if (item_0 != null) return Traverse(item_0); } return totalIndex; } if (values[1] is int level1 && values[2] is int index && values[3] is bool IsExpanded && values[4] is bool IsLastNode && values[5] is bool HasItems && parameter.ToString() == "Content") { double BaseSize = (double)values[6]; double BaseHalfSize = BaseSize / 2; double Radius = BaseHalfSize * 0.6; var res = new GeometryGroup() { FillRule = FillRule.Nonzero }; if (level1 == 0) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, 0), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - Radius), }); } else { if (index == 0) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize), 0), EndPoint = new Point((level1 * BaseSize) + (((sqrt2 * BaseHalfSize) - Radius) / sqrt2), ((sqrt2 * BaseHalfSize) - Radius) / sqrt2) }); } else { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, 0), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - Radius), }); } var parent = tlvi.parent; while (parent != null) { if (!parent.IsLastNode) { res.Children.Add(new LineGeometry { StartPoint = new Point((parent.level * BaseSize) + BaseHalfSize, 0), EndPoint = new Point((parent.level * BaseSize) + BaseHalfSize, BaseSize), }); } parent = parent.parent; } } if (!IsLastNode) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize + Radius), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseSize), }); } // 有子项并且是展开状态,统一画斜线 if (HasItems) { if (IsExpanded) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize + (((sqrt2 * BaseHalfSize) - Radius) / sqrt2), BaseHalfSize + (((sqrt2 * BaseHalfSize) - Radius) / sqrt2)), EndPoint = new Point((level1 + 1) * BaseSize, BaseSize), }); } } //最后画圆 res.Children.Add(new EllipseGeometry { Center = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize), RadiusX = Radius, RadiusY = Radius, }); if (HasItems) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize - Radius, BaseHalfSize), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize + Radius, BaseHalfSize), }); if (!IsExpanded) { res.Children.Add(new LineGeometry { StartPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize - Radius), EndPoint = new Point((level1 * BaseSize) + BaseHalfSize, BaseHalfSize + Radius), }); } } tlvi.root.UpdateColumnsWidth(); return res; } } return null; } public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) { throw new NotImplementedException(); } } public class TreeListView : TreeView { public static readonly DependencyProperty ChildrenPathProperty = DependencyProperty.Register("ChildrenPath", typeof(string), typeof(TreeListView), new PropertyMetadata(null)); public string ChildrenPath { get { return (string)GetValue(ChildrenPathProperty); } set { SetValue(ChildrenPathProperty, value); } } public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListView), new PropertyMetadata(null)); public GridViewColumnCollection Columns { get { return (GridViewColumnCollection)GetValue(ColumnsProperty); } set { SetValue(ColumnsProperty, value); } } public static readonly DependencyProperty BorderColorProperty = DependencyProperty.Register("BorderColor", typeof(Brush), typeof(TreeListView), new PropertyMetadata(Brushes.Transparent)); public Brush BorderColor { get { return (Brush)GetValue(BorderColorProperty); } set { SetValue(BorderColorProperty, value); } } public static readonly DependencyProperty ColumnHeaderContainerStyleProperty = DependencyProperty.Register("ColumnHeaderContainerStyle", typeof(Style), typeof(TreeListView), new PropertyMetadata(null)); public Style ColumnHeaderContainerStyle { get { return (Style)GetValue(ColumnHeaderContainerStyleProperty); } set { SetValue(ColumnHeaderContainerStyleProperty, value); } } /// /// 鼠标移动颜色 /// public static readonly DependencyProperty ItemHoverColorProperty = DependencyProperty.Register("ItemHoverColor", typeof(Brush), typeof(TreeListView), new PropertyMetadata(Brushes.Red)); public Brush ItemHoverColor { get { return (Brush)GetValue(ItemHoverColorProperty); } set { SetValue(ItemHoverColorProperty, value); } } /// /// 鼠标移动颜色 /// public static readonly DependencyProperty ItemHoverForegroundProperty = DependencyProperty.Register("ItemHoverForeground", typeof(Brush), typeof(TreeListView), new PropertyMetadata(SystemColors.HighlightTextBrush)); public Brush ItemHoverForeground { get { return (Brush)GetValue(ItemHoverForegroundProperty); } set { SetValue(ItemHoverForegroundProperty, value); } } /// /// Item选中颜色 /// public static readonly DependencyProperty ItemSelectedColorProperty = DependencyProperty.Register("ItemSelected", typeof(Brush), typeof(TreeListView), new PropertyMetadata(SystemColors.HighlightBrush)); public Brush ItemSelectedColor { get { return (Brush)GetValue(ItemSelectedColorProperty); } set { SetValue(ItemSelectedColorProperty, value); } } /// /// Item选中字体颜色 /// public static readonly DependencyProperty ItemSelectedForegroundProperty = DependencyProperty.Register("ItemSelectedForeground", typeof(Brush), typeof(TreeListView), new PropertyMetadata(SystemColors.HighlightTextBrush)); public Brush ItemSelectedForeground { get { return (Brush)GetValue(ItemSelectedForegroundProperty); } set { SetValue(ItemSelectedForegroundProperty, value); } } static TreeListView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeListView), new FrameworkPropertyMetadata(typeof(TreeListView))); } public TreeListView() { // this.SizeChanged += TreeListView_SizeChanged; Columns = new GridViewColumnCollection(); } private void TreeListView_SizeChanged(object sender, SizeChangedEventArgs e) { double oldWidth = e.PreviousSize.Width, newWidth = e.NewSize.Width; double changed = newWidth - oldWidth; int columns = 0; if (oldWidth <= 0) { foreach (GridViewColumn col in Columns) { GridLength? length = TreeViewColumnWidthHelper.GetAutoWidth(col); if (length == null) { changed -= col.Width; continue; } if (length.Value.GridUnitType == GridUnitType.Auto) { if (double.IsNaN(col.Width)) { col.Width = col.ActualWidth; } col.Width = double.NaN; changed -= col.ActualWidth; } else if (length.Value.GridUnitType == GridUnitType.Star) { columns += 1; } } foreach (GridViewColumn col in Columns) { GridLength? length = TreeViewColumnWidthHelper.GetAutoWidth(col); if (length != null && length.Value.GridUnitType == GridUnitType.Star) { col.Width = Math.Max(20, col.Width + (changed / columns)); } } } else { foreach (GridViewColumn col in Columns) { GridLength? length = TreeViewColumnWidthHelper.GetAutoWidth(col); if (length != null && length.Value.GridUnitType == GridUnitType.Star) { columns += 1; } } if (columns <= 0) { return; } foreach (GridViewColumn col in Columns) { GridLength? length = TreeViewColumnWidthHelper.GetAutoWidth(col); if (length != null && length.Value.GridUnitType == GridUnitType.Star) { col.Width = Math.Max(20, col.Width + (changed / columns)); } } } } protected override void OnItemTemplateChanged(DataTemplate oldItemTemplate, DataTemplate newItemTemplate) { try { HierarchicalDataTemplate ttt = newItemTemplate as HierarchicalDataTemplate; if (ttt != null) { ttt.ItemsSource = new Binding(ChildrenPath) { Mode = BindingMode.OneWay }; } } catch { } base.OnItemTemplateChanged(oldItemTemplate, newItemTemplate); } internal void UpdateColumnsWidth() { int i = 0; var totalWidth = this.ActualWidth - 20; int columns = 0; // foreach(var item in this.It) foreach (GridViewColumn col in Columns) { GridLength? length = TreeViewColumnWidthHelper.GetAutoWidth(col); if (length == null || length.Value.GridUnitType == GridUnitType.Pixel) { col.Width = col.Width; totalWidth -= col.Width; } else if (length.Value.GridUnitType == GridUnitType.Auto) { if (double.IsNaN(col.Width)) { col.Width = col.ActualWidth; } col.Width = double.NaN; totalWidth -= col.ActualWidth; } else if (length.Value.GridUnitType == GridUnitType.Star) { columns += 1; } } //foreach (GridViewColumn col in Columns) //{ // GridLength? length = TreeViewColumnWidthHelper.GetAutoWidth(col); // if (length != null && length.Value.GridUnitType == GridUnitType.Star) // { // col.Width = totalWidth / columns; // } //} } public override void OnApplyTemplate() { base.OnApplyTemplate(); } protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); } protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { base.OnItemsSourceChanged(oldValue, newValue); if (oldValue is INotifyCollectionChanged oncc) { oncc.CollectionChanged -= Nncc_CollectionChanged; } if (oldValue is INotifyPropertyChanged onpc) { onpc.PropertyChanged -= Nnpc_PropertyChanged; } if (newValue is INotifyCollectionChanged nncc) { nncc.CollectionChanged += Nncc_CollectionChanged; } if (newValue is INotifyPropertyChanged nnpc) { nnpc.PropertyChanged += Nnpc_PropertyChanged; } } internal void UpdateChildrenUI() { if (ItemsSource is IEnumerable source) { foreach (var obj in source) { var sb = this.ItemContainerGenerator.ContainerFromItem(obj) as TreeListViewItem; sb?.TryUpdateUI(); } } } private void Nnpc_PropertyChanged(object sender, PropertyChangedEventArgs e) { //UpdateChildrenUI(); } private void Nncc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { UpdateChildrenUI(); } protected override DependencyObject GetContainerForItemOverride() { return new TreeListViewItem(0, this, null); } } public class TreeListViewItem : TreeViewItem, INotifyPropertyChanged { public static void TreeViewItem_PreviewMouseDoubleClick(object sender, System.Windows.Input.MouseButtonEventArgs e) { // 向上遍历视觉树,查找是否有需要处理双击的控件 DependencyObject current = e.OriginalSource as DependencyObject; while (current != null) { // 如果找到需要处理双击的控件(例如Button、TextBox等),则不阻止事件 if (current is EditBox eb && eb.CanEdit) { return; // 让事件继续传递 } current = VisualTreeHelper.GetParent(current); } e.Handled = true; } public static readonly DependencyProperty ColumnsProperty = DependencyProperty.Register("Columns", typeof(GridViewColumnCollection), typeof(TreeListViewItem), new PropertyMetadata(null)); public event PropertyChangedEventHandler PropertyChanged; private int _totalIndex; public int TotalIndex { get => _totalIndex; set { if (_totalIndex != value) { _totalIndex = value; PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TotalIndex))); } } } public int level { get { int i = 0; var p = parent; while (p != null) { i++; p = p.parent; } return i; } } public int index { get { return parent == null ? root.Items.IndexOf(this.DataContext) : parent.Items.IndexOf(this.DataContext); } } public bool IsLastNode { get { return parent == null ? index == root.Items.Count - 1 : index == parent.Items.Count - 1; } } public TreeListView root { get; private set; } public TreeListViewItem parent { get; private set; } public TreeListViewItem(int level, TreeListView root, TreeListViewItem parent) { this.IsExpanded = true; this.root = root; this.parent = parent; this.Loaded += TreeListViewItem_Loaded; this.AddHandler(TreeViewItem.RequestBringIntoViewEvent, new RequestBringIntoViewEventHandler(TreeViewItem_RequestBringIntoView), handledEventsToo: true); } private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e) { // 取消自动滚动 e.Handled = true; } private void TreeListViewItem_Loaded(object sender, RoutedEventArgs e) { } protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { base.OnItemsSourceChanged(oldValue, newValue); if (oldValue is INotifyCollectionChanged oncc) { oncc.CollectionChanged -= Nncc_CollectionChanged; } if (oldValue is INotifyPropertyChanged onpc) { onpc.PropertyChanged -= Nnpc_PropertyChanged; } if (newValue is INotifyCollectionChanged nncc) { nncc.CollectionChanged += Nncc_CollectionChanged; } if (newValue is INotifyPropertyChanged nnpc) { nnpc.PropertyChanged += Nnpc_PropertyChanged; } } protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); } private void Nnpc_PropertyChanged(object sender, PropertyChangedEventArgs e) { //root.UpdateChildrenUI(); } private void Nncc_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { //root.UpdateChildrenUI(); TryUpdateUI(); } internal void TryUpdateUI() { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(level))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(index))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsLastNode))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(HasItems))); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(IsExpanded))); if (ItemsSource is IEnumerable source) { foreach (var obj in source) { var sb = this.ItemContainerGenerator.ContainerFromItem(obj) as TreeListViewItem; sb?.TryUpdateUI(); } } } static TreeListViewItem() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeListViewItem), new FrameworkPropertyMetadata(typeof(TreeListViewItem))); } protected override DependencyObject GetContainerForItemOverride() { return new TreeListViewItem(level + 1, root, this); } } }