// (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 { #region Data private bool _beganEditWithKeyboard; private bool _isThreeState; private CheckBox _currentCheckBox; private DataGrid _owningGrid; #endregion Data /// /// Initializes a new instance of the class. /// public DataGridCheckBoxColumn() { BindingTarget = CheckBox.IsCheckedProperty; } #region Public Properties /// /// 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 { return _isThreeState; } set { if (_isThreeState != value) { _isThreeState = value; NotifyPropertyChanged(nameof(IsThreeState)); } } } #endregion Public Properties #region Protected Methods /// /// 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) { 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) { // 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; } } 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) { checkBox.IsThreeState = IsThreeState; } else { throw DataGridError.DataGrid.ValueIsNotAnInstanceOf("element", typeof(CheckBox)); } } #endregion Protected Methods #region Private Methods 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; checkBox.IsThreeState = IsThreeState; } 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) { _beganEditWithKeyboard = true; OwningGrid.BeginEdit(); return; } } } _beganEditWithKeyboard = false; } 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; } } } } #endregion Private Methods } }