// (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;
}
}
}
}
}
}