DataGridCheckBoxColumn.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved.
  5. using Avalonia.Input;
  6. using Avalonia.Interactivity;
  7. using Avalonia.Layout;
  8. using System;
  9. using System.Collections.Specialized;
  10. namespace Avalonia.Controls
  11. {
  12. /// <summary>
  13. /// Represents a <see cref="T:System.Windows.Controls.DataGrid" /> column that hosts
  14. /// <see cref="T:System.Windows.Controls.CheckBox" /> controls in its cells.
  15. /// </summary>
  16. public class DataGridCheckBoxColumn : DataGridBoundColumn
  17. {
  18. private CheckBox _currentCheckBox;
  19. private DataGrid _owningGrid;
  20. /// <summary>
  21. /// Initializes a new instance of the <see cref="T:System.Windows.Controls.DataGridCheckBoxColumn" /> class.
  22. /// </summary>
  23. public DataGridCheckBoxColumn()
  24. {
  25. BindingTarget = CheckBox.IsCheckedProperty;
  26. }
  27. /// <summary>
  28. /// Defines the <see cref="IsThreeState"/> property.
  29. /// </summary>
  30. public static StyledProperty<bool> IsThreeStateProperty =
  31. CheckBox.IsThreeStateProperty.AddOwner<DataGridCheckBoxColumn>();
  32. /// <summary>
  33. /// Gets or sets a value that indicates whether the hosted <see cref="T:System.Windows.Controls.CheckBox" /> controls allow three states or two.
  34. /// </summary>
  35. /// <returns>
  36. /// true if the hosted controls support three states; false if they support two states. The default is false.
  37. /// </returns>
  38. public bool IsThreeState
  39. {
  40. get => GetValue(IsThreeStateProperty);
  41. set => SetValue(IsThreeStateProperty, value);
  42. }
  43. protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
  44. {
  45. base.OnPropertyChanged(change);
  46. if (change.Property == IsThreeStateProperty)
  47. {
  48. NotifyPropertyChanged(change.Property.Name);
  49. }
  50. }
  51. /// <summary>
  52. /// Causes the column cell being edited to revert to the specified value.
  53. /// </summary>
  54. /// <param name="editingElement">
  55. /// The element that the column displays for a cell in editing mode.
  56. /// </param>
  57. /// <param name="uneditedValue">
  58. /// The previous, unedited value in the cell being edited.
  59. /// </param>
  60. protected override void CancelCellEdit(IControl editingElement, object uneditedValue)
  61. {
  62. if (editingElement is CheckBox editingCheckBox)
  63. {
  64. editingCheckBox.IsChecked = (bool?)uneditedValue;
  65. }
  66. }
  67. /// <summary>
  68. /// Gets a <see cref="T:System.Windows.Controls.CheckBox" /> control that is bound to the column's <see cref="P:System.Windows.Controls.DataGridBoundColumn.Binding" /> property value.
  69. /// </summary>
  70. /// <param name="cell">
  71. /// The cell that will contain the generated element.
  72. /// </param>
  73. /// <param name="dataItem">
  74. /// The data item represented by the row that contains the intended cell.
  75. /// </param>
  76. /// <returns>
  77. /// A new <see cref="T:System.Windows.Controls.CheckBox" /> control that is bound to the column's <see cref="P:System.Windows.Controls.DataGridBoundColumn.Binding" /> property value.
  78. /// </returns>
  79. protected override IControl GenerateEditingElementDirect(DataGridCell cell, object dataItem)
  80. {
  81. var checkBox = new CheckBox
  82. {
  83. Margin = new Thickness(0)
  84. };
  85. ConfigureCheckBox(checkBox);
  86. return checkBox;
  87. }
  88. /// <summary>
  89. /// Gets a read-only <see cref="T:System.Windows.Controls.CheckBox" /> control that is bound to the column's <see cref="P:System.Windows.Controls.DataGridBoundColumn.Binding" /> property value.
  90. /// </summary>
  91. /// <param name="cell">
  92. /// The cell that will contain the generated element.
  93. /// </param>
  94. /// <param name="dataItem">
  95. /// The data item represented by the row that contains the intended cell.
  96. /// </param>
  97. /// <returns>
  98. /// A new, read-only <see cref="T:System.Windows.Controls.CheckBox" /> control that is bound to the column's <see cref="P:System.Windows.Controls.DataGridBoundColumn.Binding" /> property value.
  99. /// </returns>
  100. protected override IControl GenerateElement(DataGridCell cell, object dataItem)
  101. {
  102. bool isEnabled = false;
  103. CheckBox checkBoxElement = new CheckBox();
  104. if (EnsureOwningGrid())
  105. {
  106. if (cell.RowIndex != -1 && cell.ColumnIndex != -1 &&
  107. cell.OwningRow != null &&
  108. cell.OwningRow.Slot == this.OwningGrid.CurrentSlot &&
  109. cell.ColumnIndex == this.OwningGrid.CurrentColumnIndex)
  110. {
  111. isEnabled = true;
  112. if (_currentCheckBox != null)
  113. {
  114. _currentCheckBox.IsEnabled = false;
  115. }
  116. _currentCheckBox = checkBoxElement;
  117. }
  118. }
  119. checkBoxElement.IsEnabled = isEnabled;
  120. checkBoxElement.IsHitTestVisible = false;
  121. ConfigureCheckBox(checkBoxElement);
  122. if (Binding != null)
  123. {
  124. checkBoxElement.Bind(BindingTarget, Binding);
  125. }
  126. return checkBoxElement;
  127. }
  128. /// <summary>
  129. /// Called when a cell in the column enters editing mode.
  130. /// </summary>
  131. /// <param name="editingElement">
  132. /// The element that the column displays for a cell in editing mode.
  133. /// </param>
  134. /// <param name="editingEventArgs">
  135. /// Information about the user gesture that is causing a cell to enter editing mode.
  136. /// </param>
  137. /// <returns>
  138. /// The unedited value.
  139. /// </returns>
  140. protected override object PrepareCellForEdit(IControl editingElement, RoutedEventArgs editingEventArgs)
  141. {
  142. if (editingElement is CheckBox editingCheckBox)
  143. {
  144. void EditValue()
  145. {
  146. // User clicked the checkbox itself or pressed space, let's toggle the IsChecked value
  147. if (editingCheckBox.IsThreeState)
  148. {
  149. switch (editingCheckBox.IsChecked)
  150. {
  151. case false:
  152. editingCheckBox.IsChecked = true;
  153. break;
  154. case true:
  155. editingCheckBox.IsChecked = null;
  156. break;
  157. case null:
  158. editingCheckBox.IsChecked = false;
  159. break;
  160. }
  161. }
  162. else
  163. {
  164. editingCheckBox.IsChecked = !editingCheckBox.IsChecked;
  165. }
  166. }
  167. bool? uneditedValue = editingCheckBox.IsChecked;
  168. if(editingEventArgs is PointerPressedEventArgs args)
  169. {
  170. void ProcessPointerArgs()
  171. {
  172. // Editing was triggered by a mouse click
  173. Point position = args.GetPosition(editingCheckBox);
  174. Rect rect = new Rect(0, 0, editingCheckBox.Bounds.Width, editingCheckBox.Bounds.Height);
  175. if(rect.Contains(position))
  176. {
  177. EditValue();
  178. }
  179. }
  180. void OnLayoutUpdated(object sender, EventArgs e)
  181. {
  182. if(!editingCheckBox.Bounds.IsEmpty)
  183. {
  184. editingCheckBox.LayoutUpdated -= OnLayoutUpdated;
  185. ProcessPointerArgs();
  186. }
  187. }
  188. if(editingCheckBox.Bounds.IsEmpty)
  189. {
  190. editingCheckBox.LayoutUpdated += OnLayoutUpdated;
  191. }
  192. else
  193. {
  194. ProcessPointerArgs();
  195. }
  196. }
  197. return uneditedValue;
  198. }
  199. return false;
  200. }
  201. /// <summary>
  202. /// Called by the DataGrid control when this column asks for its elements to be
  203. /// updated, because its CheckBoxContent or IsThreeState property changed.
  204. /// </summary>
  205. protected internal override void RefreshCellContent(IControl element, string propertyName)
  206. {
  207. if (element == null)
  208. {
  209. throw new ArgumentNullException("element");
  210. }
  211. if (element is CheckBox checkBox)
  212. {
  213. DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty);
  214. }
  215. else
  216. {
  217. throw DataGridError.DataGrid.ValueIsNotAnInstanceOf("element", typeof(CheckBox));
  218. }
  219. }
  220. private void Columns_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
  221. {
  222. if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems.Contains(this) && _owningGrid != null)
  223. {
  224. _owningGrid.Columns.CollectionChanged -= Columns_CollectionChanged;
  225. _owningGrid.CurrentCellChanged -= OwningGrid_CurrentCellChanged;
  226. _owningGrid.KeyDown -= OwningGrid_KeyDown;
  227. _owningGrid.LoadingRow -= OwningGrid_LoadingRow;
  228. _owningGrid = null;
  229. }
  230. }
  231. private void ConfigureCheckBox(CheckBox checkBox)
  232. {
  233. checkBox.HorizontalAlignment = HorizontalAlignment.Center;
  234. checkBox.VerticalAlignment = VerticalAlignment.Center;
  235. DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty);
  236. }
  237. private bool EnsureOwningGrid()
  238. {
  239. if (OwningGrid != null)
  240. {
  241. if (OwningGrid != _owningGrid)
  242. {
  243. _owningGrid = OwningGrid;
  244. _owningGrid.Columns.CollectionChanged += Columns_CollectionChanged;
  245. _owningGrid.CurrentCellChanged += OwningGrid_CurrentCellChanged;
  246. _owningGrid.KeyDown += OwningGrid_KeyDown;
  247. _owningGrid.LoadingRow += OwningGrid_LoadingRow;
  248. }
  249. return true;
  250. }
  251. return false;
  252. }
  253. private void OwningGrid_CurrentCellChanged(object sender, EventArgs e)
  254. {
  255. if (_currentCheckBox != null)
  256. {
  257. _currentCheckBox.IsEnabled = false;
  258. }
  259. if (OwningGrid != null && OwningGrid.CurrentColumn == this
  260. && OwningGrid.IsSlotVisible(OwningGrid.CurrentSlot))
  261. {
  262. if (OwningGrid.DisplayData.GetDisplayedElement(OwningGrid.CurrentSlot) is DataGridRow row)
  263. {
  264. CheckBox checkBox = GetCellContent(row) as CheckBox;
  265. if (checkBox != null)
  266. {
  267. checkBox.IsEnabled = true;
  268. }
  269. _currentCheckBox = checkBox;
  270. }
  271. }
  272. }
  273. private void OwningGrid_KeyDown(object sender, KeyEventArgs e)
  274. {
  275. if (e.Key == Key.Space && OwningGrid != null &&
  276. OwningGrid.CurrentColumn == this)
  277. {
  278. if (OwningGrid.DisplayData.GetDisplayedElement(OwningGrid.CurrentSlot) is DataGridRow row)
  279. {
  280. CheckBox checkBox = GetCellContent(row) as CheckBox;
  281. if (checkBox == _currentCheckBox)
  282. {
  283. OwningGrid.BeginEdit();
  284. }
  285. }
  286. }
  287. }
  288. private void OwningGrid_LoadingRow(object sender, DataGridRowEventArgs e)
  289. {
  290. if (OwningGrid != null)
  291. {
  292. if (GetCellContent(e.Row) is CheckBox checkBox)
  293. {
  294. if (OwningGrid.CurrentColumnIndex == Index && OwningGrid.CurrentSlot == e.Row.Slot)
  295. {
  296. if (_currentCheckBox != null)
  297. {
  298. _currentCheckBox.IsEnabled = false;
  299. }
  300. checkBox.IsEnabled = true;
  301. _currentCheckBox = checkBox;
  302. }
  303. else
  304. {
  305. checkBox.IsEnabled = false;
  306. }
  307. }
  308. }
  309. }
  310. }
  311. }