Skip to content

Commit 24fefe5

Browse files
committed
Fix #69: MultipleChoiceFilter introduces reflection-only dependency to TomsToolbox.Wpf
1 parent eaa4acd commit 24fefe5

File tree

3 files changed

+236
-4
lines changed

3 files changed

+236
-4
lines changed
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
namespace DataGridExtensions
2+
{
3+
using System;
4+
using System.Collections.Specialized;
5+
using System.ComponentModel;
6+
using System.Windows;
7+
using System.Windows.Controls;
8+
using System.Windows.Controls.Primitives;
9+
10+
using Microsoft.Xaml.Behaviors;
11+
12+
using TomsToolbox.Wpf;
13+
14+
/// <summary>
15+
/// A behavior for a list box to handle the interaction between the list box and a "select all" checkbox.
16+
/// </summary>
17+
/// <seealso cref="Behavior{T}" />
18+
internal class ListBoxSelectAllBehavior : Behavior<ListBox>
19+
{
20+
private readonly DispatcherThrottle _collectionChangedThrottle;
21+
private bool _isListBoxUpdating;
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="ListBoxSelectAllBehavior"/> class.
25+
/// </summary>
26+
public ListBoxSelectAllBehavior()
27+
{
28+
_collectionChangedThrottle = new DispatcherThrottle(ListBox_CollectionChanged);
29+
}
30+
31+
/// <summary>
32+
/// Gets or sets a flag indicating if all files are selected. Bind this property to the <see cref="ToggleButton.IsChecked"/> property of a three state check box.
33+
/// </summary>
34+
public bool? AreAllFilesSelected
35+
{
36+
get => (bool?)GetValue(AreAllFilesSelectedProperty);
37+
set => SetValue(AreAllFilesSelectedProperty, value);
38+
}
39+
/// <summary>
40+
/// Identifies the <see cref="AreAllFilesSelected"/> dependency property
41+
/// </summary>
42+
public static readonly DependencyProperty AreAllFilesSelectedProperty =
43+
DependencyProperty.Register("AreAllFilesSelected", typeof(bool?), typeof(ListBoxSelectAllBehavior), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, (sender, e) => ((ListBoxSelectAllBehavior)sender)?.AreAllFilesSelected_Changed((bool?)e.NewValue)));
44+
45+
/// <summary>
46+
/// Called after the behavior is attached to an AssociatedObject.
47+
/// </summary>
48+
/// <remarks>
49+
/// Override this to hook up functionality to the AssociatedObject.
50+
/// </remarks>
51+
protected override void OnAttached()
52+
{
53+
base.OnAttached();
54+
55+
var listBox = AssociatedObject;
56+
if ((listBox == null) || DesignerProperties.GetIsInDesignMode(listBox))
57+
return;
58+
59+
listBox.SelectAll();
60+
61+
listBox.SelectionChanged += ListBox_SelectionChanged;
62+
((INotifyCollectionChanged)listBox.Items).CollectionChanged += (_, __) => _collectionChangedThrottle.Tick();
63+
}
64+
65+
private void ListBox_SelectionChanged(object? sender, EventArgs? e)
66+
{
67+
var listBox = AssociatedObject;
68+
if (listBox == null)
69+
return;
70+
71+
try
72+
{
73+
_isListBoxUpdating = true;
74+
75+
if (listBox.Items.Count == listBox.SelectedItems.Count)
76+
{
77+
AreAllFilesSelected = true;
78+
}
79+
else if (listBox.SelectedItems.Count == 0)
80+
{
81+
AreAllFilesSelected = false;
82+
}
83+
else
84+
{
85+
AreAllFilesSelected = null;
86+
}
87+
}
88+
finally
89+
{
90+
_isListBoxUpdating = false;
91+
}
92+
}
93+
94+
private void ListBox_CollectionChanged()
95+
{
96+
var listBox = AssociatedObject;
97+
98+
if (AreAllFilesSelected.GetValueOrDefault())
99+
{
100+
listBox?.SelectAll();
101+
}
102+
}
103+
104+
private void AreAllFilesSelected_Changed(bool? newValue)
105+
{
106+
var listBox = AssociatedObject;
107+
if (listBox == null)
108+
return;
109+
110+
if (_isListBoxUpdating)
111+
return;
112+
113+
if (newValue == null)
114+
{
115+
Dispatcher?.BeginInvoke(() => AreAllFilesSelected = false);
116+
return;
117+
}
118+
119+
if (newValue.GetValueOrDefault())
120+
{
121+
listBox.SelectAll();
122+
}
123+
else
124+
{
125+
listBox.SelectedIndex = -1;
126+
}
127+
}
128+
}
129+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
namespace DataGridExtensions
2+
{
3+
using System;
4+
using System.Linq;
5+
using System.Windows;
6+
using System.Windows.Controls.Primitives;
7+
using System.Windows.Input;
8+
9+
using Microsoft.Xaml.Behaviors;
10+
11+
using TomsToolbox.Wpf;
12+
13+
/// <summary>
14+
/// Handle focus for popups opened by toggle buttons.
15+
/// When the popup opens, the focus is set to the first focusable control in the popup.
16+
/// When the popup closes, the focus is set back to the button.
17+
/// </summary>
18+
internal class PopupFocusManagerBehavior : Behavior<Popup>
19+
{
20+
/// <summary>
21+
/// Gets or sets the toggle button that controls the popup.
22+
/// </summary>
23+
public ToggleButton? ToggleButton
24+
{
25+
get => (ToggleButton)GetValue(ToggleButtonProperty);
26+
set => SetValue(ToggleButtonProperty, value);
27+
}
28+
/// <summary>
29+
/// Identifies the <see cref="ToggleButton"/> dependency property.
30+
/// </summary>
31+
public static readonly DependencyProperty ToggleButtonProperty =
32+
DependencyProperty.Register("ToggleButton", typeof(ToggleButton), typeof(PopupFocusManagerBehavior));
33+
34+
/// <summary>
35+
/// Called after the behavior is attached to an AssociatedObject.
36+
/// </summary>
37+
/// <remarks>
38+
/// Override this to hook up functionality to the AssociatedObject.
39+
/// </remarks>
40+
protected override void OnAttached()
41+
{
42+
base.OnAttached();
43+
44+
var popup = AssociatedObject;
45+
46+
popup.IsKeyboardFocusWithinChanged += Popup_IsKeyboardFocusWithinChanged;
47+
popup.Opened += Popup_Opened;
48+
popup.KeyDown += Popup_KeyDown;
49+
}
50+
51+
/// <summary>
52+
/// Called when the behavior is being detached from its AssociatedObject, but before it has actually occurred.
53+
/// </summary>
54+
/// <remarks>
55+
/// Override this to unhook functionality from the AssociatedObject.
56+
/// </remarks>
57+
protected override void OnDetaching()
58+
{
59+
base.OnDetaching();
60+
61+
var popup = AssociatedObject;
62+
63+
popup.IsKeyboardFocusWithinChanged -= Popup_IsKeyboardFocusWithinChanged;
64+
popup.Opened -= Popup_Opened;
65+
popup.KeyDown -= Popup_KeyDown;
66+
}
67+
68+
private void Popup_KeyDown(object? sender, KeyEventArgs? e)
69+
{
70+
if (ToggleButton == null)
71+
return;
72+
73+
switch (e?.Key)
74+
{
75+
case Key.Escape:
76+
case Key.Enter:
77+
case Key.Tab:
78+
ToggleButton.Focus();
79+
break;
80+
}
81+
}
82+
83+
private void Popup_Opened(object? sender, EventArgs? e)
84+
{
85+
if (!(sender is Popup popup))
86+
return;
87+
88+
var child = popup.Child;
89+
var focusable = child?.VisualDescendantsAndSelf().OfType<UIElement>().FirstOrDefault(item => item.Focusable);
90+
if (focusable != null)
91+
{
92+
popup.BeginInvoke(() => focusable.Focus());
93+
}
94+
}
95+
96+
private void Popup_IsKeyboardFocusWithinChanged(object? sender, DependencyPropertyChangedEventArgs e)
97+
{
98+
if (Equals(e.NewValue, false) && (ToggleButton != null))
99+
{
100+
ToggleButton.IsChecked = false;
101+
}
102+
}
103+
}
104+
}

src/DataGridExtensions/Themes/Generic.xaml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33
xmlns:src="clr-namespace:DataGridExtensions"
44
xmlns:behaviors="clr-namespace:DataGridExtensions.Behaviors"
55
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
6-
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase"
7-
xmlns:toms="urn:TomsToolbox">
6+
xmlns:componentModel="clr-namespace:System.ComponentModel;assembly=WindowsBase">
87

98
<!-- Style for a filter check box. The check box is only visible when it's hovered, focused or has a value. -->
109
<Style x:Key="{x:Static src:DataGridFilter.ColumnHeaderSearchCheckBoxStyleKey}" TargetType="CheckBox">
@@ -183,7 +182,7 @@
183182
</CollectionViewSource>
184183
</FrameworkElement.Resources>
185184
<i:Interaction.Behaviors>
186-
<toms:PopupFocusManagerBehavior ToggleButton="{Binding ElementName=ToggleButton}" />
185+
<src:PopupFocusManagerBehavior ToggleButton="{Binding ElementName=ToggleButton}" />
187186
</i:Interaction.Behaviors>
188187
<Border Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}" BorderThickness="1" BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
189188
<DockPanel>
@@ -218,7 +217,7 @@
218217
</DataTemplate>
219218
</ListBox.ItemTemplate>
220219
<i:Interaction.Behaviors>
221-
<toms:ListBoxSelectAllBehavior AreAllFilesSelected="{Binding IsChecked, ElementName=SelectAll}" />
220+
<src:ListBoxSelectAllBehavior AreAllFilesSelected="{Binding IsChecked, ElementName=SelectAll}" />
222221
</i:Interaction.Behaviors>
223222
</ListBox>
224223
</DockPanel>

0 commit comments

Comments
 (0)