diff --git a/Maui.DataGrid/DataGrid.xaml.cs b/Maui.DataGrid/DataGrid.xaml.cs index 1abd19b..ea2141f 100644 --- a/Maui.DataGrid/DataGrid.xaml.cs +++ b/Maui.DataGrid/DataGrid.xaml.cs @@ -1395,7 +1395,7 @@ private DataGridCell CreateHeaderCell(DataGridColumn column) Grid.SetColumn(column.SortingIconContainer, 1); } - return new DataGridCell(cellContent, HeaderBackground); + return new DataGridCell(cellContent, HeaderBackground, column, false); } private void InitHeaderView() @@ -1407,8 +1407,6 @@ private void InitHeaderView() SetColumnsBindingContext(); - _headerView.Children.Clear(); - if (Columns == null) { _headerView.ColumnDefinitions.Clear(); @@ -1445,7 +1443,24 @@ private void InitHeaderView() col.HeaderView.UpdateBindings(this, HeaderBordersVisible); Grid.SetColumn(col.HeaderView, i); - _headerView.Children.Add(col.HeaderView); + + if (_headerView.Children.TryGetItem(i, out var existingCell)) + { + if (existingCell is not DataGridCell cell) + { + throw new InvalidDataException($"{nameof(DataGridRow)} should only contain {nameof(DataGridCell)}s"); + } + + if (cell.Column != col) + { + _headerView.Children[i] = col.HeaderView; + } + } + else + { + _headerView.Children.Add(col.HeaderView); + } + } // Remove extra columns diff --git a/Maui.DataGrid/DataGridCell.cs b/Maui.DataGrid/DataGridCell.cs index 7446090..3725760 100644 --- a/Maui.DataGrid/DataGridCell.cs +++ b/Maui.DataGrid/DataGridCell.cs @@ -7,7 +7,8 @@ namespace Maui.DataGrid; /// internal sealed class DataGridCell : Grid { - internal DataGridCell(View cellContent, Color? backgroundColor) + internal DataGridCell(View cellContent, Color? backgroundColor, DataGridColumn column, bool isEditing) + { var colorfulCellContent = new ContentView { @@ -15,9 +16,19 @@ internal DataGridCell(View cellContent, Color? backgroundColor) Content = cellContent, }; + Content = cellContent; + Column = column; + IsEditing = isEditing; + Children.Add(colorfulCellContent); } + public View Content { get; } + + public DataGridColumn Column { get; } + public bool IsEditing { get; } + + internal void UpdateBindings(DataGrid dataGrid, bool bordersVisible = true) { // The DataGridCell is a grid, and the padding constitutes the cell's border diff --git a/Maui.DataGrid/DataGridRow.cs b/Maui.DataGrid/DataGridRow.cs index 3ae088f..d193b52 100644 --- a/Maui.DataGrid/DataGridRow.cs +++ b/Maui.DataGrid/DataGridRow.cs @@ -1,184 +1,213 @@ -namespace Maui.DataGrid; - -using Extensions; -using Microsoft.Maui.Controls; - -internal sealed class DataGridRow : Grid -{ - private delegate bool ParserDelegate(string value); - - #region Fields - - private Color? _bgColor; - private Color? _textColor; - private bool _hasSelected; - - #endregion Fields - - #region Properties - - public DataGrid DataGrid - { - get => (DataGrid)GetValue(DataGridProperty); - set => SetValue(DataGridProperty, value); - } - - public object RowToEdit - { - get => GetValue(RowToEditProperty); - set => SetValue(RowToEditProperty, value); - } - - #endregion Properties - +namespace Maui.DataGrid; + +using Extensions; +using Microsoft.Maui.Controls; + +internal sealed class DataGridRow : Grid +{ + private delegate bool ParserDelegate(string value); + + #region Fields + + private Color? _bgColor; + private Color? _textColor; + private bool _hasSelected; + + #endregion Fields + + #region Properties + + public DataGrid DataGrid + { + get => (DataGrid)GetValue(DataGridProperty); + set => SetValue(DataGridProperty, value); + } + + public object RowToEdit + { + get => GetValue(RowToEditProperty); + set => SetValue(RowToEditProperty, value); + } + + #endregion Properties + #region Bindable Properties - - public static readonly BindableProperty DataGridProperty = - BindablePropertyExtensions.Create(null, BindingMode.OneTime, - propertyChanged: (b, o, n) => - { - var self = (DataGridRow)b; - - if (o is DataGrid oldDataGrid) - { - oldDataGrid.ItemSelected -= self.DataGrid_ItemSelected; - oldDataGrid.Columns.CollectionChanged -= self.OnColumnsChanged; - - foreach (var column in oldDataGrid.Columns) - { - column.VisibilityChanged -= self.OnVisibilityChanged; - } - } - - if (n is DataGrid newDataGrid) - { - newDataGrid.ItemSelected += self.DataGrid_ItemSelected; - newDataGrid.Columns.CollectionChanged += self.OnColumnsChanged; - - foreach (var column in newDataGrid.Columns) - { - column.VisibilityChanged += self.OnVisibilityChanged; - } - } - }); - - public static readonly BindableProperty RowToEditProperty = - BindablePropertyExtensions.Create(null, BindingMode.OneWay, - propertyChanged: (b, o, n) => - { - if (o == n || b is not DataGridRow row) - { - return; - } - - if (o == row.BindingContext || n == row.BindingContext) - { - row.CreateView(); - } + + public static readonly BindableProperty DataGridProperty = + BindablePropertyExtensions.Create(null, BindingMode.OneTime, + propertyChanged: (b, o, n) => + { + var self = (DataGridRow)b; + + if (o is DataGrid oldDataGrid) + { + oldDataGrid.ItemSelected -= self.DataGrid_ItemSelected; + oldDataGrid.Columns.CollectionChanged -= self.OnColumnsChanged; + + foreach (var column in oldDataGrid.Columns) + { + column.VisibilityChanged -= self.OnVisibilityChanged; + } + } + + if (n is DataGrid newDataGrid) + { + newDataGrid.ItemSelected += self.DataGrid_ItemSelected; + newDataGrid.Columns.CollectionChanged += self.OnColumnsChanged; + + foreach (var column in newDataGrid.Columns) + { + column.VisibilityChanged += self.OnVisibilityChanged; + } + } }); - - #endregion Bindable Properties - - #region Methods - - private void CreateView() - { - Children.Clear(); - - UpdateColors(); - - if (DataGrid.Columns == null || DataGrid.Columns.Count == 0) - { - ColumnDefinitions.Clear(); - return; - } - - for (var i = 0; i < DataGrid.Columns.Count; i++) - { - var col = DataGrid.Columns[i]; - - // Add or update columns as needed - if (i > ColumnDefinitions.Count - 1) - { - ColumnDefinitions.Add(col.ColumnDefinition); - } - else if (ColumnDefinitions[i] != col.ColumnDefinition) - { - ColumnDefinitions[i] = col.ColumnDefinition; - } - - if (!col.IsVisible) - { - continue; + + public static readonly BindableProperty RowToEditProperty = + BindablePropertyExtensions.Create(null, BindingMode.OneWay, + propertyChanged: (b, o, n) => + { + if (o == n || b is not DataGridRow row) + { + return; + } + + if (o == row.BindingContext || n == row.BindingContext) + { + row.CreateView(); + } + }); + + #endregion Bindable Properties + + #region Methods + + private void CreateView() + { + UpdateColors(); + + if (DataGrid.Columns == null || DataGrid.Columns.Count == 0) + { + ColumnDefinitions.Clear(); + return; + } + + for (var i = 0; i < DataGrid.Columns.Count; i++) + { + var col = DataGrid.Columns[i]; + + // Add or update columns as needed + if (i > ColumnDefinitions.Count - 1) + { + ColumnDefinitions.Add(col.ColumnDefinition); + } + else if (ColumnDefinitions[i] != col.ColumnDefinition) + { + ColumnDefinitions[i] = col.ColumnDefinition; } - - var cellContent = CreateCell(col); - - var dataGridCell = new DataGridCell(cellContent, _bgColor); - - dataGridCell.UpdateBindings(DataGrid); - - SetColumn((BindableObject)dataGridCell, i); - Children.Add(dataGridCell); - } - - // Remove extra columns - for (var i = ColumnDefinitions.Count - 1; i > DataGrid.Columns.Count - 1; i--) - { - ColumnDefinitions.RemoveAt(i); - } - } - - private View CreateCell(DataGridColumn col) - { - if (RowToEdit == BindingContext) - { - return CreateEditCell(col); - } - - return CreateViewCell(col); - } - - private View CreateViewCell(DataGridColumn col) - { - View cell; - - if (col.CellTemplate != null) - { - cell = (View)col.CellTemplate.CreateContent(); - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - cell.SetBinding(BindingContextProperty, - new Binding(col.PropertyName, source: BindingContext)); - } - } - else - { - cell = new Label - { - TextColor = _textColor, - VerticalTextAlignment = col.VerticalTextAlignment, - HorizontalTextAlignment = col.HorizontalTextAlignment, - LineBreakMode = col.LineBreakMode, - FontSize = DataGrid.FontSize, - FontFamily = DataGrid.FontFamily - }; - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - cell.SetBinding(Label.TextProperty, - new Binding(col.PropertyName, stringFormat: col.StringFormat, source: BindingContext)); - } - } - - return cell; - } - - private View CreateEditCell(DataGridColumn col) - { - var cell = GenerateTemplatedEditCell(col); - + + if (!col.IsVisible) + { + continue; + } + + if (Children.TryGetItem(i, out var existingChild)) + { + if (existingChild is not DataGridCell existingCell) + { + throw new InvalidDataException($"{nameof(DataGridRow)} should only contain {nameof(DataGridCell)}s"); + } + + var isEditing = RowToEdit == BindingContext; + + if (existingCell.Column != col || existingCell.IsEditing != isEditing) + { + Children[i] = GenerateCellForColumn(col, i); + } + } + else + { + var newCell = GenerateCellForColumn(col, i); + Children.Add(newCell); + } + } + + // Remove extra columns + for (var i = ColumnDefinitions.Count - 1; i > DataGrid.Columns.Count - 1; i--) + { + ColumnDefinitions.RemoveAt(i); + } + } + + private DataGridCell GenerateCellForColumn(DataGridColumn col, int columnIndex) + { + var dataGridCell = CreateCell(col); + + dataGridCell.UpdateBindings(DataGrid); + + SetColumn((BindableObject)dataGridCell, columnIndex); + + return dataGridCell; + } + + private DataGridCell CreateCell(DataGridColumn col) + { + View cellContent; + + var isEditing = RowToEdit == BindingContext; + + if (isEditing) + { + cellContent = CreateEditCell(col); + } + else + { + cellContent = CreateViewCell(col); + + } + + return new DataGridCell(cellContent, _bgColor, col, isEditing); + } + + private View CreateViewCell(DataGridColumn col) + { + View cell; + + if (col.CellTemplate != null) + { + cell = (View)col.CellTemplate.CreateContent(); + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) + { + cell.SetBinding(BindingContextProperty, + new Binding(col.PropertyName, source: BindingContext)); + } + } + else + { + cell = new Label + { + TextColor = _textColor, + VerticalTextAlignment = col.VerticalTextAlignment, + HorizontalTextAlignment = col.HorizontalTextAlignment, + LineBreakMode = col.LineBreakMode, + FontSize = DataGrid.FontSize, + FontFamily = DataGrid.FontFamily + }; + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) + { + cell.SetBinding(Label.TextProperty, + new Binding(col.PropertyName, stringFormat: col.StringFormat, source: BindingContext)); + } + } + + return cell; + } + + private View CreateEditCell(DataGridColumn col) + { + var cell = GenerateTemplatedEditCell(col); + return cell ?? CreateDefaultEditCell(col); } @@ -203,168 +232,168 @@ private View CreateDefaultEditCell(DataGridColumn col) }; } - private View? GenerateTemplatedEditCell(DataGridColumn col) - { - if (col.EditCellTemplate == null) - { - return null; - } - - var cell = (View)col.EditCellTemplate.CreateContent(); - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - cell.SetBinding(BindingContextProperty, - new Binding(col.PropertyName, source: BindingContext)); - } - - return cell; - } - - private Entry GenerateTextEditCell(DataGridColumn col) - { - var entry = new Entry - { - TextColor = _textColor, - VerticalTextAlignment = col.VerticalTextAlignment, - HorizontalTextAlignment = col.HorizontalTextAlignment, - FontSize = DataGrid.FontSize, - FontFamily = DataGrid.FontFamily - }; - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - entry.SetBinding(Entry.TextProperty, - new Binding(col.PropertyName, BindingMode.TwoWay, stringFormat: col.StringFormat, source: BindingContext)); - } - - return entry; - } - - private CheckBox GenerateBooleanEditCell(DataGridColumn col) - { - var checkBox = new CheckBox - { - Color = _textColor, - BackgroundColor = _bgColor, - }; - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - checkBox.SetBinding(CheckBox.IsCheckedProperty, - new Binding(col.PropertyName, BindingMode.TwoWay, source: BindingContext)); - } - - return checkBox; - } - - private Entry GenerateNumericEditCell(DataGridColumn col, ParserDelegate parserDelegate) - { - var entry = new Entry - { - TextColor = _textColor, - VerticalTextAlignment = col.VerticalTextAlignment, - HorizontalTextAlignment = col.HorizontalTextAlignment, - FontSize = DataGrid.FontSize, + private View? GenerateTemplatedEditCell(DataGridColumn col) + { + if (col.EditCellTemplate == null) + { + return null; + } + + var cell = (View)col.EditCellTemplate.CreateContent(); + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) + { + cell.SetBinding(BindingContextProperty, + new Binding(col.PropertyName, source: BindingContext)); + } + + return cell; + } + + private Entry GenerateTextEditCell(DataGridColumn col) + { + var entry = new Entry + { + TextColor = _textColor, + VerticalTextAlignment = col.VerticalTextAlignment, + HorizontalTextAlignment = col.HorizontalTextAlignment, + FontSize = DataGrid.FontSize, + FontFamily = DataGrid.FontFamily + }; + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) + { + entry.SetBinding(Entry.TextProperty, + new Binding(col.PropertyName, BindingMode.TwoWay, stringFormat: col.StringFormat, source: BindingContext)); + } + + return entry; + } + + private CheckBox GenerateBooleanEditCell(DataGridColumn col) + { + var checkBox = new CheckBox + { + Color = _textColor, + BackgroundColor = _bgColor, + }; + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) + { + checkBox.SetBinding(CheckBox.IsCheckedProperty, + new Binding(col.PropertyName, BindingMode.TwoWay, source: BindingContext)); + } + + return checkBox; + } + + private Entry GenerateNumericEditCell(DataGridColumn col, ParserDelegate parserDelegate) + { + var entry = new Entry + { + TextColor = _textColor, + VerticalTextAlignment = col.VerticalTextAlignment, + HorizontalTextAlignment = col.HorizontalTextAlignment, + FontSize = DataGrid.FontSize, FontFamily = DataGrid.FontFamily, - Keyboard = Keyboard.Numeric - }; - - entry.TextChanged += (s, e) => - { - if (!string.IsNullOrEmpty(e.NewTextValue) && !parserDelegate(e.NewTextValue)) - { - ((Entry)s!).Text = e.OldTextValue; - } - }; - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - entry.SetBinding(Entry.TextProperty, - new Binding(col.PropertyName, BindingMode.TwoWay, source: BindingContext)); - } - - return entry; - } - - private DatePicker GenerateDateTimeEditCell(DataGridColumn col) - { - var datePicker = new DatePicker - { - TextColor = _textColor, - }; - - if (!string.IsNullOrWhiteSpace(col.PropertyName)) - { - datePicker.SetBinding(DatePicker.DateProperty, - new Binding(col.PropertyName, BindingMode.TwoWay, source: BindingContext)); - } - - return datePicker; - } - - private void UpdateColors() - { - _hasSelected = DataGrid.SelectedItem == BindingContext || DataGrid.SelectedItems.Contains(BindingContext); - var rowIndex = DataGrid.InternalItems?.IndexOf(BindingContext) ?? -1; - - if (rowIndex < 0) - { - return; - } - - _bgColor = DataGrid.SelectionMode != SelectionMode.None && _hasSelected - ? DataGrid.ActiveRowColor - : DataGrid.RowsBackgroundColorPalette.GetColor(rowIndex, BindingContext); - _textColor = DataGrid.RowsTextColorPalette.GetColor(rowIndex, BindingContext); - - foreach (var cell in Children.OfType()) + Keyboard = Keyboard.Numeric + }; + + entry.TextChanged += (s, e) => + { + if (!string.IsNullOrEmpty(e.NewTextValue) && !parserDelegate(e.NewTextValue)) + { + ((Entry)s!).Text = e.OldTextValue; + } + }; + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) { - cell.UpdateCellColors(_bgColor, _textColor); - } - } - - /// - protected override void OnBindingContextChanged() - { - base.OnBindingContextChanged(); - CreateView(); - } - - /// - protected override void OnParentSet() - { - base.OnParentSet(); - - if (Parent == null) - { - DataGrid.ItemSelected -= DataGrid_ItemSelected; - DataGrid.Columns.CollectionChanged -= OnColumnsChanged; - - foreach (var column in DataGrid.Columns) - { - column.VisibilityChanged -= OnVisibilityChanged; - } - } - } - - private void OnColumnsChanged(object? sender, EventArgs e) - { - CreateView(); - } - - private void OnVisibilityChanged(object? sender, EventArgs e) - { - CreateView(); - } - - private void DataGrid_ItemSelected(object? sender, SelectionChangedEventArgs e) - { - if (_hasSelected || (e.CurrentSelection.Count > 0 && e.CurrentSelection.Any(s => s == BindingContext))) - { - UpdateColors(); - } - } - - #endregion Methods -} + entry.SetBinding(Entry.TextProperty, + new Binding(col.PropertyName, BindingMode.TwoWay, source: BindingContext)); + } + + return entry; + } + + private DatePicker GenerateDateTimeEditCell(DataGridColumn col) + { + var datePicker = new DatePicker + { + TextColor = _textColor, + }; + + if (!string.IsNullOrWhiteSpace(col.PropertyName)) + { + datePicker.SetBinding(DatePicker.DateProperty, + new Binding(col.PropertyName, BindingMode.TwoWay, source: BindingContext)); + } + + return datePicker; + } + + private void UpdateColors() + { + _hasSelected = DataGrid.SelectedItem == BindingContext || DataGrid.SelectedItems.Contains(BindingContext); + var rowIndex = DataGrid.InternalItems?.IndexOf(BindingContext) ?? -1; + + if (rowIndex < 0) + { + return; + } + + _bgColor = DataGrid.SelectionMode != SelectionMode.None && _hasSelected + ? DataGrid.ActiveRowColor + : DataGrid.RowsBackgroundColorPalette.GetColor(rowIndex, BindingContext); + _textColor = DataGrid.RowsTextColorPalette.GetColor(rowIndex, BindingContext); + + foreach (var cell in Children.OfType()) + { + cell.UpdateCellColors(_bgColor, _textColor); + } + } + + /// + protected override void OnBindingContextChanged() + { + base.OnBindingContextChanged(); + CreateView(); + } + + /// + protected override void OnParentSet() + { + base.OnParentSet(); + + if (Parent == null) + { + DataGrid.ItemSelected -= DataGrid_ItemSelected; + DataGrid.Columns.CollectionChanged -= OnColumnsChanged; + + foreach (var column in DataGrid.Columns) + { + column.VisibilityChanged -= OnVisibilityChanged; + } + } + } + + private void OnColumnsChanged(object? sender, EventArgs e) + { + CreateView(); + } + + private void OnVisibilityChanged(object? sender, EventArgs e) + { + CreateView(); + } + + private void DataGrid_ItemSelected(object? sender, SelectionChangedEventArgs e) + { + if (_hasSelected || (e.CurrentSelection.Count > 0 && e.CurrentSelection.Any(s => s == BindingContext))) + { + UpdateColors(); + } + } + + #endregion Methods +} diff --git a/Maui.DataGrid/Extensions/ListExtensions.cs b/Maui.DataGrid/Extensions/ListExtensions.cs new file mode 100644 index 0000000..94ff55b --- /dev/null +++ b/Maui.DataGrid/Extensions/ListExtensions.cs @@ -0,0 +1,18 @@ +namespace Maui.DataGrid.Extensions; + +internal static class ListExtensions +{ + public static bool TryGetItem(this IList list, int index, out T? item) + { + if (index >= 0 && index < list.Count) + { + item = list[index]; + return true; + } + else + { + item = default; + return false; + } + } +}