// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Layout; using System; using System.Collections.Specialized; namespace Avalonia.Controls { /// /// Represents a column that hosts /// controls in its cells. /// public class DataGridCheckBoxColumn : DataGridBoundColumn { private CheckBox _currentCheckBox; private DataGrid _owningGrid; /// /// Initializes a new instance of the class. /// public DataGridCheckBoxColumn() { BindingTarget = CheckBox.IsCheckedProperty; } /// /// Defines the property. /// public static StyledProperty IsThreeStateProperty = CheckBox.IsThreeStateProperty.AddOwner(); /// /// Gets or sets a value that indicates whether the hosted controls allow three states or two. /// /// /// true if the hosted controls support three states; false if they support two states. The default is false. /// public bool IsThreeState { get => GetValue(IsThreeStateProperty); set => SetValue(IsThreeStateProperty, value); } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); if (change.Property == IsThreeStateProperty) { NotifyPropertyChanged(change.Property.Name); } } /// /// Causes the column cell being edited to revert to the specified value. /// /// /// The element that the column displays for a cell in editing mode. /// /// /// The previous, unedited value in the cell being edited. /// protected override void CancelCellEdit(IControl editingElement, object uneditedValue) { if (editingElement is CheckBox editingCheckBox) { editingCheckBox.IsChecked = (bool?)uneditedValue; } } /// /// Gets a control that is bound to the column's property value. /// /// /// The cell that will contain the generated element. /// /// /// The data item represented by the row that contains the intended cell. /// /// /// A new control that is bound to the column's property value. /// protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem) { var checkBox = new CheckBox { Margin = new Thickness(0) }; ConfigureCheckBox(checkBox); return checkBox; } /// /// Gets a read-only control that is bound to the column's property value. /// /// /// The cell that will contain the generated element. /// /// /// The data item represented by the row that contains the intended cell. /// /// /// A new, read-only control that is bound to the column's property value. /// protected override IControl GenerateElement(DataGridCell cell, object dataItem) { bool isEnabled = false; CheckBox checkBoxElement = new CheckBox(); if (EnsureOwningGrid()) { if (cell.RowIndex != -1 && cell.ColumnIndex != -1 && cell.OwningRow != null && cell.OwningRow.Slot == this.OwningGrid.CurrentSlot && cell.ColumnIndex == this.OwningGrid.CurrentColumnIndex) { isEnabled = true; if (_currentCheckBox != null) { _currentCheckBox.IsEnabled = false; } _currentCheckBox = checkBoxElement; } } checkBoxElement.IsEnabled = isEnabled; checkBoxElement.IsHitTestVisible = false; ConfigureCheckBox(checkBoxElement); if (Binding != null) { checkBoxElement.Bind(BindingTarget, Binding); } return checkBoxElement; } /// /// Called when a cell in the column enters editing mode. /// /// /// The element that the column displays for a cell in editing mode. /// /// /// Information about the user gesture that is causing a cell to enter editing mode. /// /// /// The unedited value. /// protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs) { if (editingElement is CheckBox editingCheckBox) { void EditValue() { // User clicked the checkbox itself or pressed space, let's toggle the IsChecked value if (editingCheckBox.IsThreeState) { switch (editingCheckBox.IsChecked) { case false: editingCheckBox.IsChecked = true; break; case true: editingCheckBox.IsChecked = null; break; case null: editingCheckBox.IsChecked = false; break; } } else { 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; } /// /// Called by the DataGrid control when this column asks for its elements to be /// updated, because its CheckBoxContent or IsThreeState property changed. /// protected internal override void RefreshCellContent(IControl element, string propertyName) { if (element == null) { throw new ArgumentNullException("element"); } if (element is CheckBox checkBox) { DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty); } else { throw DataGridError.DataGrid.ValueIsNotAnInstanceOf("element", typeof(CheckBox)); } } private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Contains(this) && _owningGrid != null) { _owningGrid.Columns.CollectionChanged -= Columns_CollectionChanged; _owningGrid.CurrentCellChanged -= OwningGrid_CurrentCellChanged; _owningGrid.KeyDown -= OwningGrid_KeyDown; _owningGrid.LoadingRow -= OwningGrid_LoadingRow; _owningGrid = null; } } private void ConfigureCheckBox(CheckBox checkBox) { checkBox.HorizontalAlignment = HorizontalAlignment.Center; checkBox.VerticalAlignment = VerticalAlignment.Center; DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty); } private bool EnsureOwningGrid() { if (OwningGrid != null) { if (OwningGrid != _owningGrid) { _owningGrid = OwningGrid; _owningGrid.Columns.CollectionChanged += Columns_CollectionChanged; _owningGrid.CurrentCellChanged += OwningGrid_CurrentCellChanged; _owningGrid.KeyDown += OwningGrid_KeyDown; _owningGrid.LoadingRow += OwningGrid_LoadingRow; } return true; } return false; } private void OwningGrid_CurrentCellChanged(object sender, EventArgs e) { if (_currentCheckBox != null) { _currentCheckBox.IsEnabled = false; } if (OwningGrid != null && OwningGrid.CurrentColumn == this && OwningGrid.IsSlotVisible(OwningGrid.CurrentSlot)) { if (OwningGrid.DisplayData.GetDisplayedElement(OwningGrid.CurrentSlot) is DataGridRow row) { CheckBox checkBox = GetCellContent(row) as CheckBox; if (checkBox != null) { checkBox.IsEnabled = true; } _currentCheckBox = checkBox; } } } private void OwningGrid_KeyDown(object sender, KeyEventArgs e) { if (e.Key == Key.Space && OwningGrid != null && OwningGrid.CurrentColumn == this) { if (OwningGrid.DisplayData.GetDisplayedElement(OwningGrid.CurrentSlot) is DataGridRow row) { CheckBox checkBox = GetCellContent(row) as CheckBox; if (checkBox == _currentCheckBox) { OwningGrid.BeginEdit(); } } } } private void OwningGrid_LoadingRow(object sender, DataGridRowEventArgs e) { if (OwningGrid != null) { if (GetCellContent(e.Row) is CheckBox checkBox) { if (OwningGrid.CurrentColumnIndex == Index && OwningGrid.CurrentSlot == e.Row.Slot) { if (_currentCheckBox != null) { _currentCheckBox.IsEnabled = false; } checkBox.IsEnabled = true; _currentCheckBox = checkBox; } else { checkBox.IsEnabled = false; } } } } } }