Reputation: 40291
I have a UserControl
view which require data to be pulled from the database to set the ObservableCollection
in order to render a DataGrid.
I want to set the ObservableCollection
when the UserControl
is loaded not when the view model is constructed. Also, I prefer to asynchronously set the ObservableCollection
so the UserContror
opens immediacy then then the data-grid is set when the data becomes available.
In my view model I create a public method called SetVendors()
which fetches the data from the database and sets ObservableCollection
just before the view is notified. However, I am using the view class directly to set the SetVendors()
method which sound like a bad practice for some reason to me.
Questions Is it possible to set the data on view-load directly from the view-model? If so, how? Also, is it possible to set the data asynchronously so the view loads quick?
Here is my ViewModel code
public class VendorsListViewModel : ViewModel
{
protected IUnitOfWork UnitOfWork { get; set; }
public int CurrentPage { get; set; }
public int ModelsPerPage { get; set; }
public IPagedList PageMeta { get; set; }
private ObservableCollection<Vendor> _Vendors { get; set; }
public ObservableCollection<Vendor> Vendors
{
get
{
return _Vendors;
}
set
{
_Vendors = value;
NotifyPropertyChanged();
}
}
public VendorsListViewModel(IUnitOfWork unitOfWork)
: base()
{
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 20;
}
public VendorsListViewModel()
: this(new UnitOfWork())
{
}
public void SetVendors()
{
var vendors = UnitOfWork.Vendors.Get(CurrentPage, ModelsPerPage);
Vendors = new ObservableCollection<Vendor>(vendors);
PageMeta = vendors.GetMetaData();
}
}
This is how currently set the data from the code behind the view which sound like a bad practice to me.
public partial class Index : UserControl
{
public Index()
{
InitializeComponent();
}
// This event is called from the view when the form is loaded using `Loaded="ViewLoaded"` XAML code
private void ViewLoaded(object sender, EventArgs e)
{
(DataContext as VendorsListViewModel).SetVendors();
}
}
Upvotes: 0
Views: 2429
Reputation: 15190
Loading the data from the view´s code behind is indeed a bad practice as you are breaking the MVVM separation of concerns.
A better way to do that would be using a command, or, more precisely an event to command, check this article for a nice explanation.
To achieve this you can use the System.Windows.Interactivity library that comes with Blend for Visual Studio SDK for .NET, if you don't have it just run the Visual Studio installer and select it.
Then add a reference to the library by going to Add reference -> Assemblies -> Extensions.
Once you have this, things are simple really, you need to just:
First add the namespace to your View:
xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity"
Then, bind the event to a command in your view model like this:
<i:Interaction.Triggers>
<i:EventTrigger EventName="Loaded">
<i:InvokeCommandAction Command="{Binding LoadedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
Now in your ViewModel we have a little problem, we need to provide a command to be called when the event is triggered, a command is an implementation of the ICommand
interface, all MVVM frameworks out there will provide you with ready to roll ICommand
implementations so you can either use one of these frameworks or you can implement ICommand
yourself if you just need it for this particular example, wait! you don't need to implement it, you can just get it from this question ,-)
For my example, however, I will use MVVM Light toolkit which I recommend you to use as it will relief you from most of the MVVM annoyances that you will need to deal with in any real-world application.
You can get MVVM Light from NuGet:
Install-Package MvvmLight -Version 5.4.1
So now you can use MvvmLight implementation of ICommand
which is called RelayCommand
, you would do so in your ViewModel as follows:
[...]
public ICommand LoadedCommand { get; private set; }
public VendorsListViewModel(IUnitOfWork unitOfWork) : base() {
UnitOfWork = unitOfWork;
CurrentPage = 1;
ModelsPerPage = 25;
LoadedCommand = new RelayCommand(this.Loaded);
}
private void Loaded()
{
SetVendors();
}
As per your second question: absolutely, you can load the data asynchronously if you want to. This is a whole different question though, take a look at this article for more info: https://www.thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/
Upvotes: 1