Proper DataGrid search from TextBox in WPF using MVVM
Solution 1
If you only want to highlight the cells with the text from the TextBox
you could make an AttatchedProperty
for the DataGrid
to accept your search value from the TextBox
and create another AttatchedProperty
for the Cell
to indicate a match that you can usee to set properties in the Cell
style. Then we create a IMultiValueConverter
to check the Cell
value for a match to the search Text
.
This way its reusable on other projects as you only need the AttachedProperties
and Converter
Bind the AttachedProperty
SearchValue
to your TextBox
Text
property.
<DataGrid local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}"
Then create a Style
for DataGridCell
and create a Setter for the AttachedProperty
IsTextMatch
using the IMultiValueConverter
to return if the cells text matches the SearchValue
<Setter Property="local:DataGridTextSearch.IsTextMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
</Setter.Value>
</Setter>
Then we can use the Cells
attached IsTextMatch
property to set a highlight using a Trigger
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="Background" Value="Orange" />
</Trigger>
</Style.Triggers>
Here is a working example showing my rambilings :)
Code:
namespace WpfApplication17
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 0; i < 20; i++)
{
TestData.Add(new TestClass { MyProperty = GetRandomText(), MyProperty2 = GetRandomText(), MyProperty3 = GetRandomText() });
}
}
private string GetRandomText()
{
return System.IO.Path.GetFileNameWithoutExtension(System.IO.Path.GetRandomFileName());
}
private ObservableCollection<TestClass> _testData = new ObservableCollection<TestClass>();
public ObservableCollection<TestClass> TestData
{
get { return _testData; }
set { _testData = value; }
}
}
public class TestClass
{
public string MyProperty { get; set; }
public string MyProperty2 { get; set; }
public string MyProperty3 { get; set; }
}
public static class DataGridTextSearch
{
// Using a DependencyProperty as the backing store for SearchValue. This enables animation, styling, binding, etc...
public static readonly DependencyProperty SearchValueProperty =
DependencyProperty.RegisterAttached("SearchValue", typeof(string), typeof(DataGridTextSearch),
new FrameworkPropertyMetadata(string.Empty, FrameworkPropertyMetadataOptions.Inherits));
public static string GetSearchValue(DependencyObject obj)
{
return (string)obj.GetValue(SearchValueProperty);
}
public static void SetSearchValue(DependencyObject obj, string value)
{
obj.SetValue(SearchValueProperty, value);
}
// Using a DependencyProperty as the backing store for IsTextMatch. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsTextMatchProperty =
DependencyProperty.RegisterAttached("IsTextMatch", typeof(bool), typeof(DataGridTextSearch), new UIPropertyMetadata(false));
public static bool GetIsTextMatch(DependencyObject obj)
{
return (bool)obj.GetValue(IsTextMatchProperty);
}
public static void SetIsTextMatch(DependencyObject obj, bool value)
{
obj.SetValue(IsTextMatchProperty, value);
}
}
public class SearchValueConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string cellText = values[0] == null ? string.Empty : values[0].ToString();
string searchText = values[1] as string;
if (!string.IsNullOrEmpty(searchText) && !string.IsNullOrEmpty(cellText))
{
return cellText.ToLower().StartsWith(searchText.ToLower());
}
return false;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
return null;
}
}
}
Xaml:
<Window x:Class="WpfApplication17.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication17"
Title="MainWindow" Height="350" Width="525" Name="UI">
<StackPanel DataContext="{Binding ElementName=UI}">
<TextBox Name="SearchBox" />
<DataGrid x:Name="grid" local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TestData}" >
<DataGrid.Resources>
<local:SearchValueConverter x:Key="SearchValueConverter" />
<Style TargetType="{x:Type DataGridCell}">
<Setter Property="local:DataGridTextSearch.IsTextMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="Content.Text" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="Background" Value="Orange" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
</StackPanel>
</Window>
Result:
Edit:
If you just want to select the row based on a single Column you can modify quite easily :).
Override the Style of DataGridRow
instead of DataGridCell
.
<Style TargetType="{x:Type DataGridRow}">
First pass in the property you want into the IMultiValueConverter
this should be your DataContext
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext.MyProperty" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
Then change the Trigger
to set IsSelected
on the Row
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
Should look like this:
<DataGrid x:Name="grid" local:DataGridTextSearch.SearchValue="{Binding ElementName=SearchBox, Path=Text, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding TestData}" >
<DataGrid.Resources>
<local:SearchValueConverter x:Key="SearchValueConverter" />
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="local:DataGridTextSearch.IsTextMatch">
<Setter.Value>
<MultiBinding Converter="{StaticResource SearchValueConverter}">
<Binding RelativeSource="{RelativeSource Self}" Path="DataContext.MyProperty" />
<Binding RelativeSource="{RelativeSource Self}" Path="(local:DataGridTextSearch.SearchValue)" />
</MultiBinding>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="local:DataGridTextSearch.IsTextMatch" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
</Style.Triggers>
</Style>
</DataGrid.Resources>
</DataGrid>
Result:
Solution 2
I have been using MVVM for quite a while now, and I still prefer using it as a guideline rather than a strict practice, partly because it isn't always practical to do everything in MVVM pattern exactly, and even more so if you are not too familiar with it.
I would suggest just playing around with it until you manage to find a form of MVVM that suits you.
I don't believe it is taboo to have Code in the Code Behind of the MVVM if the code is UI related.
ScrollIntoView isn't a Bindable property so if you want to bind to it you will have to create a dependency Property to indirectly handle the binding. As for setting the selected item you could do it through something like:
View:
<TextBox Height="23" Text={Binding Path=Selected, UpdateSourceTrigger=PropertyChanged} HorizontalAlignment="Left" Margin="90,147,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" />
<DataGrid AutoGenerateColumns="True"
ItemsSource="{Binding Path=ItemList}"
SelectedItem="{Binding Path=Selected}" >
</DataGrid>
ViewModel:
private string _selected = "";
public string Selected
{
get{ return _selected; }
set
{
if(_selected == value) return;
_selected = value;
base.OnPropertyChanged("Selected");
}
}
Related videos on Youtube
Daryl Behrens
I am a hands on, logic loving coder, who specializes in the .Net stack, but also lovers to play around with other technologies.
Updated on November 24, 2021Comments
-
Daryl Behrens over 2 years
I am new to the MVVM pattern, and a little confused on when to use Code Behind. I have a very simple form right now, that includes one TextBox, and one DataGrid. What I would like is to be able to have the DataGrid change its selected item based on the TextBox.
I have done this in Code Behind and it works fine using the following code:
private void textBox1_TextChanged(object sender, TextChangedEventArgs e) { for (int i = 0; i < dataGrid1.Items.Count; i++) { string cellContent = dtReferral.Rows[i][0].ToString(); try { if (cellContent != null && cellContent.Substring(0, textBox1.Text.Length).Equals(textBox1.Text)) { object item = dataGrid1.Items[i]; dataGrid1.SelectedItem = item; dataGrid1.ScrollIntoView(item); //row.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next)); break; } } catch { } } }
Now, I just want to highlight the Item in the Datagrid that starts with text in textbox, and allow the user to press a button to edit selected item.
Is it okay to have this logic in the Code Behind file? Or would I need to do this through some sort of binding? If I should do this through the View Model with Binding, any direction would be appreciated. Thank you.
-
Daryl Behrens about 11 yearsWow, thanks for all the work you have done on this. Honestly all I want to do is select the entire row based on the text in the first column. Could I adapt your code for that.
-
sa_ddam213 about 11 yearsI have added an edit that shows how to modify to do what you need, its pretty easy to change :)
-
Vikram about 11 years@sa_ddam213 Thanks for this answer . That's why I love this site. We get so many things to try ourselves.
-
Vikram about 11 years@sa_ddam213 would it be possible to scroll the searched item into the view
-
sa_ddam213 about 11 yearsI cant fit the answer here but if you sak as a new question I can show you how :)
-
Vikram about 11 years@sa_ddam213 please find the question here stackoverflow.com/questions/15474971/…
-
MoonKnight about 11 years+1 @sa_ddam213 I have implemented your code above, but the
SearchValueConverter
Convert
method is only being fired when I load data into the grid, not when the value of the text box is changed. Any ideas? Thanks for this post - it was most helpful :] -
Hank about 10 years@sa_ddam213 your search works fantastic, I used your first highlighted cell style. I have created a popup with the search textbox in it and added one more binding to the converter in the same fashion as SearchValue, however this is causing me issues, I have posted a new question stackoverflow.com/questions/22927740/… and was hoping you may be able to shed some light on it.
-
klaydze about 8 yearsHi, the code is working perfect! What if I like to re-populate the DataGrid base on the text that the user encoded in the search textbox rather than highlighting or selecting the record in the datagrid using MVVM pattern? I can re populate the datagrid using code behind but not sure on how to implement it in MVVM pattern. TIA!
-
Galma88 over 7 yearsWhat if I want to highlight only the word I'm searching?
-
John C over 3 yearsWpfApplication17 made me laugh. You must answer a lot of these questions. So Thanks!