From a3f118adca1bd108c9be516397445e688fa74fac Mon Sep 17 00:00:00 2001 From: Alexey Bogdanov Date: Thu, 30 Jan 2020 20:40:18 +0300 Subject: [PATCH] [WPF] Implement GTK-like behaviour for Expand and CanResize column flags of TreeView --- Xwt.WPF/Xwt.WPFBackend/ExGridViewColumn.cs | 80 ++++++++++++ Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs | 134 +++++++++++++++++++-- Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs | 22 +--- Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs | 24 +++- 4 files changed, 226 insertions(+), 34 deletions(-) create mode 100644 Xwt.WPF/Xwt.WPFBackend/ExGridViewColumn.cs diff --git a/Xwt.WPF/Xwt.WPFBackend/ExGridViewColumn.cs b/Xwt.WPF/Xwt.WPFBackend/ExGridViewColumn.cs new file mode 100644 index 000000000..77d022cfc --- /dev/null +++ b/Xwt.WPF/Xwt.WPFBackend/ExGridViewColumn.cs @@ -0,0 +1,80 @@ +using System; +using System.ComponentModel; +using System.Windows.Controls; +using System.Windows.Data; + +namespace Xwt.WPFBackend +{ + public class ExGridViewColumn + : GridViewColumn + { + private readonly BindingSource bindingSource; + + public ExGridViewColumn (Action onWidthUpdated) + { + bindingSource = new BindingSource (onWidthUpdated); + + var widthBinding = new Binding (BindingSource.WidthPropertyName) { + Mode = BindingMode.TwoWay, + Source = bindingSource, + }; + BindingOperations.SetBinding (this, WidthProperty, widthBinding); + } + + public bool Expands { get; set; } + + public bool CanResize { + get { return bindingSource.CanResize; } + set { bindingSource.CanResize = value; } + } + + public void SetWidthForced (double width) + { + bindingSource.SetWidthForced (width); + } + + class BindingSource + : INotifyPropertyChanged + { + public const string WidthPropertyName = nameof (Width); + + private readonly Action onWidthUpdated; + private double width = double.NaN; + + public BindingSource (Action onWidthUpdated) + { + this.onWidthUpdated = onWidthUpdated; + } + + public event PropertyChangedEventHandler PropertyChanged; + + public bool CanResize { get; set; } + + public double Width { + get { + return width; + } + set { + if (CanResize) { + width = value; + onWidthUpdated (); + } + OnPropertyChanged (WidthPropertyName); + } + } + + public void SetWidthForced (double width) + { + bool savedCanResize = CanResize; + CanResize = true; + Width = width; + CanResize = savedCanResize; + } + + private void OnPropertyChanged (string name) + { + PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (name)); + } + } + } +} diff --git a/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs b/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs index 2ff994ed8..f39554312 100644 --- a/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs +++ b/Xwt.WPF/Xwt.WPFBackend/ExTreeView.cs @@ -1,4 +1,4 @@ -// +// // ExTreeView.cs // // Author: @@ -35,11 +35,10 @@ using System.Windows.Controls; using System.Collections.Generic; using SWC = System.Windows.Controls; +using SWM = System.Windows.Media; using WKey = System.Windows.Input.Key; -using System.Windows.Input; -using System.Windows.Controls.Primitives; -using System.Windows.Automation.Peers; - +using System.Windows.Automation.Peers; + namespace Xwt.WPFBackend { using Keyboard = System.Windows.Input.Keyboard; @@ -47,13 +46,17 @@ namespace Xwt.WPFBackend public class ExTreeView : SWC.TreeView, IWpfWidget { - public ExTreeView() + private ScrollViewer scrollViewer; + private bool needColumnWidthsUpdate; + + public ExTreeView () { SelectedItems = new ObservableCollection (); - Loaded += new RoutedEventHandler(ExTreeView_Loaded); - } - - void ExTreeView_Loaded(object sender, RoutedEventArgs e) + Loaded += OnLoaded; + View.Columns.CollectionChanged += OnColumnsCollectionChanged; + } + + private void OnLoaded(object sender, RoutedEventArgs e) { if (SelectionMode == SWC.SelectionMode.Single) { @@ -65,6 +68,11 @@ void ExTreeView_Loaded(object sender, RoutedEventArgs e) } } + private void OnColumnsCollectionChanged (object sender, NotifyCollectionChangedEventArgs e) + { + UpdateColumnWidths (); + } + public event EventHandler SelectedItemsChanged; public WidgetBackend Backend @@ -133,6 +141,24 @@ public ExTreeViewItem ContainerFromObject (object item) return treeItem; } + public void UpdateColumnWidths () + { + needColumnWidthsUpdate = true; + InvalidateVisual (); + } + + protected override void OnRenderSizeChanged (SizeChangedInfo sizeInfo) + { + UpdateColumnWidths (); + base.OnRenderSizeChanged (sizeInfo); + } + + protected override void OnRender (SWM.DrawingContext drawingContext) + { + EnsureColumnWidthUpdated (); + base.OnRender (drawingContext); + } + protected override bool IsItemItsOwnContainerOverride (object item) { return item is ExTreeViewItem; @@ -183,6 +209,94 @@ protected virtual void OnSelectedItemsPropertyChanged (DependencyPropertyChanged var newNotifying = e.NewValue as INotifyCollectionChanged; if (newNotifying != null) newNotifying.CollectionChanged += SelectedItemsCollectionChanged; + } + + private void EnsureColumnWidthUpdated () + { + if (!needColumnWidthsUpdate) + return; + + UpdateExpandableColumnWidths (); + needColumnWidthsUpdate = false; + } + + private ScrollViewer FindScrollViewer () + { + DependencyObject currentObj = this; + while (currentObj != null) { + var scrollViewer = FindAmongChildren (currentObj); + if (scrollViewer == null) { + currentObj = SWM.VisualTreeHelper.GetParent (currentObj); + continue; + } + + scrollViewer.ScrollChanged += OnScrollViewerScrollChanged; + return scrollViewer; + } + return null; + } + + private static T FindAmongChildren (DependencyObject obj) + where T : DependencyObject + { + int childrenCount = SWM.VisualTreeHelper.GetChildrenCount (obj); + for (int i = 0; i < childrenCount; i++) { + var child = SWM.VisualTreeHelper.GetChild (obj, i); + if (child is T) + return (T) child; + + var result = FindAmongChildren (child); + if (result != null) + return result; + } + return null; + } + + private void OnScrollViewerScrollChanged (object sender, ScrollChangedEventArgs e) + { + UpdateColumnWidths (); + } + + private double CalculateTreeContentWidth () + { + if (scrollViewer == null) + scrollViewer = FindScrollViewer (); + + double width = ActualWidth; + if (scrollViewer != null) { + if (scrollViewer?.ComputedVerticalScrollBarVisibility == Visibility.Visible) + width -= SystemParameters.VerticalScrollBarWidth; + } + + const double HorizontalMargin = 10; // Prevents horizontal scroll bar to appear before it should + return width - HorizontalMargin; + } + + private void UpdateExpandableColumnWidths () + { + double totalWidth = CalculateTreeContentWidth (); + if (totalWidth <= 0.0) + return; + + var columns = View.Columns.Select (c => (ExGridViewColumn) c); + if (!columns.Any ()) + return; + + var expandableColumns = columns.Where (c => c.Expands).ToList (); + var nonExpandableColumns = columns.Where (c => !c.Expands).ToList (); + + if (expandableColumns.Count == 0) { + var lastColumn = columns.Last (); + expandableColumns.Add (lastColumn); + nonExpandableColumns.Remove (lastColumn); + } + + double totalFixedWidth = nonExpandableColumns.Sum (c => c.ActualWidth); + double totalWidthForExpanding = totalWidth - totalFixedWidth; + double newExpandableWidth = Math.Max (totalWidthForExpanding / expandableColumns.Count, 0.0); + + foreach (var column in expandableColumns) + column.SetWidthForced (newExpandableWidth); } private bool TraverseTree (Func action, ExTreeViewItem parent = null) diff --git a/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs b/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs index f2d997ec4..9833ce6a8 100644 --- a/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs +++ b/Xwt.WPF/Xwt.WPFBackend/ExTreeViewItem.cs @@ -43,10 +43,8 @@ namespace Xwt.WPFBackend public class ExTreeViewItem : TreeViewItem { - - public ExTreeViewItem() + public ExTreeViewItem () { - Loaded += OnLoaded; HorizontalContentAlignment = HorizontalAlignment.Stretch; } @@ -208,24 +206,6 @@ private void FindParent() } } - private void OnLoaded (object sender, RoutedEventArgs routedEventArgs) - { - ItemsControl parent = ItemsControlFromItemContainer (this); - if (parent == null) - return; - - int index = parent.Items.IndexOf (DataContext); - if (index != parent.Items.Count - 1) - return; - - foreach (var column in this.view.View.Columns) { - if (Double.IsNaN (column.Width)) - column.Width = column.ActualWidth; - - column.Width = Double.NaN; - } - } - protected override void OnKeyDown(System.Windows.Input.KeyEventArgs e) { //We can't allow TreeViewItem(our base class) to get this message(OnKeyDown) because it will mess with our ExTreeView handling diff --git a/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs b/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs index 6ee8e43d1..62c0605b2 100644 --- a/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs +++ b/Xwt.WPF/Xwt.WPFBackend/TreeViewBackend.cs @@ -238,7 +238,13 @@ public void SetSource (ITreeDataSource source, IBackend sourceBackend) public object AddColumn (ListViewColumn column) { - var col = new GridViewColumn (); + if (column.Expands && column.CanResize) + column.CanResize = false; + + var col = new ExGridViewColumn (Tree.UpdateColumnWidths) { + Expands = column.Expands, + CanResize = column.CanResize, + }; UpdateColumn (column, col, ListViewColumnChange.Title); @@ -251,7 +257,7 @@ public object AddColumn (ListViewColumn column) public void UpdateColumn (ListViewColumn column, object handle, ListViewColumnChange change) { - var col = ((GridViewColumn) handle); + var col = (ExGridViewColumn) handle; switch (change) { case ListViewColumnChange.Title: col.Header = column.Title; @@ -271,12 +277,24 @@ public void UpdateColumn (ListViewColumn column, object handle, ListViewColumnCh } MapColumn (column, col); - break; + case ListViewColumnChange.Alignment: var style = new Style(typeof(GridViewColumnHeader)); style.Setters.Add(new Setter(Control.HorizontalContentAlignmentProperty, Util.ToWpfHorizontalAlignment(column.Alignment))); col.HeaderContainerStyle = style; + break; + + case ListViewColumnChange.Expanding: + if (column.Expands && column.CanResize) + column.CanResize = false; + col.Expands = column.Expands; + break; + + case ListViewColumnChange.CanResize: + if (column.CanResize && column.Expands) + column.Expands = false; + col.CanResize = column.CanResize; break; } }