Proper DataGrid search from TextBox in WPF using MVVM Proper DataGrid search from TextBox in WPF using MVVM wpf wpf

Proper DataGrid search from TextBox in WPF using MVVM


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:

enter image description here enter image description here

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:

enter image description here


I have been using MVVM for quiet a while now, and I still prefer using at 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 to 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 you 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");      } }