How to filter data from the view using MVVM Pattern?
I am trying to filter data in the DataGrid using the texboxes using the MVVM pattern. Just not sure how to do it. Would appreciate any guidance.
My current code:
Model
public class Technical_BestBeforeDates_Model
{
public int Id { get; private set; }
public string ProductCode { get; private set; }
public string ProductDescription { get; private set; }
}
ViewModel
public class Technical_BestBeforeDates_ViewModel : BaseViewModel
{
// Properties
public int Id { get; set; }
public string ProductCode { get; set; }
public string ProductDescription { get; set; }
// Private model
private Technical_BestBeforeDates_Model Technical_BestBeforeDates_Model;
// Update Method
public void GetValuesFromModel()
{
Id = Technical_BestBeforeDates_Model.Id;
ProductCode = Technical_BestBeforeDates_Model.ProductCode;
ProductDescription = Technical_BestBeforeDates_Model.ProductDescription;
}
public Technical_BestBeforeDates_ViewModel(Technical_BestBeforeDates_Model model)
{
Technical_BestBeforeDates_Model = model;
GetValuesFromModel();
}
}
RecordViewModel
public class Technical_BestBeforeDates_Record_ViewModel : BaseViewModel
{
// Hold list of view models
public List<Technical_BestBeforeDates_ViewModel> VMs { get; set; }
// Recieve data from View Model
public Technical_BestBeforeDates_Record_ViewModel()
{
var records = GetDataFromDatabase();
VMs = new List<Technical_BestBeforeDates_ViewModel>();
foreach (Technical_BestBeforeDates_Model model in records)
{
VMs.Add(new Technical_BestBeforeDates_ViewModel(model));
}
}
// Get data using dapper
public List<Technical_BestBeforeDates_Model> GetDataFromDatabase()
{
string query = @"SELECT
b.id AS ID,
ProductCode,
ProductDescription
FROM technical_bestbeforedates b";
using (var conn = new MySqlConnection(ConfigurationManager.ConnectionStrings["MySQL"].ConnectionString))
{
conn.Open();
// Using dapper to fill the model
var Records = conn.Query<Technical_BestBeforeDates_Model>(query).ToList();
return Records;
}
}
}
View
<DockPanel>
<DataGrid ItemsSource="{Binding VMs}"
AutoGenerateColumns="False"
IsReadOnly="True"
ColumnWidth="auto">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="Id" />
<DataGridTextColumn Binding="{Binding ProductCode}" Header="Product Code" />
<DataGridTextColumn Binding="{Binding ProductDescription}" Header="Product Description" />
</DataGrid.Columns>
</DataGrid>
</DockPanel>
1 answer
I wrote quick (and somewhere dirty) example to show one of possible approaches:
- Add properties corresponding to each filter:
private string productCode;
private string productDescription;
public string ProductCode
{
get => productCode;
set => SetProperty(ref productCode, value);
}
public string ProductDescription
{
get => productDescription;
set => SetProperty(ref productDescription, value);
}
- To keep it simple assume we want to filter data by pressing a button. So we have to add a command:
public ICommand FilterCommand { get; }
- Replace
List
withObservableCollection
public Technical_BestBeforeDates_Record_ViewModel()
{
VMs = new ObservableCollection<Technical_BestBeforeDates_Model>();
FilterCommand = new RelayCommand(_ => UpdateDataFromDatabase());
UpdateDataFromDatabase();
}
public void UpdateDataFromDatabase()
{
VMs.Clear();
// ToDo: replace with call to a DB
var records = DataStorage.GetFilteredData(ProductCode, ProductDescription);
foreach (Technical_BestBeforeDates_Model model in records)
{
VMs.Add(model);
}
}
- Write a UI
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Row="0"
Grid.Column="0"
Margin="5"
HorizontalAlignment="Center"
Text="Code" />
<TextBlock
Grid.Row="1"
Grid.Column="0"
Margin="5"
HorizontalAlignment="Center"
Text="Description" />
<TextBox
Grid.Row="0"
Grid.Column="1"
Margin="5"
Text="{Binding ProductCode}" />
<TextBox
Grid.Row="1"
Grid.Column="1"
Margin="5"
Text="{Binding ProductDescription}" />
<Button
Grid.Row="2"
Grid.Column="1"
MaxWidth="100"
Margin="5"
HorizontalAlignment="Right"
Command="{Binding FilterCommand}"
Content="Filter" />
</Grid>
<DataGrid
Grid.Row="1"
AutoGenerateColumns="False"
ColumnWidth="auto"
IsReadOnly="True"
ItemsSource="{Binding VMs}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Id}" Header="Id" />
<DataGridTextColumn Binding="{Binding ProductCode}" Header="Product Code" />
<DataGridTextColumn Binding="{Binding ProductDescription}" Header="Product Description" />
</DataGrid.Columns>
</DataGrid>
</Grid>
In the code above I assume that
-
BaseViewModel
class implementsINotifyPropertyChanged
interface and hasSetProperty
method. -
Technical_BestBeforeDates_Record_ViewModel
class is a "main" VM for thatUserControl
withDataGrid
and a filter panel. -
Technical_BestBeforeDates_Model
class actually contains constructor so user can create its instance.
I've create a gist with full code to make it easier to you if some parts aren't clear enough.
https://gist.github.com/FoggyFinder/3d25d3260eaec06f819106cde33b914d
7 comments
Hi, yes you're right regarding BaseViewModel, it only has the INotifyPropertChanged. I had been advised to create Technical_BestBeforeDates_ViewModel for the purpose of preventing the UI accesing the Model directly.
@gzi98 I see no reason to create duplicate types merely to avoid connection between Model
and View
in case of displaying data.
@FoggyFiner It kinda defeats the idea of using MVVM right?
@gzi98 There is no right answer to the Q. Someone think it's against the pattern but other have quite the opposite opinion. Anyway, I see no reason to create a proxy for object that can't be mutated "outside". But I'm a purist when it comes to creating objects.
How do I replace the dummy data with the real database? I have never seen such code before private static readonly Technical_BestBeforeDates_Model[] data = new Technical_BestBeforeDates_Model[]
1 comment
I wrote some thoughts but it would be better if you provide MCVE rather than some random parts of code. Let me explain: You ask about filter so part that related to DB shouldn't be included at all. It's unclear how exactly
BaseViewModel
class looks. What's a purpose ofTechnical_BestBeforeDates_ViewModel
? And feel free to correct my poor English grammar. — FoggyFinder about 1 month ago