diff --git a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs index e2a067ac61..ccf1f3f77a 100644 --- a/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs +++ b/src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs @@ -17,7 +17,6 @@ namespace Avalonia.Controls /// public class DataGridCheckBoxColumn : DataGridBoundColumn { - private bool _beganEditWithKeyboard; private CheckBox _currentCheckBox; private DataGrid _owningGrid; @@ -153,23 +152,7 @@ namespace Avalonia.Controls { if (editingElement is CheckBox editingCheckBox) { - bool? uneditedValue = editingCheckBox.IsChecked; - bool editValue = false; - if(editingEventArgs is PointerPressedEventArgs args) - { - // Editing was triggered by a mouse click - Point position = args.GetPosition(editingCheckBox); - Rect rect = new Rect(0, 0, editingCheckBox.Bounds.Width, editingCheckBox.Bounds.Height); - editValue = rect.Contains(position); - } - else if (_beganEditWithKeyboard) - { - // Editing began by a user pressing spacebar - editValue = true; - _beganEditWithKeyboard = false; - } - - if (editValue) + void EditValue() { // User clicked the checkbox itself or pressed space, let's toggle the IsChecked value if (editingCheckBox.IsThreeState) @@ -192,6 +175,40 @@ namespace Avalonia.Controls editingCheckBox.IsChecked = !editingCheckBox.IsChecked; } } + + bool? uneditedValue = editingCheckBox.IsChecked; + if(editingEventArgs is PointerPressedEventArgs args) + { + void ProcessPointerArgs() + { + // Editing was triggered by a mouse click + Point position = args.GetPosition(editingCheckBox); + Rect rect = new Rect(0, 0, editingCheckBox.Bounds.Width, editingCheckBox.Bounds.Height); + if(rect.Contains(position)) + { + EditValue(); + } + } + + void OnLayoutUpdated(object sender, EventArgs e) + { + if(!editingCheckBox.Bounds.IsEmpty) + { + editingCheckBox.LayoutUpdated -= OnLayoutUpdated; + ProcessPointerArgs(); + } + } + + if(editingCheckBox.Bounds.IsEmpty) + { + editingCheckBox.LayoutUpdated += OnLayoutUpdated; + } + else + { + ProcessPointerArgs(); + } + } + return uneditedValue; } return false; @@ -284,13 +301,10 @@ namespace Avalonia.Controls CheckBox checkBox = GetCellContent(row) as CheckBox; if (checkBox == _currentCheckBox) { - _beganEditWithKeyboard = true; OwningGrid.BeginEdit(); - return; } } } - _beganEditWithKeyboard = false; } private void OwningGrid_LoadingRow(object sender, DataGridRowEventArgs e) diff --git a/src/Avalonia.Controls/DefinitionBase.cs b/src/Avalonia.Controls/DefinitionBase.cs index 38ebbe5bf9..3237f6f37b 100644 --- a/src/Avalonia.Controls/DefinitionBase.cs +++ b/src/Avalonia.Controls/DefinitionBase.cs @@ -662,31 +662,64 @@ namespace Avalonia.Controls { DefinitionBase definitionBase = _registry[i]; - if (sharedMinSizeChanged || definitionBase.LayoutWasUpdated) + // we'll set d.UseSharedMinimum to maintain the invariant: + // d.UseSharedMinimum iff d._minSize < this.MinSize + // i.e. iff d is not a "long-pole" definition. + // + // Measure/Arrange of d's Grid uses d._minSize for long-pole + // definitions, and max(d._minSize, shared size) for + // short-pole definitions. This distinction allows us to react + // to changes in "long-pole-ness" more efficiently and correctly, + // by avoiding remeasures when a long-pole definition changes. + bool useSharedMinimum = !MathUtilities.AreClose(definitionBase._minSize, sharedMinSize); + + // before doing that, determine whether d's Grid needs to be remeasured. + // It's important _not_ to remeasure if the last measure is still + // valid, otherwise infinite loops are possible + bool measureIsValid; + + if(!definitionBase.UseSharedMinimum) { - // if definition's min size is different, then need to re-measure - if (!MathUtilities.AreClose(sharedMinSize, definitionBase.MinSize)) - { - Grid parentGrid = (Grid)definitionBase.Parent; - parentGrid.InvalidateMeasure(); - definitionBase.UseSharedMinimum = true; - } - else - { - definitionBase.UseSharedMinimum = false; - - // if measure is valid then also need to check arrange. - // Note: definitionBase.SizeCache is volatile but at this point - // it contains up-to-date final size - if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) - { - Grid parentGrid = (Grid)definitionBase.Parent; - parentGrid.InvalidateArrange(); - } - } + // d was a long-pole. measure is valid iff it's still a long-pole, + // since previous measure didn't use shared size. + measureIsValid = !useSharedMinimum; + } + else if(useSharedMinimum) + { + // d was a short-pole, and still is. measure is valid + // iff the shared size didn't change + measureIsValid = !sharedMinSizeChanged; + } + else + { + // d was a short-pole, but is now a long-pole. This can + // happen in several ways: + // a. d's minSize increased to or past the old shared size + // b. other long-pole definitions decreased, leaving + // d as the new winner + // In the former case, the measure is valid - it used + // d's new larger minSize. In the latter case, the + // measure is invalid - it used the old shared size, + // which is larger than d's (possibly changed) minSize + measureIsValid = (definitionBase.LayoutWasUpdated && + MathUtilities.GreaterThanOrClose(definitionBase._minSize, this.MinSize)); + } - definitionBase.LayoutWasUpdated = false; + if(!measureIsValid) + { + definitionBase.Parent.InvalidateMeasure(); } + else if (!MathUtilities.AreClose(sharedMinSize, definitionBase.SizeCache)) + { + // if measure is valid then also need to check arrange. + // Note: definitionBase.SizeCache is volatile but at this point + // it contains up-to-date final size + definitionBase.Parent.InvalidateArrange(); + } + + // now we can restore the invariant, and clear the layout flag + definitionBase.UseSharedMinimum = useSharedMinimum; + definitionBase.LayoutWasUpdated = false; } _minSize = sharedMinSize;