Proper Datagrid Search from Textbox in Wpf Using Mvvm

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:

Sample Image Sample Image

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:

Sample Image

How to display textbox data in wpf datagrid using MVVM model?

In order to do what you want, the DataGrid should be bound to the collection (e.g. ObservableCollection) where you store all the items, when you click the "Add" button, you call Entities.Add(NewEntity);, where Entities is the collection and NewEntity is a property of the view model. You bind the text boxes to the properties of NewEntity. i've made a small gist here to demonstrate.

WPF MVVM Textbox and Datagrid binding

I had missed calling OnProperty in setter of CurrentItem Object . So the UI was never informed about this .

Search in datagrid

You can create an additional property within the ViewModel called SelectedPerson

        private Person_selectedperson;

public Person SelectedPerson
{
get { return _selectedperson; }
set
{
_selectedperson = value;
OnPropertyChanged("SelectedPerson");
}
}

This property should represent one instance within the collection of Contactpersons. Then, bind this property to SelectedItem within the datagrid

<DataGrid ItemsSource="{Binding Contactpersons}" SelectedItem="{Binding SelectedPerson}"

Then within your event/method related to the textbox search button. Run a linq query or something similar to find the first match within the collection and set SelectedPerson to that item (I did it within a search click event while quickly writing the code, however, you may want to place in command in viewmodel to better adhere to mvvm)...

        private void Button_Click_1(object sender, RoutedEventArgs e)
{
string searchValue = textbox1.Text;
vm.SelectedPerson = vm.Contactpersons.Where(a => a.LastName.Contains(searchValue)).FirstOrDefault();
}

There will be additional error handling required beyond this example (checking if there is no matches etc)

TextChanged event of TextBox in GridView WPF MVVM

You trigger the function in the setter of the property that you bind to the textbox. You also have to set UpdateSourceTrigger of the binding to PropertyChanged in order to get it to trigger everytime you change the content of the textbox.

The function triggered in the setter should update the ObservableCollection, which will cause the DataGrid to update its content.

Se the code example below, code wont compile but shows the general idea.

xaml:

<DataGrid ItemsSource="{Binding Rows}" AutoGenerateColumns="False" >
<DataGrid.Columns>
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBox Text="{Binding Text}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>

SubviewModel.cs:

public SubViewModel(ViewModel vm)
{
_vm = vm;
}

private string _text;
private ViewModel _vm;

public string Text
{
get
{
return _text;
}
set
{
if (_text == value)
{
return;
}

_text = value;
OnPropertyChanged("Text");
RefreshResult();
}

private void RefreshResult()
{
// Do something with the _text and manipulate the _vm.Rows?
}

ViewModel.cs:

private ObservableCollection<SubViewModel> _rows;
public ViewModel()
{
//Initialize the sub view models
_rows = new ObservableCollection<SubViewModel>();

//Populate the list somehow
foreach (var r in sourceOfRows)
{
_rows.Add(new SubViewModel(this));
}
}

public ObservableCollection<SubViewModels> Rows
{
get
{
return _rows;
}
}

Select a row in dataGrid by searching for a string programatically

First of all, if you're doing something in WPF you'd better do it the WPF Way.

If you're not interested in learning XAML and MVVM, then you'd rather go back to dinosaur useless technologies.

<Window x:Class="MiscSamples.DataGridSearch"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="DataGridSearch" Height="300" Width="300">
<DockPanel>
<DockPanel DockPanel.Dock="Top">
<TextBlock Text="Search:" DockPanel.Dock="Left"/>
<TextBox Text="{Binding SearchString, UpdateSourceTrigger=PropertyChanged}"/>
</DockPanel>

<DataGrid ItemsSource="{Binding ItemsView}" AutoGenerateColumns="True"/>
</DockPanel>
</Window>

Code Behind:

public partial class DataGridSearch : Window
{
public DataGridSearch()
{
InitializeComponent();

DataContext = new DataGridSearchViewModel();
}
}

ViewModel:

public class DataGridSearchViewModel: PropertyChangedBase
{
private string _searchString;
public string SearchString
{
get { return _searchString; }
set
{
_searchString = value;
OnPropertyChanged("SearchString");
ItemsView.Refresh();
}
}

private ICollectionView _itemsView;
public ICollectionView ItemsView
{
get { return _itemsView; }
}

private ObservableCollection<DataGridSearchModel> _items;
public ObservableCollection<DataGridSearchModel> Items
{
get { return _items ?? (_items = new ObservableCollection<DataGridSearchModel>()); }
}

public DataGridSearchViewModel()
{
_itemsView = CollectionViewSource.GetDefaultView(Items);
_itemsView.Filter = x => Filter(x as DataGridSearchModel);

Enumerable.Range(0, 100)
.Select(x => CreateRandomItem())
.ToList()
.ForEach(Items.Add);
}

private bool Filter(DataGridSearchModel model)
{
var searchstring = (SearchString ?? string.Empty).ToLower();

return model != null &&
((model.LastName ?? string.Empty).ToLower().Contains(searchstring) ||
(model.FirstName ?? string.Empty).ToLower().Contains(searchstring) ||
(model.Address ?? string.Empty).ToLower().Contains(searchstring));
}

private DataGridSearchModel CreateRandomItem()
{
return new DataGridSearchModel
{
LastName = RandomGenerator.GetNext(1),
FirstName = RandomGenerator.GetNext(1),
Address = RandomGenerator.GetNext(4)
};
}
}

Data Item:

public class DataGridSearchModel:PropertyChangedBase
{
public string LastName { get; set; }

public string FirstName { get; set; }

public string Address { get; set; }
}

PropertyChangedBase (MVVM helper class):

public class PropertyChangedBase:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;

protected virtual void OnPropertyChanged(string propertyName)
{
Application.Current.Dispatcher.BeginInvoke((Action) (() =>
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
}));
}
}

Random Generator (just to generate random strings)

public static class RandomGenerator
{
private static string TestData = "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum";
private static List<string> words;
private static int maxword;
private static Random random = new Random();

static RandomGenerator()
{
words = TestData.Split(' ').ToList();
maxword = words.Count - 1;
}

public static string GetNext(int wordcount)
{
return string.Join(" ", Enumerable.Range(0, wordcount)
.Select(x => words[random.Next(0, maxword)]));
}

public static int GetNextInt(int min, int max)
{
return random.Next(min, max);
}
}

Result:

Sample Image

  • Fully MVVM.
  • There's not a single line of code that manipulates any UI element. This is the WPF way.
  • UpdateSourceTrigger=PropertyChanged in the TextBox binding makes it search as you type.
  • Fully strongly typed object model much better than using DataTables and stuff like that.
  • WPF rocks. Just copy and paste my code in a File -> New Project -> WPF Application and see the results for yourself.
  • Forget the winforms mentality, it's irrelevant, clumsy, generates bad code and just feels noob and immature.

How to bind a datagrid row with text boxes in wpf mvvm

Welcome to StackOverflow! Usually people won't answer questions without the following.

  • A clear summary of what you're attempting to do.
  • All of the relevant code
  • Any exceptions that you are experiencing
  • All of the research links you used in order to get where you are

That said, I will give you a demo of what you're asking for.

Here is the XAML

<Window x:Class="datagriddemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ViewModel="clr-namespace:datagriddemo"
Title="MainWindow" Height="350" Width="420" Background="Gray">

<!--This is where we define the resource that the XAML here is going to
use. As you can see, I am importing our view model-->
<Window.Resources>
<ViewModel:ProductGridViewModel x:Key="ProductViewModel"/>
</Window.Resources>


<!--This is the main layout of the XAML. In the Grid below,
I set the "DataContext" to our static resource we just defined above.
What this does is tell everyone inside this grid, "If you don't define
your own data context, you're going to use mine" Because of this, all
of the elements inside this grid will have access to the public properties
of the ViewModel-->
<Grid DataContext="{StaticResource ResourceKey=ProductViewModel}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="100"/>
</Grid.ColumnDefinitions>

<!-- This is the datagrid that we are displaying. The two important things
to note here are "ItemsSource" and "SelectedItem". "ItemsSource" is the collection
that we want to display in our grid. This is where are product models are stored.
SelectedProduct is going to be where the selected grid row is stored so we can
access its data with the text boxes defined below. -->
<DataGrid
Width="500"
Grid.Column="0"
AutoGenerateColumns="False"
ItemsSource="{Binding Products}"
SelectedItem="{Binding SelectedProduct, Mode=TwoWay}">
<DataGrid.Columns>
<DataGridTextColumn IsReadOnly="True" Header="Product ID" Binding="{Binding ProductID, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn IsReadOnly="True" Header="Product Name" Binding="{Binding ProductName, UpdateSourceTrigger=PropertyChanged}" />
<DataGridTextColumn IsReadOnly="True" Header="Total Sold" Binding="{Binding TotalSold, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>

<!-- This stack panel contains the text boxes we are going to use to edit our data. Notice that the
bindings point to SelectedProduct.Property. This is because we are accessing properties inside of
the SelectedProduct property in our ViewModel. When we edit these text boxes the data in the grid
will automatically change. -->
<StackPanel Height="100" Background="Wheat" Margin="10" Orientation="Vertical" Grid.Column="1">
<TextBlock FontWeight="Bold" Width="100" TextWrapping="Wrap">Update your product info!</TextBlock>
<TextBox Width="100" Text="{Binding SelectedProduct.ProductName, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
<TextBox Width="100" Text="{Binding SelectedProduct.TotalSold, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</StackPanel>
</Grid>
</Window>

Next is our ViewModel. If the view is the frosting on the cake then you can think of the view model as the cake around the pudding. Your view model is where your logic lives. It will do sorting, crunching and other stuff.

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace datagriddemo
{
public class ProductGridViewModel
{
private ProductModel _SelectedProduct;
private ObservableCollection<ProductModel> _Products;

/// <summary>
/// Notice that though this updates the grid as you
/// add or remove elements, it doesn't call onproperty
/// changed in the setter... Magic? Nope, Observable
/// collections call OnPropertyChanged for you.
///
/// Caveat: They will NOT call on property changed
/// for you if you do Products = new ObservableCollection...
/// Only when you do Products.Add(yourProduct)...
/// </summary>
public ObservableCollection<ProductModel> Products
{
get { return _Products; }
set { _Products = value; }
}



/// <summary>
/// This is the selected row in the grid. It automatically changes
/// when you select new rows because we set the grid SelectedItem property
/// to Mode=TwoWay
/// </summary>
public ProductModel SelectedProduct
{
get { return _SelectedProduct; }
set { _SelectedProduct = value; }
}


/// <summary>
/// View Models constructor. It get's called automatically when the view
/// is initialized because we declared it as a static resource in the XAML.
/// </summary>
public ProductGridViewModel()
{
//DONT FORGET TO NEW UP YOUR OBSERVABLE COLLECTIONS!!
Products = new ObservableCollection<ProductModel>();

//Don't forget to generate the data!
GenerateProducts();
}

/// <summary>
/// Use this method to generate dummy data
/// </summary>
private void GenerateProducts()
{
for (int x = 0; x < 100; x++)
{

this.Products.Add(new ProductModel(x,"Product #"+x,x+50));
}
}

}
}

Finally there is your Model. This is your actual data. This, yes this, is your pudding.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace datagriddemo
{
public class ProductModel : INotifyPropertyChanged
{
private Int32 _ProductID;
private String _ProductName;
private Int32 _TotalSold;

/// <summary>
/// Note for the properties below:
/// Notice that first, the property names match those bound in the XAML
/// This is all part of the grand scheme.
///
/// When the OnProperty changed is called, the UI knows to go search for
/// those properties. It's important that these all have the correct spelling
/// and casing.
/// </summary>

public Int32 TotalSold
{
get { return _TotalSold; }
set
{
_TotalSold = value;
OnPropertyChanged("TotalSold");
}
}


public String ProductName
{
get { return _ProductName; }
set
{
_ProductName = value;
OnPropertyChanged("ProductName");
}
}

public Int32 ProductID
{
get { return _ProductID; }
set
{
_ProductID = value;
OnPropertyChanged("ProductID");
}
}


/// <summary>
/// Just a normal constructor to load up our properties.
/// </summary>
/// <param name="productID"></param>
/// <param name="productName"></param>
/// <param name="totalSold"></param>
public ProductModel(Int32 productID, String productName, Int32 totalSold)
{
this.ProductID = productID;
this.ProductName = productName;
this.TotalSold = totalSold;
}

/// <summary>
/// This is for updating the UI
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

/// <summary>
/// When a property changes in this object, if you want it reflected on the
/// UI you need to call this function
/// </summary>
/// <param name="propertyName"></param>
public void OnPropertyChanged(String propertyName)
{
PropertyChangedEventHandler handler = PropertyChanged;

if (handler != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
}

Finally here is the expected result

In this first image we see our grid loaded up with its new data

Sample Image

If you click on a row you will see it's data populate the two text boxes on the side.

Sample Image

Finally if you change the data in the text boxes you will see it update in the grid immediately.

Sample Image

And that's it! A complete MVVM datagrid to text box and back solution. Hope this helps. Please remember what I said about what is expected from you as a question asker and have fun! Welcome to WPF and MVVM.



Related Topics



Leave a reply



Submit