This project is read-only.

Default ViewModel causes startup issue

May 16, 2013 at 7:31 PM
Hi all,
By default, the ListViewModel (at least the examples) all call LoadData() in the constructor. With small tables and/or few fields in the tables, this isn't a big deal.

When the StartViewModel runs, it creates ViewActions that include a new instance of the ViewModel.

Since the constructor calls the load, each model is, essentially, requesting all the data for each table before I even see a menu or toolbar.

If I remove the LoadData call from the constructor, then no data appears.

How do I change it so that the data is NOT loaded until I click on the item from the menu/toolbar/whatever?

My guess is that it goes into the ViewOpened event, but I am not quite sure how to do that the "correct" way. Nor how to tell when the view is being opened to show or just as part of the constructor.

I am sure it's quite simple, I just am not sure where to put it.

Thanks
May 16, 2013 at 11:11 PM
I am not totally sure what you mean by this:
When the StartViewModel runs, it creates ViewActions that include a new instance of the ViewModel.
The view actions in the start view model typically only have code in their execute, so when one of those actions is triggered, it calls another controller action. Only then should the controller create whatever view model it uses and thus load the data.

As far as the actual data loading goes: Yes, it would be much better to do multi-threaded loading. We have our AsyncWorker object for that which makes it easy. Of course you can also use the default Visual Studio Async features as well (although you do not get automatic progress indicators and such). Have you taken a look at the sample code that comes with the various articles we have published? I think most of them do the async stuff.


Markus
May 16, 2013 at 11:23 PM
Markus,

The following code is from StartViewModel and is how I was lead to believe we should implement menu options:

Actions.Add(new ViewAction("Customers", execute: (a, o) => Controller.Action("Customer", "List"), category: "Customer")
{
Significance = ViewActionSignificance.AboveNormal,
ActionView = Controller.LoadView(StandardViews.LargeText03),
ActionViewModel = new CustomerListViewModel()
});

In this code, the last part creates a new instance of CustomerListViewModel.

The constructor in the CustomerListViewModel calls the LoadData() method - which for the list, brings down all the customers.

This is run before the menu options are displayed as it is part of how they get defined.

So when each xxxListViewModel gets defined, it gets a copy of all the data that should appear in the list - even though the call is only to define the menu, not display the data.

I would assume that the FW then does a refresh on the loaded data prior to displaying it when the menu option is selected.

My point is that, as the menu is being built, each ViewModel that is being attached to menu options is loading a bunch of data that is not needed at the time. If my app has well over a 100 tables, each with many rows, just getting the menu to show will be slow, not to mention generating a ton of network traffic that isn't really used.

So my thought is that I want to instantiate the xxxListViewModel, but take the call to LoadData out of it. But if I do this, it doesn't work. So I need to know where I can move the LoadData call so that it is called before the view is rendered (but not on instantiation.) I am guessing I can put it in an event, but not sure which one would be appropriate within the CODE FW, that's all.

Thanks.
May 16, 2013 at 11:41 PM
Are you showing a customer list inside your action then? I don't think so, right?

The approach you are showing above is when the action itself has it's own view and view model. This would be the case if you are using your action as a tile in a Metro UI and you want to show data inside that tile that is non-standard. Typically, this would be a very simple model that would show maybe the number of total customers and the number of new customers, or something like that. Not an entire list. And I think that is not what you are trying to do, right?


Markus
May 16, 2013 at 11:48 PM
Markus,

Correct. I am trying to get a menu option that shows (as appropriate for the theme - I just happen to be using Battleship as my default).

I use the code above to add the menu item. Apparently I am implementing this incorrectly. Let me review the docs/notes I have to find out the correct way to add the menu item.

Thanks,

Fletcher
May 17, 2013 at 12:02 AM
Edited May 17, 2013 at 12:05 AM
Just do this:
Actions.Add(new ViewAction("Customers", execute: (a, o) => Controller.Action("Customer", "List"), category: "Customer") 
 {
    Significance = ViewActionSignificance.AboveNormal
}); 
Then, in the List() method of the controller, do that:
public ActionResult List()
{
    return View(new CustomerListViewModel());
}
In the CustomerListViewModel class, you load your data. Ideally async:
public class CustomerListViewModel : ViewModel
{
    public CustomerListViewModel()
    {
        Customers = new ObservableCollection<CustomerViewModel>();
        LoadData();
    }

    private void LoadData()
    {
        AsyncWorker.Execute(() => {
                List<Customer> customers = HoweverYouGetYourData();
                return customers;
            }, customers => {
                Customers.Clear();
                foreach (var customer in customers)
                    Customers.Add(new CustomerViewModel{ /* map all the properties here */ });
            }, this);
    }

    public ObservableCollection<CustomerViewModel> Customers { get; set; }
}
This way 1) your view action stays simple, 2) you are not taking a performance hit until the data is actually needed, and 3) the data is loaded async so no significant delay will be seen, and 4) since you are using our AsyncWorker it automatically allows you to do things on the background thread (1st parameter) and then funnels everything to the foreground thread where you can handle the result and assign to the UI in a thread-safe manner (2nd parameter) and since we are passing this (3rd parameter) into the async worker, it can even update a status indicator that will then get picked up by styles, so you will even see a progress indicator in the UI without having to do anything else (assuming you are using our styles).


Markus