From 368de7e79f5bb8d5870b4aea530633a8534a70d5 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Jul 2023 13:18:24 +0000 Subject: [PATCH 1/9] clean up; apply only changed text to editable --- .../Avalonia.Android/AndroidInputMethod.cs | 32 +++++++++++++++++-- .../Platform/SkiaPlatform/TopLevelImpl.cs | 16 ++++------ 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index ea1958efa6..dea029aaf2 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -114,16 +114,42 @@ namespace Avalonia.Android private void _client_SurroundingTextChanged(object sender, EventArgs e) { var surroundingText = _client.SurroundingText ?? ""; + var editableText = _inputConnection.EditableWrapper.ToString(); - _inputConnection.EditableWrapper.IgnoreChange = true; + if (editableText != surroundingText) + { + _inputConnection.EditableWrapper.IgnoreChange = true; + + var diff = GetDiff(); - _inputConnection.Editable.Replace(0, _inputConnection.Editable.Length(), surroundingText); + _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff); - _inputConnection.EditableWrapper.IgnoreChange = false; + _inputConnection.EditableWrapper.IgnoreChange = false; + } var selection = Client.Selection; _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End); + + (int index, string diff) GetDiff() + { + int index = 0; + + var longerLength = Math.Max(surroundingText.Length, editableText.Length); + + for(int i = 0; i < longerLength; i++) + { + if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i]) + { + index = i; + break; + } + } + + var diffString = surroundingText.Substring(index, surroundingText.Length - index); + + return (index, diffString); + } } public void SetCursorRect(Rect rect) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5d82d099c9..5492742a4c 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -447,6 +447,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform { private readonly AvaloniaInputConnection _inputConnection; + public event EventHandler SelectionChanged; + public EditableWrapper(AvaloniaInputConnection inputConnection) { _inputConnection = inputConnection; @@ -458,8 +460,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { if (!IgnoreChange && start != end) { - var text = tb.SubSequence(0, tb.Length()); - SelectSurroundingTextForDeletion(start, end); } @@ -470,8 +470,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { if (!IgnoreChange && start != end) { - var text = tb.SubSequence(tbstart, tbend); - SelectSurroundingTextForDeletion(start, end); } @@ -482,6 +480,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _inputConnection.InputMethod.Client.Selection = new TextSelection(start, end); } + + public override void SetSpan(Java.Lang.Object what, int start, int end, [GeneratedEnum] SpanTypes flags) + { + base.SetSpan(what, start, end, flags); + } } internal class AvaloniaInputConnection : BaseInputConnection @@ -548,11 +551,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) { - if(_composingRegion != null) - { - _inputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End); - } - _toplevel.TextInput(committedText); _composingRegion = null; From 117ea16f0ba8d0099cd96875336f82a9e1082030 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Mon, 17 Jul 2023 17:24:19 +0000 Subject: [PATCH 2/9] remove redundant selection update --- src/Android/Avalonia.Android/AndroidInputMethod.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index dea029aaf2..1404adbae0 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -127,10 +127,6 @@ namespace Avalonia.Android _inputConnection.EditableWrapper.IgnoreChange = false; } - var selection = Client.Selection; - - _imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End); - (int index, string diff) GetDiff() { int index = 0; From 24e2321bf1c15dec876fbc67f176d00c11953bf0 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 19 Jul 2023 08:04:22 +0000 Subject: [PATCH 3/9] android - use batch edits to defer updates to input manager --- .../Avalonia.Android/AndroidInputMethod.cs | 29 ++++++- .../Platform/SkiaPlatform/TopLevelImpl.cs | 85 +++++++++++-------- 2 files changed, 76 insertions(+), 38 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 1404adbae0..60f56805d1 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -18,6 +18,8 @@ namespace Avalonia.Android public bool IsActive { get; } public InputMethodManager IMM { get; } + + void OnBatchEditedEnded(); } enum CustomImeFlags @@ -103,6 +105,13 @@ namespace Avalonia.Android } private void _client_SelectionChanged(object sender, EventArgs e) + { + if (_inputConnection.IsInBatchEdit) + return; + OnSelectionChanged(); + } + + private void OnSelectionChanged() { var selection = Client.Selection; @@ -112,6 +121,22 @@ namespace Avalonia.Android } private void _client_SurroundingTextChanged(object sender, EventArgs e) + { + if (_inputConnection.IsInBatchEdit) + return; + OnSurroundingTextChanged(); + } + + public void OnBatchEditedEnded() + { + if (_inputConnection.IsInBatchEdit) + return; + + OnSurroundingTextChanged(); + OnSelectionChanged(); + } + + private void OnSurroundingTextChanged() { var surroundingText = _client.SurroundingText ?? ""; var editableText = _inputConnection.EditableWrapper.ToString(); @@ -133,7 +158,7 @@ namespace Avalonia.Android var longerLength = Math.Max(surroundingText.Length, editableText.Length); - for(int i = 0; i < longerLength; i++) + for (int i = 0; i < longerLength; i++) { if (surroundingText.Length == i || editableText.Length == i || surroundingText[i] != editableText[i]) { @@ -142,7 +167,7 @@ namespace Avalonia.Android } } - var diffString = surroundingText.Substring(index, surroundingText.Length - index); + var diffString = surroundingText.Substring(index, surroundingText.Length - index); return (index, diffString); } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 5492742a4c..26b8837f76 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Runtime.Versioning; +using System.Threading; using Android.App; using Android.Content; using Android.Graphics; @@ -454,6 +455,9 @@ namespace Avalonia.Android.Platform.SkiaPlatform _inputConnection = inputConnection; } + public TextSelection CurrentSelection => new TextSelection(Selection.GetSelectionStart(this), Selection.GetSelectionEnd(this)); + public TextSelection CurrentComposition => new TextSelection(BaseInputConnection.GetComposingSpanStart(this), BaseInputConnection.GetComposingSpanEnd(this)); + public bool IgnoreChange { get; set; } public override IEditable Replace(int start, int end, ICharSequence tb) @@ -480,11 +484,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _inputConnection.InputMethod.Client.Selection = new TextSelection(start, end); } - - public override void SetSpan(Java.Lang.Object what, int start, int end, [GeneratedEnum] SpanTypes flags) - { - base.SetSpan(what, start, end, flags); - } } internal class AvaloniaInputConnection : BaseInputConnection @@ -493,8 +492,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform private readonly IAndroidInputMethod _inputMethod; private readonly EditableWrapper _editable; private bool _commitInProgress; - private (int Start, int End)? _composingRegion; - private TextSelection _selection; + private int _batchLevel = 0; public AvaloniaInputConnection(TopLevelImpl toplevel, IAndroidInputMethod inputMethod) : base(inputMethod.View, true) { @@ -513,36 +511,64 @@ namespace Avalonia.Android.Platform.SkiaPlatform public TopLevelImpl Toplevel => _toplevel; + public bool IsInBatchEdit => _batchLevel > 0; + public override bool SetComposingRegion(int start, int end) { - _composingRegion = new(start, end); - return base.SetComposingRegion(start, end); } public override bool SetComposingText(ICharSequence text, int newCursorPosition) { - if(_composingRegion != null) + BeginBatchEdit(); + _editable.IgnoreChange = true; + + try { - // Select the composing region. - InputMethod.Client.Selection = new TextSelection(_composingRegion.Value.Start, _composingRegion.Value.End); - } - var compositionText = text.SubSequence(0, text.Length()); + if (_editable.CurrentComposition.Start > -1) + { + // Select the composing region. + InputMethod.Client.Selection = new TextSelection(_editable.CurrentComposition.Start, _editable.CurrentComposition.End); + } + var compositionText = text.SubSequence(0, text.Length()); + + if (_inputMethod.IsActive && !_commitInProgress) + { + if (string.IsNullOrEmpty(compositionText)) + _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); - if (_inputMethod.IsActive && !_commitInProgress) + else + _toplevel.TextInput(compositionText); + } + base.SetComposingText(text, newCursorPosition); + } + finally { - if (string.IsNullOrEmpty(compositionText)) - _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); + _editable.IgnoreChange = false; - else - _toplevel.TextInput(compositionText); + EndBatchEdit(); } return true; } + public override bool BeginBatchEdit() + { + _batchLevel = Interlocked.Increment(ref _batchLevel); + return base.BeginBatchEdit(); + } + + public override bool EndBatchEdit() + { + _batchLevel = Interlocked.Decrement(ref _batchLevel); + + _inputMethod.OnBatchEditedEnded(); + return base.EndBatchEdit(); + } + public override bool CommitText(ICharSequence text, int newCursorPosition) { + BeginBatchEdit(); _commitInProgress = true; var ret = base.CommitText(text, newCursorPosition); @@ -551,22 +577,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) { - _toplevel.TextInput(committedText); - - _composingRegion = null; + _toplevel.TextInput(committedText); } _commitInProgress = false; + EndBatchEdit(); return ret; } - public override bool FinishComposingText() - { - _composingRegion = null; - return base.FinishComposingText(); - } - public override bool DeleteSurroundingText(int beforeLength, int afterLength) { if (InputMethod.IsActive) @@ -577,7 +596,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform if (InputMethod.IsActive) { - var selection = _selection; + var selection = _editable.CurrentSelection; InputMethod.Client.Selection = new TextSelection(selection.Start - beforeLength, selection.End + afterLength); @@ -589,12 +608,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform return result; } - public override bool SetSelection(int start, int end) - { - _selection = new TextSelection(start, end); - return base.SetSelection(start, end); - } - public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode) { switch (actionCode) @@ -628,7 +641,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform return null; } - var selection = _selection; + var selection = _editable.CurrentSelection; ExtractedText extract = new ExtractedText { From f762be4ff6deaf4456d80a7bf44c69d1cdc871a8 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 19 Jul 2023 12:39:01 +0000 Subject: [PATCH 4/9] only handle deletion in surrounding text on the client side --- .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 26b8837f76..fa01cb83e3 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -583,7 +583,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform _commitInProgress = false; EndBatchEdit(); - return ret; + return true; } public override bool DeleteSurroundingText(int beforeLength, int afterLength) @@ -592,7 +592,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { EditableWrapper.IgnoreChange = true; } - var result = base.DeleteSurroundingText(beforeLength, afterLength); if (InputMethod.IsActive) { @@ -605,7 +604,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform EditableWrapper.IgnoreChange = true; } - return result; + return true; } public override bool PerformEditorAction([GeneratedEnum] ImeAction actionCode) From fde8744175ee25ba5e231fc58a3064e94f03face Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Wed, 19 Jul 2023 15:09:53 +0000 Subject: [PATCH 5/9] delete selection when committext has null text --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index fa01cb83e3..3b2a3657b3 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -575,10 +575,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform var committedText = text.SubSequence(0, text.Length()); - if (_inputMethod.IsActive && !string.IsNullOrEmpty(committedText)) - { - _toplevel.TextInput(committedText); - } + if (_inputMethod.IsActive) + if (string.IsNullOrEmpty(committedText)) + _inputMethod.View.DispatchKeyEvent(new KeyEvent(KeyEventActions.Down, Keycode.ForwardDel)); + else + _toplevel.TextInput(committedText); _commitInProgress = false; EndBatchEdit(); From a54163f1c10fcb5dbaabd8a9b3eae379f5f53a81 Mon Sep 17 00:00:00 2001 From: Emmanuel Hansen Date: Thu, 20 Jul 2023 13:10:50 +0000 Subject: [PATCH 6/9] select current composing region if active when commiting and ensure selection is set when surrounding text changes --- src/Android/Avalonia.Android/AndroidInputMethod.cs | 7 +++++++ .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 60f56805d1..b6c1154455 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -150,6 +150,13 @@ namespace Avalonia.Android _inputConnection.Editable.Replace(diff.index, editableText.Length, diff.diff); _inputConnection.EditableWrapper.IgnoreChange = false; + + if(diff.index == 0) + { + var selection = _client.Selection; + _client.Selection = new TextSelection(selection.Start, 0); + _client.Selection = selection; + } } (int index, string diff) GetDiff() diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 3b2a3657b3..a6e8d10777 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -571,8 +571,15 @@ namespace Avalonia.Android.Platform.SkiaPlatform BeginBatchEdit(); _commitInProgress = true; + var composingRegion = _editable.CurrentComposition; + var ret = base.CommitText(text, newCursorPosition); + if(composingRegion.Start != -1) + { + InputMethod.Client.Selection = composingRegion; + } + var committedText = text.SubSequence(0, text.Length()); if (_inputMethod.IsActive) From 99f1225f57372094353870ceb60837da7cabf73b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 20 Jul 2023 18:28:41 +0200 Subject: [PATCH 7/9] Prevent NRE --- src/Android/Avalonia.Android/AndroidInputMethod.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index b6c1154455..7d5130cf5d 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -138,6 +138,11 @@ namespace Avalonia.Android private void OnSurroundingTextChanged() { + if(_client is null) + { + return; + } + var surroundingText = _client.SurroundingText ?? ""; var editableText = _inputConnection.EditableWrapper.ToString(); From ed6331cd3dfde3036da361cdddc75e5031228e30 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 3 Nov 2021 15:41:53 +0100 Subject: [PATCH 8/9] 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 a5695afeb7..a77ac985ac 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumn.cs @@ -7,7 +7,6 @@ using Avalonia.Data; using Avalonia.Interactivity; using Avalonia.VisualTree; using Avalonia.Collections; -using Avalonia.Utilities; using System; using System.ComponentModel; using System.Linq; @@ -24,8 +23,6 @@ namespace Avalonia.Controls { 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 @@ namespace Avalonia.Controls private IBinding _clipboardContentBinding; private ControlTheme _cellTheme; private Classes _cellStyleClasses; + private bool _setWidthInternalNoCallback; /// /// Initializes a new instance of the class. @@ -214,6 +212,36 @@ namespace Avalonia.Controls 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 @@ namespace Avalonia.Controls } } + 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 @@ namespace Avalonia.Controls /// 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 @@ namespace Avalonia.Controls 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 @@ namespace Avalonia.Controls } else { - desiredValue = ActualMinWidth; + desiredValue = target.ActualMinWidth; } } @@ -862,7 +872,7 @@ namespace Avalonia.Controls { 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 @@ namespace Avalonia.Controls }; 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 @@ namespace Avalonia.Controls /// internal void EnsureWidth() { - SetWidthInternalNoCallback(CoerceWidth(Width)); + SetWidthInternalNoCallback(CoerceWidth(this, Width)); } internal Control GenerateElementInternal(DataGridCell cell, object dataItem) @@ -931,17 +941,17 @@ namespace Avalonia.Controls /// 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 @@ namespace Avalonia.Controls // 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 @@ namespace Avalonia.Controls // 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 @@ namespace Avalonia.Controls } } - 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 @@ namespace Avalonia.Controls /// 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 @@ namespace Avalonia.Controls && 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 0755864c25..28e8a0ed5e 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs @@ -785,7 +785,9 @@ namespace Avalonia.Controls 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 4056b78bfe..8dd5bd262b 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridColumns.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridColumns.cs @@ -91,7 +91,10 @@ namespace Avalonia.Controls // 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 @@ namespace Avalonia.Controls { 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 @@ namespace Avalonia.Controls 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 @@ namespace Avalonia.Controls { // 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 @@ namespace Avalonia.Controls 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 @@ namespace Avalonia.Controls { // 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 a193352f52..9bff8b2cf5 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridLength.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridLength.cs @@ -7,7 +7,6 @@ using Avalonia.Utilities; 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 9/9] 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 0000000000..034e664305 --- /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 356834832d..88252091c4 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 @@ -