Skip to content

Commit 3fa6677

Browse files
authored
Create DataGridCheckAllColumn.cs
1 parent 7847e20 commit 3fa6677

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed

WPF/DataGridCheckAllColumn.cs

Lines changed: 359 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
1+
// Code Auther: Flithor (Mr.Squirrel.Downy)
2+
// License: MIT
3+
// =======WARNING=======
4+
// Use this code at your own risk
5+
// The newest code may on the https://stackoverflow.com/a/77603913/6859121
6+
7+
using System;
8+
using System.Collections.Generic;
9+
using System.ComponentModel;
10+
using System.Globalization;
11+
using System.Linq;
12+
using System.Reflection;
13+
using System.Windows;
14+
using System.Windows.Controls;
15+
using System.Windows.Controls.Primitives;
16+
using System.Windows.Data;
17+
using System.Windows.Media;
18+
19+
using Expression = System.Linq.Expressions.Expression;
20+
21+
namespace Flithor_ReusableCodes
22+
{
23+
/// <summary>
24+
/// A DataGrid Column work for binding to Checked property for item element, and can Check All
25+
/// </summary>
26+
public class DataGridCheckAllColumn : DataGridBoundColumn
27+
{
28+
#region Private Fields
29+
//CheckBox in header
30+
private readonly CheckBox checkAllCheckBox;
31+
//owner DataGrid control for this column
32+
private DataGrid ownerDatagrid;
33+
//owner DataGrid current delegate get current list version
34+
//if version changed then change bindings
35+
private Func<int> getInnerEnumeratorVersion;
36+
//cached list version
37+
private int cachedInnerVersion;
38+
//default style for CheckBox
39+
private static Style _defaultElementStyle;
40+
#endregion
41+
42+
#region Initialize Control
43+
public static Style DefaultElementStyle
44+
{
45+
get
46+
{
47+
if (_defaultElementStyle == null)
48+
{
49+
var style = new Style(typeof(CheckBox))
50+
{
51+
Setters =
52+
{
53+
new Setter(UIElement.FocusableProperty, false),
54+
new Setter(CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Center),
55+
new Setter(CheckBox.VerticalAlignmentProperty, VerticalAlignment.Center)
56+
}
57+
};
58+
59+
style.Seal();
60+
_defaultElementStyle = style;
61+
}
62+
63+
return _defaultElementStyle;
64+
}
65+
}
66+
67+
static DataGridCheckAllColumn()
68+
{
69+
//override default element style
70+
ElementStyleProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(DefaultElementStyle));
71+
//make column readonly by default
72+
IsReadOnlyProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(true));
73+
//not allows move column
74+
CanUserReorderProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(false));
75+
//not allows resize column
76+
CanUserResizeProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(false));
77+
//not allows order items by click header
78+
CanUserSortProperty.OverrideMetadata(typeof(DataGridCheckAllColumn), new FrameworkPropertyMetadata(false));
79+
}
80+
81+
public DataGridCheckAllColumn()
82+
{
83+
//override header
84+
Header = checkAllCheckBox = new CheckBox();
85+
}
86+
87+
protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
88+
{
89+
if (ownerDatagrid != null) return;
90+
91+
ownerDatagrid = GetParentDataGrid();
92+
if (ownerDatagrid == null) return;
93+
94+
InitInnerVersionDetect(ownerDatagrid.Items);
95+
96+
((INotifyPropertyChanged)ownerDatagrid.Items).PropertyChanged += OnPropertyChanged;
97+
98+
//if DataGrid has items now, init bindings
99+
checkAllCheckBox.IsEnabled = ownerDatagrid.Items.Count > 0;
100+
if (checkAllCheckBox.IsEnabled)
101+
ResetCheckCurrentAllBinding();
102+
}
103+
//find parent DataGrid(if not end initialize, may return null)
104+
private DataGrid GetParentDataGrid()
105+
{
106+
DependencyObject elment = checkAllCheckBox;
107+
do
108+
{
109+
elment = VisualTreeHelper.GetParent(elment);
110+
}
111+
while (elment != null && !(elment is DataGrid));
112+
return elment as DataGrid;
113+
}
114+
#endregion
115+
116+
#region Generate Element
117+
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
118+
{
119+
return GenerateCheckBox(false, cell, dataItem);
120+
}
121+
122+
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
123+
{
124+
return GenerateCheckBox(true, cell, dataItem);
125+
}
126+
127+
private CheckBox GenerateCheckBox(bool isEditing, DataGridCell cell, object dataItem)
128+
{
129+
var checkBox = new CheckBox();
130+
ApplyStyle(isEditing, checkBox);
131+
ApplyBinding(dataItem, checkBox);
132+
return checkBox;
133+
}
134+
135+
private void ApplyBinding(object dataItem, CheckBox checkBox)
136+
{
137+
var binding = CloneBinding(Binding, dataItem);
138+
if (binding is Binding newBinding)
139+
{
140+
newBinding.Mode = BindingMode.TwoWay;
141+
newBinding.UpdateSourceTrigger = UpdateSourceTrigger.PropertyChanged;
142+
}
143+
BindingOperations.ClearBinding(checkBox, CheckBox.IsCheckedProperty);
144+
checkBox.SetBinding(CheckBox.IsCheckedProperty, binding);
145+
}
146+
147+
internal void ApplyStyle(bool isEditing, FrameworkElement element)
148+
{
149+
Style style = PickStyle(isEditing);
150+
if (style != null)
151+
{
152+
element.Style = style;
153+
}
154+
}
155+
156+
private Style PickStyle(bool isEditing)
157+
{
158+
Style style = isEditing ? EditingElementStyle : ElementStyle;
159+
if (isEditing && (style == null))
160+
{
161+
style = ElementStyle;
162+
}
163+
164+
return style;
165+
}
166+
#endregion
167+
168+
#region Update Binding
169+
private void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
170+
{
171+
if (ownerDatagrid == null || e.PropertyName != nameof(ownerDatagrid.Items.Count))
172+
return;
173+
//if items count changed then means the collection may changed
174+
if (ownerDatagrid.Items.Count == 0)
175+
{
176+
//if Items goes empty then clear the check binding and disable check all
177+
BindingOperations.ClearBinding(checkAllCheckBox, CheckBox.IsCheckedProperty);
178+
checkAllCheckBox.IsEnabled = false;
179+
}
180+
else
181+
{
182+
//else update the binding to current displayed items
183+
ResetCheckCurrentAllBinding();
184+
checkAllCheckBox.IsEnabled = true;
185+
}
186+
}
187+
188+
private void ResetCheckCurrentAllBinding()
189+
{
190+
//If version changed then update binding by current items
191+
if (ownerDatagrid == null || !InnerVersionChanged()) return;
192+
193+
var checkAllBinding = new MultiBinding
194+
{
195+
Converter = AllBoolStatusConverter.Default,
196+
Mode = BindingMode.TwoWay
197+
};
198+
//binding items by current displayed items
199+
var currentItems = ownerDatagrid.Items.OfType<object>().ToList();
200+
foreach (var item in currentItems)
201+
{
202+
checkAllBinding.Bindings.Add(CloneBinding((Binding)Binding, item));
203+
}
204+
205+
//clear old binding if exists
206+
BindingOperations.ClearBinding(checkAllCheckBox, CheckBox.IsCheckedProperty);
207+
208+
checkAllCheckBox.SetBinding(CheckBox.IsCheckedProperty, checkAllBinding);
209+
}
210+
211+
//generate DataGrid.Items version get delegate
212+
private void InitInnerVersionDetect(ItemCollection itemCollection)
213+
{
214+
//Timestamp property is the version mark of ItemCollection to tell us is it changed
215+
var collectionTimestampProerty = itemCollection.GetType()
216+
.GetProperty("Timestamp", BindingFlags.Instance | BindingFlags.NonPublic);
217+
//use Linq Expression build a simple delegate to access Timestamp property
218+
getInnerEnumeratorVersion = Expression.Lambda<Func<int>>(Expression.Property(
219+
Expression.Constant(itemCollection),
220+
collectionTimestampProerty)).Compile();
221+
}
222+
//get the inner collection version to detect is it changed
223+
private bool InnerVersionChanged()
224+
{
225+
var currentInnerVersion = getInnerEnumeratorVersion.Invoke();
226+
if (currentInnerVersion != cachedInnerVersion)
227+
{
228+
cachedInnerVersion = currentInnerVersion;
229+
return true;
230+
}
231+
232+
return false;
233+
}
234+
//create a new binding instance by existed binding
235+
private static BindingBase CloneBinding(BindingBase bindingBase, object source)
236+
{
237+
switch (bindingBase)
238+
{
239+
case Binding binding:
240+
var resultBinding = new Binding
241+
{
242+
Source = source,
243+
AsyncState = binding.AsyncState,
244+
BindingGroupName = binding.BindingGroupName,
245+
BindsDirectlyToSource = binding.BindsDirectlyToSource,
246+
Converter = binding.Converter,
247+
ConverterCulture = binding.ConverterCulture,
248+
ConverterParameter = binding.ConverterParameter,
249+
//ElementName = binding.ElementName,
250+
FallbackValue = binding.FallbackValue,
251+
IsAsync = binding.IsAsync,
252+
Mode = binding.Mode,
253+
NotifyOnSourceUpdated = binding.NotifyOnSourceUpdated,
254+
NotifyOnTargetUpdated = binding.NotifyOnTargetUpdated,
255+
NotifyOnValidationError = binding.NotifyOnValidationError,
256+
Path = binding.Path,
257+
//RelativeSource = binding.RelativeSource,
258+
StringFormat = binding.StringFormat,
259+
TargetNullValue = binding.TargetNullValue,
260+
UpdateSourceExceptionFilter = binding.UpdateSourceExceptionFilter,
261+
UpdateSourceTrigger = binding.UpdateSourceTrigger,
262+
ValidatesOnDataErrors = binding.ValidatesOnDataErrors,
263+
ValidatesOnExceptions = binding.ValidatesOnExceptions,
264+
XPath = binding.XPath,
265+
};
266+
267+
foreach (var validationRule in binding.ValidationRules)
268+
{
269+
resultBinding.ValidationRules.Add(validationRule);
270+
}
271+
272+
return resultBinding;
273+
case MultiBinding multiBinding:
274+
var resultMultiBinding = new MultiBinding
275+
{
276+
BindingGroupName = multiBinding.BindingGroupName,
277+
Converter = multiBinding.Converter,
278+
ConverterCulture = multiBinding.ConverterCulture,
279+
ConverterParameter = multiBinding.ConverterParameter,
280+
FallbackValue = multiBinding.FallbackValue,
281+
Mode = multiBinding.Mode,
282+
NotifyOnSourceUpdated = multiBinding.NotifyOnSourceUpdated,
283+
NotifyOnTargetUpdated = multiBinding.NotifyOnTargetUpdated,
284+
NotifyOnValidationError = multiBinding.NotifyOnValidationError,
285+
StringFormat = multiBinding.StringFormat,
286+
TargetNullValue = multiBinding.TargetNullValue,
287+
UpdateSourceExceptionFilter = multiBinding.UpdateSourceExceptionFilter,
288+
UpdateSourceTrigger = multiBinding.UpdateSourceTrigger,
289+
ValidatesOnDataErrors = multiBinding.ValidatesOnDataErrors,
290+
ValidatesOnExceptions = multiBinding.ValidatesOnDataErrors,
291+
};
292+
293+
foreach (var validationRule in multiBinding.ValidationRules)
294+
{
295+
resultMultiBinding.ValidationRules.Add(validationRule);
296+
}
297+
298+
foreach (var childBinding in multiBinding.Bindings)
299+
{
300+
resultMultiBinding.Bindings.Add(CloneBinding(childBinding, source));
301+
}
302+
303+
return resultMultiBinding;
304+
case PriorityBinding priorityBinding:
305+
var resultPriorityBinding = new PriorityBinding
306+
{
307+
BindingGroupName = priorityBinding.BindingGroupName,
308+
FallbackValue = priorityBinding.FallbackValue,
309+
StringFormat = priorityBinding.StringFormat,
310+
TargetNullValue = priorityBinding.TargetNullValue,
311+
};
312+
313+
foreach (var childBinding in priorityBinding.Bindings)
314+
{
315+
resultPriorityBinding.Bindings.Add(CloneBinding(childBinding, source));
316+
}
317+
318+
return resultPriorityBinding;
319+
default:
320+
throw new NotSupportedException("Failed to clone binding");
321+
}
322+
}
323+
/// <summary>
324+
/// A MultiValueConverter to merge all items bound bool value into one
325+
/// </summary>
326+
private class AllBoolStatusConverter : IMultiValueConverter
327+
{
328+
public static readonly AllBoolStatusConverter Default = new AllBoolStatusConverter();
329+
330+
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
331+
{
332+
if (values.Length == 0 || values.OfType<bool>().Count() != values.Length)
333+
return false;
334+
// detect all items are equals the first
335+
var firstStatus = values.First();
336+
337+
foreach (var value in values)
338+
{
339+
//any one not equals to first then return null
340+
if (!Equals(value, firstStatus))
341+
return null;
342+
}
343+
344+
return firstStatus;
345+
}
346+
347+
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
348+
{
349+
//if the check all CheckBox checked or unchecked then update all items bound value
350+
var res = new object[targetTypes.Length];
351+
for (int i = 0; i < res.Length; i++)
352+
res[i] = value;
353+
return res;
354+
}
355+
}
356+
#endregion
357+
358+
}
359+
}

0 commit comments

Comments
 (0)