using System; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Data.Common; 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 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(); } } 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 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 ColumnHeaderContainerStyleProperty = DependencyProperty.Register("ColumnHeaderContainerStyle", typeof(Style), typeof(TreeListView), new PropertyMetadata(null)); public Style ColumnHeaderContainerStyle { get { return (Style)GetValue(ColumnHeaderContainerStyleProperty); } set { SetValue(ColumnHeaderContainerStyleProperty, value); } } static TreeListView() { DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeListView), new FrameworkPropertyMetadata(typeof(TreeListView))); } public TreeListView() { Columns = new GridViewColumnCollection(); } internal void UpdateColumnsWidth() { int i = 0; // foreach(var item in this.It) foreach (GridViewColumn col in Columns) { var length = TreeViewColumnWidthHelper.GetAutoWidth(col); if (length == null) { continue; } if (length.Value.GridUnitType == GridUnitType.Auto) { if (double.IsNaN(col.Width)) { col.Width = col.ActualWidth; } col.Width = double.NaN; } } } 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 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; } 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); } } }