diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCell.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCell.cs index 5a9822c4..36531919 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCell.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCell.cs @@ -115,6 +115,12 @@ protected virtual void UpdateValue() { } + protected void RaiseCellValueChanged() + { + if (ColumnIndex != -1 && RowIndex != -1) + _treeDataGrid?.RaiseCellValueChanged(this, ColumnIndex, RowIndex); + } + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) { _treeDataGrid = this.FindLogicalAncestorOfType(); diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCheckBoxCell.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCheckBoxCell.cs index b6b1dea6..1af55752 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCheckBoxCell.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridCheckBoxCell.cs @@ -47,8 +47,12 @@ public bool? Value get => _value; set { - if (SetAndRaise(ValueProperty, ref _value, value) && Model is CheckBoxCell cell) - cell.Value = value; + if (SetAndRaise(ValueProperty, ref _value, value)) + { + if (Model is CheckBoxCell cell) + cell.Value = value; + RaiseCellValueChanged(); + } } } diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTemplateCell.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTemplateCell.cs index e216d8bf..fb9d07c8 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTemplateCell.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTemplateCell.cs @@ -33,10 +33,14 @@ public class TreeDataGridTemplateCell : TreeDataGridCell private IDataTemplate? _editingTemplate; private ContentPresenter? _editingContentPresenter; - public object? Content - { + public object? Content + { get => _content; - private set => SetAndRaise(ContentProperty, ref _content, value); + private set + { + if (SetAndRaise(ContentProperty, ref _content, value)) + RaiseCellValueChanged(); + } } public IDataTemplate? ContentTemplate diff --git a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTextCell.cs b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTextCell.cs index 510b35fe..7b50b511 100644 --- a/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTextCell.cs +++ b/src/Avalonia.Controls.TreeDataGrid/Primitives/TreeDataGridTextCell.cs @@ -55,8 +55,12 @@ public string? Value get => _value; set { - if (SetAndRaise(ValueProperty, ref _value, value) && Model is ITextCell cell && !_modelValueChanging) - cell.Text = _value; + if (SetAndRaise(ValueProperty, ref _value, value) && Model is ITextCell cell) + { + if (!_modelValueChanging) + cell.Text = _value; + RaiseCellValueChanged(); + } } } diff --git a/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs b/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs index dc9fe98b..1483405a 100644 --- a/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs +++ b/src/Avalonia.Controls.TreeDataGrid/TreeDataGrid.cs @@ -212,6 +212,7 @@ internal ITreeDataGridSelectionInteraction? SelectionInteraction public event EventHandler? CellClearing; public event EventHandler? CellPrepared; + public event EventHandler? CellValueChanged; public event EventHandler? RowClearing; public event EventHandler? RowPrepared; @@ -415,6 +416,17 @@ internal void RaiseCellPrepared(TreeDataGridCell cell, int columnIndex, int rowI } } + internal void RaiseCellValueChanged(TreeDataGridCell cell, int columnIndex, int rowIndex) + { + if (CellValueChanged is not null) + { + _cellArgs ??= new TreeDataGridCellEventArgs(); + _cellArgs.Update(cell, columnIndex, rowIndex); + CellValueChanged(this, _cellArgs); + _cellArgs.Update(null, -1, -1); + } + } + internal void RaiseRowClearing(TreeDataGridRow row, int rowIndex) { if (RowClearing is not null) diff --git a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs index 5afa60f7..b5af9467 100644 --- a/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs +++ b/tests/Avalonia.Controls.TreeDataGrid.Tests/TreeDataGridTests_Flat.cs @@ -288,6 +288,51 @@ public void Raises_CellClearing_CellPrepared_Events_On_Scroll() Assert.Equal(2, preparedRaised); } + [AvaloniaFact(Timeout = 10000)] + public void Raises_CellValueChanged_When_Model_Value_Changed() + { + var (target, items) = CreateTarget(); + var raised = 0; + + target.CellValueChanged += (s, e) => + { + Assert.Equal(1, e.ColumnIndex); + Assert.Equal(1, e.RowIndex); + ++raised; + }; + + items[1].Title = "Changed"; + + Assert.Equal(1, raised); + } + + [AvaloniaFact(Timeout = 10000)] + public void Does_Not_Raise_CellValueChanged_Events_On_Initial_Layout() + { + var (target, items) = CreateTarget(runLayout: false); + var raised = 0; + + target.CellValueChanged += (s, e) => ++raised; + + target.UpdateLayout(); + + Assert.Equal(0, raised); + } + + [AvaloniaFact(Timeout = 10000)] + public void Does_Not_Raise_CellValueChanged_Events_On_Scroll() + { + var (target, items) = CreateTarget(); + var raised = 0; + + target.CellValueChanged += (s, e) => ++raised; + + target.Scroll!.Offset = new Vector(0, 10); + Layout(target); + + Assert.Equal(0, raised); + } + [AvaloniaFact(Timeout = 10000)] public void Does_Not_Realize_Columns_Outside_Viewport() { @@ -785,8 +830,14 @@ private static TextColumnOptions MaxWidth(double max) private class Model : NotifyingBase { + private string? _title; + public int Id { get; set; } - public string? Title { get; set; } + public string? Title + { + get => _title; + set => RaiseAndSetIfChanged(ref _title, value); + } } } }