Skip to content

Commit 031ffdc

Browse files
committed
feat: Add multi-column search
1 parent d80d679 commit 031ffdc

File tree

4 files changed

+85
-11
lines changed

4 files changed

+85
-11
lines changed

SimpleDataGrid.Example/MainViewModel.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ public class MainViewModel
99
public PagedCollection<Person> People { get; }
1010
public List<int> PageSizes { get; } = [10, 25, 50, 100];
1111

12+
public bool SearchByName { get; set; } = true;
13+
public bool SearchByEmail { get; set; } = false;
14+
public bool SearchByDepartment { get; set; } = false;
15+
1216
public MainViewModel()
1317
{
1418
People = new PagedCollection<Person>(10);
@@ -20,7 +24,7 @@ private static List<Person> GetPeople()
2024
var people = new List<Person>();
2125
for (var i = 1; i <= 100; i++)
2226
{
23-
people.Add(new Person { Id = i, Name = $"Person {i}", Age = 20 + (i % 50) });
27+
people.Add(new Person { Id = i, Name = $"Person {i}", Age = 20 + (i % 50), Email = $"person{i}@example.com", Department = (i % 2 == 0) ? "Sales" : "Marketing" });
2428
}
2529
return people;
2630
}
@@ -58,4 +62,6 @@ public class Person
5862
public int Id { get; set; }
5963
public string Name { get; set; } = string.Empty;
6064
public int Age { get; set; }
65+
public string Email { get; set; } = string.Empty;
66+
public string Department { get; set; } = string.Empty;
6167
}

SimpleDataGrid.Example/MainWindow.xaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
<CheckBox x:Name="WildcardCheckBox" Content="Use Wildcards" VerticalAlignment="Center" Margin="5" />
3030
<ProgressBar IsIndeterminate="True" Width="20" Height="20" Margin="5" Visibility="{Binding People.IsSearching, Converter={StaticResource BooleanToVisibilityConverter}}"/>
3131
</StackPanel>
32+
<StackPanel Orientation="Horizontal" Margin="5,0,0,0">
33+
<TextBlock Text="Search In:" VerticalAlignment="Center" Margin="0,0,5,0"/>
34+
<CheckBox Content="Name" IsChecked="{Binding SearchByName}" Checked="SearchOption_Changed" Unchecked="SearchOption_Changed" Margin="0,0,5,0"/>
35+
<CheckBox Content="Email" IsChecked="{Binding SearchByEmail}" Checked="SearchOption_Changed" Unchecked="SearchOption_Changed" Margin="0,0,5,0"/>
36+
<CheckBox Content="Department" IsChecked="{Binding SearchByDepartment}" Checked="SearchOption_Changed" Unchecked="SearchOption_Changed" Margin="0,0,5,0"/>
37+
</StackPanel>
3238
<StackPanel Orientation="Horizontal">
3339
<TextBox x:Name="MinAgeTextBox" Width="50" Margin="5" />
3440
<Button Content="Filter by Min Age" Click="FilterButton_Click" Margin="5" />

SimpleDataGrid.Example/MainWindow.xaml.cs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,25 @@ private void NextButton_Click(object sender, RoutedEventArgs e)
2121
}
2222

2323
private void SearchTextBox_TextChanged(object sender, TextChangedEventArgs e)
24+
{
25+
ApplyMultiColumnSearch();
26+
}
27+
28+
private void SearchOption_Changed(object sender, RoutedEventArgs e)
29+
{
30+
ApplyMultiColumnSearch();
31+
}
32+
33+
private void ApplyMultiColumnSearch()
2434
{
2535
var viewModel = (MainViewModel)DataContext;
26-
viewModel.People.SetSearch(p => p.Name, SearchTextBox.Text, WildcardCheckBox.IsChecked == true, 300);
36+
var selectors = new List<Func<Person, string>>();
37+
38+
if (viewModel.SearchByName) selectors.Add(p => p.Name);
39+
if (viewModel.SearchByEmail) selectors.Add(p => p.Email);
40+
if (viewModel.SearchByDepartment) selectors.Add(p => p.Department);
41+
42+
viewModel.People.SetSearch(selectors, SearchTextBox.Text, WildcardCheckBox.IsChecked == true, 300);
2743
}
2844

2945
private void FilterButton_Click(object sender, RoutedEventArgs e)

SimpleDataGrid/Pagination/PagedCollection.cs

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public int PageSize
3131

3232
private readonly Dictionary<string, Func<T, bool>> _filters = [];
3333
private readonly List<(Func<T, object> selector, bool ascending)> _sorts = [];
34-
private Func<T, string>? _searchSelector;
34+
private IEnumerable<Func<T, string>> _searchSelectors = [];
3535
private string? _searchTerm;
3636
private bool _useWildcards;
3737
private System.Threading.Timer? _debounceTimer;
@@ -164,7 +164,49 @@ public void ClearFilters()
164164
/// <param name="debounceMilliseconds">Optional. The number of milliseconds to debounce the search. If 0, no debouncing occurs.</param>
165165
public void SetSearch(Func<T, string> selector, string? term, bool useWildcards = false, int debounceMilliseconds = 0)
166166
{
167-
_searchSelector = selector;
167+
SetSearch([selector], term, useWildcards, debounceMilliseconds);
168+
}
169+
170+
/// <summary>
171+
/// Sets the search criteria for the collection, searching across multiple properties.
172+
/// </summary>
173+
/// <param name="selectors">A collection of functions that return the string representation of the properties to search.</param>
174+
/// <param name="term">The search term.</param>
175+
/// <param name="useWildcards">A value indicating whether to use wildcards in the search term.</param>
176+
/// <param name="debounceMilliseconds">Optional. The number of milliseconds to debounce the search. If 0, no debouncing occurs.</param>
177+
public void SetSearch(IEnumerable<Func<T, string>> selectors, string? term, bool useWildcards = false, int debounceMilliseconds = 0)
178+
{
179+
_searchSelectors = selectors ?? throw new ArgumentNullException(nameof(selectors));
180+
_searchTerm = term;
181+
_useWildcards = useWildcards;
182+
183+
if (debounceMilliseconds > 0)
184+
{
185+
IsSearching = true;
186+
_debounceTimer?.Dispose();
187+
_debounceTimer = new System.Threading.Timer(_ =>
188+
{
189+
System.Windows.Application.Current.Dispatcher.Invoke(ApplyFiltering);
190+
IsSearching = false;
191+
}, null, debounceMilliseconds, System.Threading.Timeout.Infinite);
192+
}
193+
else
194+
{
195+
ApplyFiltering();
196+
SearchChanged?.Invoke(this, EventArgs.Empty);
197+
}
198+
}
199+
200+
/// <summary>
201+
/// Sets the search criteria for the collection, requiring all selectors to match the term.
202+
/// </summary>
203+
/// <param name="selectors">A collection of functions that return the string representation of the properties to search.</param>
204+
/// <param name="term">The search term.</param>
205+
/// <param name="useWildcards">A value indicating whether to use wildcards in the search term.</param>
206+
/// <param name="debounceMilliseconds">Optional. The number of milliseconds to debounce the search. If 0, no debouncing occurs.</param>
207+
public void SetSearchAll(IEnumerable<Func<T, string>> selectors, string? term, bool useWildcards = false, int debounceMilliseconds = 0)
208+
{
209+
_searchSelectors = selectors ?? throw new ArgumentNullException(nameof(selectors));
168210
_searchTerm = term;
169211
_useWildcards = useWildcards;
170212

@@ -190,7 +232,7 @@ public void SetSearch(Func<T, string> selector, string? term, bool useWildcards
190232
/// </summary>
191233
public void ClearSearch()
192234
{
193-
_searchSelector = null;
235+
_searchSelectors = [];
194236
_searchTerm = null;
195237
_useWildcards = false;
196238
ApplyFiltering();
@@ -230,19 +272,23 @@ private void ApplyFiltering()
230272
query = query.Where(filter);
231273
}
232274

233-
if (!string.IsNullOrWhiteSpace(_searchTerm) && _searchSelector != null)
275+
if (!string.IsNullOrWhiteSpace(_searchTerm) && _searchSelectors.Any())
234276
{
277+
var term = _searchTerm;
278+
Func<string, bool> matches;
279+
235280
if (_useWildcards)
236281
{
237-
var regex = new Regex(WildcardToRegex(_searchTerm), RegexOptions.IgnoreCase);
238-
query = query.Where(x => regex.IsMatch(_searchSelector(x)));
282+
var regex = new Regex(WildcardToRegex(term), RegexOptions.IgnoreCase);
283+
matches = s => regex.IsMatch(s);
239284
}
240285
else
241286
{
242-
query = query.Where(x =>
243-
_searchSelector(x)
244-
?.Contains(_searchTerm, StringComparison.OrdinalIgnoreCase) == true);
287+
matches = s => s.Contains(term, StringComparison.OrdinalIgnoreCase);
245288
}
289+
290+
// OR logic for multi-column search
291+
query = query.Where(item => _searchSelectors.Any(selector => matches(selector(item) ?? string.Empty)));
246292
}
247293

248294
if (_sorts.Count > 0)

0 commit comments

Comments
 (0)