Home > database >  Problem filtering data upon choosing an item from combo box WPF MVVM
Problem filtering data upon choosing an item from combo box WPF MVVM

Time:06-16

I have two datagrid's in two different tabitems. The datagrid in first tabitem displays some specific data on app load and should get updated if the data changes, pretty simple. The second tabitem's datagrid should be empty on app load but when an item is selected/typed from the combo box then it should filter the data as per that combo box selected item and show on the filtered data on datagrid of second tabitem (maintaining MVVM pattern).

This is what I've tried:

XAML

<TabControl>
    <TabItem Header="Pending Bills">
        <DataGrid ItemsSource="{Binding FilteredBills}" AutoGenerateColumns="False" CanUserAddRows="False">
            <DataGrid.Columns>
                <DataGridTextColumn
                        Header="Vendor"
                        Binding="{Binding Path=Party, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                <DataGridTextColumn
                        Header="Bill No."
                        Binding="{Binding Path=BillNo, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                ......
            </DataGrid.Columns>
        </DataGrid>
    </TabItem>
    <TabItem Header="Filtered Bills">
    ........
            <DataGrid Grid.Row="0" ItemsSource="{Binding AllBills}" AutoGenerateColumns="False" CanUserAddRows="False">
                <DataGrid.Columns>
                    <DataGridTextColumn
                        Header="Vendor"
                        Binding="{Binding Path=Party, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                    <DataGridTextColumn
                        Header="Bill No."
                        Binding="{Binding Path=BillNo, Mode=OneWay}"
                        Width="275"
                        IsReadOnly="True" />
                    ......
                </DataGrid.Columns>
            </DataGrid>
            <StackPanel Orientation="Horizontal">
                <ComboBox x:Name="cmbBox" ItemsSource="{Binding ComboItems}" SelectedItem="{Binding SelectedCBItem}"/>
            </StackPanel>
        </Grid>
    </TabItem>
</TabControl>

A base viewmodel to enable the PropertyChangedEventHandler

public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler PropertyChanged;

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

Basic model:

public class Bills : ViewModelBase
{
    private int _id;
    public int Id
    {
        get
        {
            return _id;
        }
        set
        {
            _id = value;
            OnPropertyChanged("Id");
        }
    }

    private string _party;
    public string Party
    {
        get
        {
            return _party;
        }
        set
        {
            _party = value;
            OnPropertyChanged("Party");
        }
    }

    private string _billNo;
    public string BillNo
    {
        get
        {
            return _billNo;
        }
        set
        {
            _billNo = value;
            OnPropertyChanged("BillNo");
        }
    }

    .......

}

The billsviewmodel and a helper data access class called Model:

public class BillsViewModel : ViewModelBase
    {

        public BillsViewModel()
        {
            cvsAll.Source = m.AllBills;
            cvsFiltered.Source = m.FilteredBills;
        }

        Model m = new Model();

        CollectionViewSource cvsAll = new CollectionViewSource();
        public ICollectionView AllBills
        {
            get { return cvsAll.View; }
        }


        public List<string> ComboItems
        {
            get { return m.UniqueValues(); }
        }

        CollectionViewSource cvsFiltered = new CollectionViewSource();
        public ICollectionView FilteredBills
        {
            get { return cvsFiltered.View; }
        }


        private string _selectedCBItem;
        public string SelectedCBItem
        {
            get { return _selectedCBItem; }
            set
            {
                _selectedCBItem = value;
                cvsFiltered.Source = m.GetBills_Pen(value);
                OnPropertyChanged("SelectedCBItem");
            }
        }
    }

    public class Model
    {
        private List<Bills> _allBills;

        public List<Bills> AllBills
        {
            get
            {
                if (_allBills == null) LoadAllBillsFromDatabase();
                
                return _allBills;
            }
        }

        private List<Bills> _filteredBills;

        public List<Bills> FilteredBills
        {
            get
            {
                if (_filteredBills == null) LoadUnpaidBillsFromDatabase();

                return _filteredBills;
            }
        }

        public void LoadAllBillsFromDatabase()
        {
            SQLiteConnection m_dbConnection = new SQLiteConnection(@"Data Source=Bills.db;");
            SQLiteCommand sqlCom = new SQLiteCommand(@"Select * From billdata", m_dbConnection);
            SQLiteDataAdapter sda = new SQLiteDataAdapter(sqlCom);
            DataTable dt = new DataTable();
            sda.Fill(dt);
            _allBills = new List<Bills>();
            
            foreach (DataRow row in dt.Rows)
            {
                _allBills.Add(new Bills()
                {
                    Id = Convert.ToInt32(row["Id"]),
                    Party = (string)row["Party"],
                    BillNo = (string)row["BillNo"],
                    .......
                });
                
            }
            m_dbConnection.Close();
        }

        public void LoadUnpaidBillsFromDatabase()
        {
            SQLiteConnection m_dbConnection = new SQLiteConnection(@"Data Source=Bills.db;");
            SQLiteCommand sqlCom = new SQLiteCommand(@"Select * From billdata where PaidOn != ''", m_dbConnection);
            SQLiteDataAdapter sda = new SQLiteDataAdapter(sqlCom);
            DataTable dt = new DataTable();
            sda.Fill(dt);
            _filteredBills = new List<Bills>();

            foreach (DataRow row in dt.Rows)
            {
                _filteredBills.Add(new Bills()
                {
                    Id = Convert.ToInt32(row["Id"]),
                    Party = (string)row["Party"],
                    BillNo = (string)row["BillNo"],
                    .......
                });

            }
            m_dbConnection.Close();
        }

        public List<string> UniqueValues()
        {
            return _allBills.Select((s) => s.Party).Distinct().OrderBy((s) => s).ToList();
        }
         

        public List<Bills> GetBills_Pen(string filterValue)
        { 
            return _allBills.FindAll(s => s.Party == filterValue).ToList();
        }
    }

There are 2 issues:

  1. The datagrid on 2nd tabitem is displaying all data on app load
  2. When I select or type a valid item in the combo box the datagrid on 2nd tabitem is not showing the filtered data.

Have I not implemented the ICollectionView the right way or what am I doing wrong here and most importantly how do I fix this ?

CodePudding user response:

This should work. I also added a function to ViewModelBase to make the code shorter

public class ViewModelBase
{
    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        var handler = PropertyChanged;
        if (handler != null)
            handler(this, new PropertyChangedEventArgs(propertyName));
    }
    protected virtual void SetValue<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
    {
        field = value;
        OnPropertyChanged(propertyName);
    }
}

public class BillsViewModel : ViewModelBase
{
    private List<BillViewModel> _allBills;

    public BillsViewModel()
    {
        _allBills = m.AllBills;
        AllBills = new ListCollectionView(_allBills);
        ComboItems = _allBills.Select(b => b.Party).Distinct().OrderBy(b => b).ToList();
        FilteredBills = new ListCollectionView(_allBills)
        {
            Filter = o => ((BillViewModel)o).Party == SelectedCBItem
        };
    }

    Model m = new Model();

    public ICollectionView AllBills { get; private set; }
    public ICollectionView FilteredBills { get; private set; }

    public List<string> ComboItems { get; private set; }

    private string _selectedCBItem;
    public string SelectedCBItem
    {
        get { return _selectedCBItem; }
        set
        {
            SetValue(ref _selectedCBItem, value);
            FilteredBills.Refresh();
        }
    }
}

CodePudding user response:

The datagrid on 2nd tabitem is displaying all data on app load

That's because you set the source of the AllBills property of the view model to the AllBills property of the model which contains (?) all items. If you want it to be empty, then don't set the Source to a list that contains any items:

public BillsViewModel()
{
    cvsFiltered.Source = m.FilteredBills;
}

When I select or type a valid item in the combo box the datagrid on 2nd tabitem is not showing the filtered data

If I understand your issue correctly, you should then set the Source of cvsAll in the setter of the SelectedCBItem property:

private string _selectedCBItem;
public string SelectedCBItem
{
    get { return _selectedCBItem; }
    set
    {
        _selectedCBItem = value;
        cvsAll.Source = m.GetBills_Pen(value); // <--
        OnPropertyChanged("SelectedCBItem");
    }
}
  • Related