From ed6331cd3dfde3036da361cdddc75e5031228e30 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 3 Nov 2021 15:41:53 +0100 Subject: [PATCH 1/2] feat(DataGrid): Allow binding DataGridColumn Width --- .../DataGridColumn.cs | 159 ++++++++++-------- .../DataGridColumnHeader.cs | 4 +- .../DataGridColumns.cs | 17 +- .../DataGridLength.cs | 1 - 4 files changed, 106 insertions(+), 75 deletions(-) diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs index a5695afeb76..a77ac985aca 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -7,7 +7,6 @@ using Avalonia.Interactivity; using Avalonia.VisualTree; using Avalonia.Collections; -using Avalonia.Utilities; using System; using System.ComponentModel; using System.Linq; @@ -24,8 +23,6 @@ public abstract class DataGridColumn : AvaloniaObject { internal const int DATAGRIDCOLUMN_maximumWidth = 65536; private const bool DATAGRIDCOLUMN_defaultIsReadOnly = false; - - private DataGridLength? _width; // Null by default, null means inherit the Width from the DataGrid private bool? _isReadOnly; private double? _maxWidth; private double? _minWidth; @@ -39,6 +36,7 @@ public abstract class DataGridColumn : AvaloniaObject private IBinding _clipboardContentBinding; private ControlTheme _cellTheme; private Classes _cellStyleClasses; + private bool _setWidthInternalNoCallback; /// /// Initializes a new instance of the class. @@ -214,6 +212,36 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang OwningGrid?.OnColumnVisibleStateChanged(this); NotifyPropertyChanged(change.Property.Name); } + else if (change.Property == WidthProperty) + { + if (!_settingWidthInternally) + { + InheritsWidth = false; + } + if (_setWidthInternalNoCallback == false) + { + var grid = OwningGrid; + var width = (change as AvaloniaPropertyChangedEventArgs).NewValue.Value; + if (grid != null) + { + var oldWidth = (change as AvaloniaPropertyChangedEventArgs).OldValue.Value; + if (width.IsStar != oldWidth.IsStar) + { + SetWidthInternalNoCallback(width); + IsInitialDesiredWidthDetermined = false; + grid.OnColumnWidthChanged(this); + } + else + { + Resize(oldWidth, width, false); + } + } + else + { + SetWidthInternalNoCallback(width); + } + } + } } @@ -549,48 +577,15 @@ public double MinWidth } } + public static readonly StyledProperty WidthProperty = AvaloniaProperty + .Register(nameof(Width) + , coerce: CoerceWidth + ); + public DataGridLength Width { - get - { - return - _width ?? - OwningGrid?.ColumnWidth ?? - // We don't have a good choice here because we don't want to make this property nullable, see DevDiv Bugs 196581 - DataGridLength.Auto; - } - set - { - if (!_width.HasValue || _width.Value != value) - { - if (!_settingWidthInternally) - { - InheritsWidth = false; - } - - if (OwningGrid != null) - { - DataGridLength width = CoerceWidth(value); - if (width.IsStar != Width.IsStar) - { - // If a column has changed either from or to a star value, we want to recalculate all - // star column widths. They are recalculated during Measure based off what the value we set here. - SetWidthInternalNoCallback(width); - IsInitialDesiredWidthDetermined = false; - OwningGrid.OnColumnWidthChanged(this); - } - else - { - // If a column width's value is simply changing, we resize it (to the right only). - Resize(width.Value, width.UnitType, width.DesiredValue, width.DisplayValue, false); - } - } - else - { - SetWidthInternalNoCallback(value); - } - } - } + get => this.GetValue(WidthProperty); + set => SetValue(WidthProperty, value); } /// @@ -812,19 +807,34 @@ internal void EndCellEditInternal() /// on the rest of the star columns. For pixel widths, the desired value is based on the pixel value. /// For auto widths, the desired value is initialized as the column's minimum width. /// + /// /// The DataGridLength to coerce. /// The resultant (coerced) DataGridLength. - internal DataGridLength CoerceWidth(DataGridLength width) + static DataGridLength CoerceWidth(AvaloniaObject source, DataGridLength width) { + var target = (DataGridColumn)source; + + if (target._setWidthInternalNoCallback) + { + return width; + } + + if (!target.IsSet(WidthProperty)) + { + + return target.OwningGrid?.ColumnWidth ?? + DataGridLength.Auto; + } + double desiredValue = width.DesiredValue; if (double.IsNaN(desiredValue)) { - if (width.IsStar && OwningGrid != null && OwningGrid.ColumnsInternal != null) + if (width.IsStar && target.OwningGrid != null && target.OwningGrid.ColumnsInternal != null) { double totalStarValues = 0; double totalStarDesiredValues = 0; double totalNonStarDisplayWidths = 0; - foreach (DataGridColumn column in OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != this && !double.IsNaN(c.Width.DesiredValue))) + foreach (DataGridColumn column in target.OwningGrid.ColumnsInternal.GetDisplayedColumns(c => c.IsVisible && c != target && !double.IsNaN(c.Width.DesiredValue))) { if (column.Width.IsStar) { @@ -839,7 +849,7 @@ internal DataGridLength CoerceWidth(DataGridLength width) if (totalStarValues == 0) { // Compute the new star column's desired value based on the available space if there are no other visible star columns - desiredValue = Math.Max(ActualMinWidth, OwningGrid.CellsWidth - totalNonStarDisplayWidths); + desiredValue = Math.Max(target.ActualMinWidth, target.OwningGrid.CellsWidth - totalNonStarDisplayWidths); } else { @@ -853,7 +863,7 @@ internal DataGridLength CoerceWidth(DataGridLength width) } else { - desiredValue = ActualMinWidth; + desiredValue = target.ActualMinWidth; } } @@ -862,7 +872,7 @@ internal DataGridLength CoerceWidth(DataGridLength width) { displayValue = desiredValue; } - displayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue)); + displayValue = Math.Max(target.ActualMinWidth, Math.Min(target.ActualMaxWidth, displayValue)); return new DataGridLength(width.Value, width.UnitType, desiredValue, displayValue); } @@ -896,7 +906,7 @@ internal virtual DataGridColumnHeader CreateHeader() }; result[!ContentControl.ContentProperty] = this[!HeaderProperty]; result[!ContentControl.ContentTemplateProperty] = this[!HeaderTemplateProperty]; - if (OwningGrid.ColumnHeaderTheme is {} columnTheme) + if (OwningGrid.ColumnHeaderTheme is { } columnTheme) { result.SetValue(StyledElement.ThemeProperty, columnTheme, BindingPriority.Template); } @@ -909,7 +919,7 @@ internal virtual DataGridColumnHeader CreateHeader() /// internal void EnsureWidth() { - SetWidthInternalNoCallback(CoerceWidth(Width)); + SetWidthInternalNoCallback(CoerceWidth(this, Width)); } internal Control GenerateElementInternal(DataGridCell cell, object dataItem) @@ -931,17 +941,17 @@ internal object PrepareCellForEditInternal(Control editingElement, RoutedEventAr /// can only decrease in size by the amount that the columns after it can increase in size. /// Likewise, the column can only increase in size if other columns can spare the width. /// - /// The new Value. - /// The new UnitType. - /// The new DesiredValue. - /// The new DisplayValue. + /// with before resize. + /// with after resize. /// Whether or not this resize was initiated by a user action. - internal void Resize(double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue, bool userInitiated) + + // double value, DataGridLengthUnitType unitType, double desiredValue, double displayValue + internal void Resize(DataGridLength oldWidth, DataGridLength newWidth, bool userInitiated) { - double newValue = value; - double newDesiredValue = desiredValue; - double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, displayValue)); - DataGridLengthUnitType newUnitType = unitType; + double newValue = newWidth.Value; + double newDesiredValue = newWidth.DesiredValue; + double newDisplayValue = Math.Max(ActualMinWidth, Math.Min(ActualMaxWidth, newWidth.DisplayValue)); + DataGridLengthUnitType newUnitType = newWidth.UnitType; int starColumnsCount = 0; double totalDisplayWidth = 0; @@ -955,11 +965,11 @@ internal void Resize(double value, DataGridLengthUnitType unitType, double desir // If we're using star sizing, we can only resize the column as much as the columns to the // right will allow (i.e. until they hit their max or min widths). - if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (unitType == DataGridLengthUnitType.Star && Width.IsStar && userInitiated))) + if (!hasInfiniteAvailableWidth && (starColumnsCount > 0 || (newUnitType == DataGridLengthUnitType.Star && newWidth.IsStar && userInitiated))) { - double limitedDisplayValue = Width.DisplayValue; + double limitedDisplayValue = oldWidth.DisplayValue; double availableIncrease = Math.Max(0, OwningGrid.CellsWidth - totalDisplayWidth); - double desiredChange = newDisplayValue - Width.DisplayValue; + double desiredChange = newDisplayValue - oldWidth.DisplayValue; if (desiredChange > availableIncrease) { // The desired change is greater than the amount of available space, @@ -979,7 +989,7 @@ internal void Resize(double value, DataGridLengthUnitType unitType, double desir // The desired change is negative, so we need to increase the widths of columns to the right. limitedDisplayValue += desiredChange + OwningGrid.IncreaseColumnWidths(DisplayIndex + 1, -desiredChange, userInitiated); } - if (ActualCanUserResize || (Width.IsStar && !userInitiated)) + if (ActualCanUserResize || (oldWidth.IsStar && !userInitiated)) { newDisplayValue = limitedDisplayValue; } @@ -1002,9 +1012,10 @@ internal void Resize(double value, DataGridLengthUnitType unitType, double desir } } - DataGridLength oldWidth = Width; - SetWidthInternalNoCallback(new DataGridLength(Math.Min(double.MaxValue, newValue), newUnitType, newDesiredValue, newDisplayValue)); - if (Width != oldWidth) + newDisplayValue = Math.Min(double.MaxValue, newValue); + newWidth = new DataGridLength(newDisplayValue, newUnitType, newDesiredValue, newDisplayValue); + SetWidthInternalNoCallback(newWidth); + if (newWidth != oldWidth) { OwningGrid.OnColumnWidthChanged(this); } @@ -1052,7 +1063,17 @@ internal void SetWidthInternal(DataGridLength width) /// The new Width. internal void SetWidthInternalNoCallback(DataGridLength width) { - _width = width; + var originalValue = _setWidthInternalNoCallback; + _setWidthInternalNoCallback = true; + try + { + Width = width; + } + finally + { + _setWidthInternalNoCallback = originalValue; + } + } /// @@ -1122,7 +1143,7 @@ internal DataGridSortDescription GetSortDescription() && OwningGrid.DataConnection != null && OwningGrid.DataConnection.SortDescriptions != null) { - if(CustomSortComparer != null) + if (CustomSortComparer != null) { return OwningGrid.DataConnection.SortDescriptions diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs index 0755864c250..28e8a0ed5e5 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -785,7 +785,9 @@ private void OnMouseMove_Resize(ref bool handled, Point mousePositionHeaders) double desiredWidth = _originalWidth + mouseDelta; desiredWidth = Math.Max(_dragColumn.ActualMinWidth, Math.Min(_dragColumn.ActualMaxWidth, desiredWidth)); - _dragColumn.Resize(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth, true); + _dragColumn.Resize(_dragColumn.Width, + new(_dragColumn.Width.Value, _dragColumn.Width.UnitType, _dragColumn.Width.DesiredValue, desiredWidth), + true); OwningGrid.UpdateHorizontalOffset(_originalHorizontalOffset); diff --git a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs index 4056b78bfe0..8dd5bd262b0 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -91,7 +91,10 @@ internal void AutoSizeColumn(DataGridColumn column, double desiredWidth) // this column is newly added, we'll just set its display value directly. if (UsesStarSizing && column.IsInitialDesiredWidthDetermined) { - column.Resize(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth, false); + var oldWidth = column.Width; + column.Resize(oldWidth, + new(column.Width.Value, column.Width.UnitType, desiredWidth, desiredWidth), + false); } else { @@ -142,7 +145,7 @@ internal bool GetColumnReadOnlyState(DataGridColumn dataGridColumn, bool isReadO { Debug.Assert(dataGridColumn != null); - if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn && + if (dataGridColumn is DataGridBoundColumn dataGridBoundColumn && dataGridBoundColumn.Binding is BindingBase binding) { var path = (binding as Binding)?.Path ?? (binding as CompiledBindingExtension)?.Path.ToString(); @@ -359,6 +362,7 @@ internal void OnColumnMaxWidthChanged(DataGridColumn column, double oldValue) if (column.IsVisible && oldValue != column.ActualMaxWidth) { + DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue); if (column.ActualMaxWidth < column.Width.DisplayValue) { // If the maximum width has caused the column to decrease in size, try first to resize @@ -371,7 +375,9 @@ internal void OnColumnMaxWidthChanged(DataGridColumn column, double oldValue) { // If the column was previously limited by its maximum value but has more room now, // attempt to resize the column to its desired width. - column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); + column.Resize(oldWitdh, + new (column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue), + false); } OnColumnWidthChanged(column); } @@ -388,6 +394,7 @@ internal void OnColumnMinWidthChanged(DataGridColumn column, double oldValue) if (column.IsVisible && oldValue != column.ActualMinWidth) { + DataGridLength oldWitdh = new(oldValue, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue); if (column.ActualMinWidth > column.Width.DisplayValue) { // If the minimum width has caused the column to increase in size, try first to resize @@ -400,7 +407,9 @@ internal void OnColumnMinWidthChanged(DataGridColumn column, double oldValue) { // If the column was previously limited by its minimum value but but can be smaller now, // attempt to resize the column to its desired width. - column.Resize(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue, false); + column.Resize(oldWitdh, + new(column.Width.Value, column.Width.UnitType, column.Width.DesiredValue, column.Width.DesiredValue), + false); } OnColumnWidthChanged(column); } diff --git a/src/Avalonia.Controls.DataGrid/DataGridLength.cs b/src/Avalonia.Controls.DataGrid/DataGridLength.cs index a193352f526..9bff8b2cf5d 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridLength.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridLength.cs @@ -7,7 +7,6 @@ using System; using System.ComponentModel; using System.Globalization; -using Avalonia.Controls.Utils; namespace Avalonia.Controls { From a5f615d783aab4765769cf45f7b9ad94bce06875 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Fri, 7 Jul 2023 11:19:08 +0200 Subject: [PATCH 2/2] feat(ControlCatalog): Add sample how binding DataGridColumn Width --- .../Models/GDPdLengthConverter.cs | 31 +++++++++++++++++++ .../ControlCatalog/Pages/DataGridPage.xaml | 21 +++++++++++-- 2 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 samples/ControlCatalog/Models/GDPdLengthConverter.cs diff --git a/samples/ControlCatalog/Models/GDPdLengthConverter.cs b/samples/ControlCatalog/Models/GDPdLengthConverter.cs new file mode 100644 index 00000000000..034e6643054 --- /dev/null +++ b/samples/ControlCatalog/Models/GDPdLengthConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using Avalonia.Data.Converters; + +namespace ControlCatalog.Models; + +internal class GDPdLengthConverter : IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is double d) + { + return new Avalonia.Controls.DataGridLength(d,Avalonia.Controls.DataGridLengthUnitType.Pixel,d,d); + } + else if (value is decimal d2) + { + var dv =System.Convert.ToDouble(d2); + return new Avalonia.Controls.DataGridLength(dv, Avalonia.Controls.DataGridLengthUnitType.Pixel, dv, dv); + } + return value; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is Avalonia.Controls.DataGridLength width) + { + return System.Convert.ToDecimal(width.DisplayValue); + } + return value; + } +} diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml index 356834832df..88252091c4b 100644 --- a/samples/ControlCatalog/Pages/DataGridPage.xaml +++ b/samples/ControlCatalog/Pages/DataGridPage.xaml @@ -1,11 +1,14 @@ + + @@ -28,8 +31,18 @@ - + + + + + @@ -38,9 +51,11 @@ -