fanarek
fanarek

Reputation: 367

DataGrid textbox filtering does not work

I have problem with DataGrid textbox filtering since I've change the way I do it.

There is big ViewModel, which instance is created in MainWindow during app initialization.

public MainWindow()
{
    InitializeComponent();
    ...
    viewModel = new ViewModel();
    viewModel.Recipients = GetRecipients();
    ...
}

I don't know if it's good way to do it, but for me it's working so I stick with that. I'm doing stuff this way, because I wanted to load big data from database once at program start so other elements could use it entire time. GetRecipients is just used for that.

This is how my ViewModel (important parts) looks like:

public class ViewModel : INotifyPropertyChanged
{

    private ICollectionView recipientsView;

    private CultureInfo culture;

    private ICommand _command;

    private ObservableCollection<Recipient> _recipients;

    public ObservableCollection<Recipient> Recipients
    {
        get { return _recipients; }
        set
        {
            _odbiorcy = value;
            OnPropertyChanged("Recipients");
        }
    }

    private bool _isEmpty;

    public bool IsEmpty
    {
        get { return _isEmpty; }
        set
        {
            _isEmpty = value;
            OnPropertyChanged("IsEmpty");
        }
    }

    private string _filter;

    public string Filter
    {
        get { return _filter; }
        set
        {
            _filter = value;
            recipientsView.Refresh();
            OnPropertyChanged("Filter");
        }
    }

    private Recipient_selected;

    public Recipient Selected
    {
        get { return _selected; }
        set
        {
            _selected = value;
            OnPropertyChanged("Selected");
        }
    }



    public ViewModel()
    {
        Recipients = new ObservableCollection<Recipient>();
        recipientsView= CollectionViewSource.GetDefaultView(Recipients);
        recipientsView.Filter = o => String.IsNullOrEmpty(Filter) ? true : ((Recipient)o).Name.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) >= 0 ||
                                                                         ((Recipient)o).City.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) >= 0 ||
                                                                         ((Recipient)o).Street.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) >= 0 ||
                                                                         ((Recipient)o).PostCode.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) >= 0 ||
                                                                         ((Recipient)o).ContactPerson.IndexOf(Filter, StringComparison.OrdinalIgnoreCase) >= 0;
    }




    public void OnPropertyChanged(string name)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(name));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

}

And...

Recipients.xaml

<UserControl x:Class="DHL.Recipients"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
         xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
         xmlns:local="clr-namespace:DHL"
         mc:Ignorable="d" 
         d:DesignHeight="460" d:DesignWidth="750">
<Grid Background="White">
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>
    <Grid Grid.Column="0">
        <Grid.RowDefinitions>
            <RowDefinition Height="60" />
            <RowDefinition Height="350"/>
            <RowDefinition Height="50" />
        </Grid.RowDefinitions>
        <Grid Grid.Row="0">
            <Label Content="Search recipient:" Margin="25,0,0,0"></Label>
            <TextBox x:Name="FilterTextBox" Text="{Binding Filter, UpdateSourceTrigger=PropertyChanged}" Width="145" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="25,25,0,0"/>
        </Grid>
        <Grid Grid.Row="1">
            <DataGrid IsReadOnly="True" x:Name="RecipientsGrid" AutoGenerateColumns="False" EnableRowVirtualization="True"
                      EnableColumnVirtualization="True" ItemsSource="{Binding Recipients}" Height="350" VerticalAlignment="Bottom"
                      SelectedItem="{Binding Selected,Mode=TwoWay}"
                      HorizontalScrollBarVisibility="Visible" MouseDoubleClick="Row_DoubleClick">
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Name" Width="250" Binding="{Binding Name,UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="Street" Width="150" Binding="{Binding Street,UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="House Number" Width="150" Binding="{Binding HouseNumber,UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="Apartament Number" Width="150" Binding="{Binding ApartamentNumber,UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="Postal Code" Width="150" Binding="{Binding PostalCode,UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="City" Width="150" Binding="{Binding City,UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="Contact Person" Width="150" Binding="{Binding ContactPerson, UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="Email" Width="150" Binding="{Binding Email, UpdateSourceTrigger=PropertyChanged}"/>
                    <DataGridTextColumn Header="Phone Number" Width="150" Binding="{Binding PhoneNumber, UpdateSourceTrigger=PropertyChanged}"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
        <Grid Grid.Row="2">
            <Button x:Name="CloseButton" Click="CloseButton_Click"
                    VerticalAlignment="Bottom" Margin="0,0,0,10" Width="100" Height="25" Content="Close"></Button>
        </Grid>
    </Grid>
</Grid>

And Recipients class:

public partial class Recipients : UserControl
{

    ViewModel viewModel;

    public Recipients (ViewModel model)
    {
        InitializeComponent();
        this.viewModel = model;
        this.DataContext = viewModel;
    }

    public Recipients ()
    {
        InitializeComponent();
        ViewModel view = new ViewModel();
        this.DataContext = view;
    }

    private void CloseButton_Click(object sender, RoutedEventArgs e)
    {
        var parent = this.Parent as Window;

        if (parent != null)
        {
            parent.DialogResult = true;
            parent.Close();
        }
    }

    private void Row_DoubleClick(object sender, MouseButtonEventArgs e)
    {
        CloseButton_Click(this, null);
    }
}

I've tried to create new Recipients instance in both ways (with ViewModel in parameter and without it). Both stopped working after I've changed whole project (creating ViewModel instance once and use it in every place in project - there is part I'm still trying to deal with). I tought it may work, because of PropertyChanged but it seems I was wrong.

The question is: Is this good way to create ViewModel once instead of creating ViewModel in every UserControl I want to use it ? If it is, what's wrong with my code ?

Upvotes: 0

Views: 109

Answers (1)

Dennis
Dennis

Reputation: 37760

what's wrong with my code

You're filtering wrong collection.
Here's where collection and its view are initially created:

public ViewModel()
{
    Recipients = new ObservableCollection<Recipient>();
    recipientsView= CollectionViewSource.GetDefaultView(Recipients);

    // rest of code not shown
}

Then you're assigning new instance to the collection:

// MainWindow ctor
viewModel.Recipients = GetRecipients();

Your grid is bound to the collection, not its view, and collection ultimately contains result of GetRecipients:

ItemsSource="{Binding Recipients}"

To fix this, do the following:

1) Fix data binding. Bind grid to collection view, not the collection itself:

// view model
public ICollectionView RecipientsView
{ 
    get
    {
        if (recipientsView == null)
        {
            // DO NOT create collection view inside constructor
            recipientsView = CollectionViewSource.GetDefaultView(Recipients);
            recipientsView.Filter = // filtering code here;
        }

        return recipientsView;
    }
}

<!-- DataGrid XAML -->
ItemsSource="{Binding RecipientsView}"

2) Make Recipients get-only. Usually you don't need to change collection object reference inside view model. You need to change collection content:

public ObservableCollection<Recipient> Recipients
{
    get
    {
        if (_recipients == null)
        {
            // DO NOT create collection inside constructor
            _recipients = new ObservableCollection<Recipient>();          
        }

        return _recipients;
    }
}

3) Change collection population code. Add UpdateRecipients method to view model:

   public void UpdateRecipients(IEnumerable<Recipient> newRecipients)
   {
       Recipients.Clear();

       foreach (var item in newRecipients)
       {
           Recipients.Add(item);
       }
   }

And call it on initialization:

viewModel = new ViewModel();
viewModel.UpdateRecipients(GetRecipients());

Upvotes: 1

Related Questions