Constructor in ViewModel - c#

Is it possible to have a constructor in the ViewModel, which initializes the data service?
My data service is accessing the web-service of the data storage in a manner similar to this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.ObjectModel;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Commands;
using MobSales.Logic.DataService;
using MobSales.Logic.Base;
using MobSales.Logic.Model;
namespace MobSales.Logic.ViewModels
{
public class CustomersViewModel:MvxViewModel
{
ICustomerService custService;
public CustomersViewModel(ICustomerService custService)
{
this.custService = custService;
if (custService != null)
{
custService.LoadCustomerCompleted += new EventHandler<CustomerLoadedEventArgs>(custService_LoadCustomerCompleted);
}
loadCustomerCommand = new MvxRelayCommand(LoadCustomer);
loadCustomerCommand.Execute();
}
private ObservableCollection<Customer> customers;
public ObservableCollection<Customer> Customers
{
get { return customers; }
set
{
customers = value;
FirePropertyChanged("Customers");
}
}
private CustomerViewModel customer;
public CustomerViewModel Customer
{
get { return customer; }
set
{
customer = value;
FirePropertyChanged("Customer");
}
}
private MvxRelayCommand loadCustomerCommand;
public MvxRelayCommand LoadCustomerCommand
{
get { return loadCustomerCommand; }
}
public void LoadCustomer()
{
custService.LoadCustomer();
}
void custService_LoadCustomerCompleted(object sender, CustomerLoadedEventArgs e)
{
if (e.Error != null)
{
return;
}
List<Customer> loadedCustomers = new List<Customer>();
foreach (var cust in e.Customers)
{
loadedCustomers.Add(new Customer(cust));
}
Customers = new ObservableCollection<Customer>(loadedCustomers);
}
}
I am getting an exception but can only see the following partial description:
Cirrious.MvvmCross.Exceptions.MvxException: Failed to load ViewModel for type MobSales.Logic.ViewModels.CustomersViewModel from locator MvxDefau…
The binding from View to ViewModel is realized as I've shown in this post: MVVMCross Bindings in Android
Thanks!

One of the unusual (opinionated) features of MvvmCross is that by default it uses ViewModel constructor parameters as part of the navigation mechanism.
This is explained with an example in my answer to Passing on variables from ViewModel to another View (MVVMCross)
The basic idea is that when a HomeViewModel requests a navigation using:
private void DoSearch()
{
RequestNavigate<TwitterViewModel>(new { searchTerm = SearchText });
}
then this will cause a TwitterViewModel to be constructed with the searchTerm passed into the constructor:
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
At present, this means that every ViewModel must have a public constructor which has either no parameters or which has only string parameters.
So the reason your ViewModel isn't loading is because the MvxDefaultViewModelLocator can't find a suitable constructor for your ViewModel.
For "services", the MvvmCross framework does provide a simple ioc container which can be most easily accessed using the GetService<IServiceType>() extension methods. For example, in the Twitter sample one of the ViewModel contains:
public class TwitterViewModel
: MvxViewModel
, IMvxServiceConsumer<ITwitterSearchProvider>
{
public TwitterViewModel(string searchTerm)
{
StartSearch(searchTerm);
}
private ITwitterSearchProvider TwitterSearchProvider
{
get { return this.GetService<ITwitterSearchProvider>(); }
}
private void StartSearch(string searchTerm)
{
if (IsSearching)
return;
IsSearching = true;
TwitterSearchProvider.StartAsyncSearch(searchTerm, Success, Error);
}
// ...
}
Similarly, you can see how the conference service data is consumed in the Conference BaseViewModel
If your preference is to use some other IoC container or some other construction mechanism for your ViewModels, then you can override the ViewModel construction within MvvmCross.
Take a look at this question (and answers) for ideas on how to do this - How to replace MvxDefaultViewModelLocator in MVVMCross application
e.g. if you want to, then it should be fairly easy for you to adjust the MyViewModelLocator example in that question to construct your ViewModel with your service.

Related

Cannot make my ViewModelLocator work

I have a ViewModel called MainViewModel (of course) which contains multiple Contructors as per the below:
[ImportingConstructor]
public MainViewModel(IWindowManager windowManager)
: this(windowManager, new DataProvider(), new LocalJsonPersistenceManager())
{
}
[PreferredConstructorAttribute]
public MainViewModel(IWindowManager windowManager, IInformationProvider infoProvider,
IPersistenceManager persistenceManager)
{
//generating data, handling clicks etc.
}
Inside that ViewModel is a public item that is constantly being updated (whenever a user clicks on a certain button and takes some actions on the form):
public Item ClickedItem
{
get { return clickedItem; }
set
{
clickedItem = value;
NotifyOfPropertyChange(() => ClickedItem);
if (ClickedItem != null)
{
FindNextItem();
}
}
}
Now i have a UserControl I am building that contains a ListView that I personnalised to make it a sticky headered listview (header moves up whenever the next header is reached blabla ...). because I can only do this via a GroupStyled ListView, I must build the data for the ListView in the C# code behind.
EDIT:
I am trying it using a ViewModelLocator as such:
public class ViewModelLocator
{
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
if (ViewModelBase.IsInDesignModeStatic)
{
// Create design time view services and models
//SimpleIoc.Default.Register<IDataService, DesignDataService>();
}
else
{
// Create run time view services and models
//SimpleIoc.Default.Register<IDataService, DataService>();
}
SimpleIoc.Default.Register<MainViewModel>();
}
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
public static void Cleanup()
{
// TODO Clear the ViewModels
}
}
And I am calling up the data's specific value as such:
var vm1 = (new ViewModelLocator()).Main;
testtxt.Text = vm1.ClickedItem.Name;
But it keeps giving me an error message on runtime on the line:
return ServiceLocator.Current.GetInstance<MainViewModel>();
in the ViewModelLocator's block:
public MainViewModel Main
{
get
{
return ServiceLocator.Current.GetInstance<MainViewModel>();
}
}
With the error message {"Type not found in cache: Caliburn.Micro.IWindowManager."} and an InnerException message of null.
It looks like MEF cannot construct the IWindowManager, which is a dependency of your ViewModel.
Try registering at least the default instance from Caliburn.
Update
taken straight from caliburn.Micro's MEF-Bootstrapper:
container = CompositionHost.Initialize(
new AggregateCatalog(
AssemblySource.Instance.Select(x => new AssemblyCatalog(x)).OfType<ComposablePartCatalog>()
)
);
var batch = new CompositionBatch();
batch.AddExportedValue<IWindowManager>(new WindowManager());
batch.AddExportedValue<IEventAggregator>(new EventAggregator());
batch.AddExportedValue(container);
container.Compose(batch);
You can check the sample code from Caliburn.Micro Github Samples.
https://github.com/Caliburn-Micro/Caliburn.Micro/blob/master/samples/Caliburn.Micro.HelloMef/Caliburn.Micro.HelloMef/MefBootstrapper.cs
You need to register your WindowManager in MEF.

WebService call in MVVM

I'm developing simple application in WPF with MVVM Light Toolkit. I have two views:
HomeView (default)
CustomersView
This is part of the MainViewModel class:
public MainViewModel()
{
CurrentViewModel = Bootstrapper.Instance.Container.Resolve<HomeViewModel>();
}
private void ExecuteShowCustomersCommand()
{
CurrentViewModel = Bootstrapper.Instance.Container.Resolve<CustomersViewModel>();
}
In CustomerViewModel I have property:
public ObservableCollection<Customers> Customers
{
get { return _customers; }
set
{
if (_customers == value) return;
_customers = value;
RaisePropertyChanged(CustomersPropertyName);
}
}
And my question is, when I should call the web service to get customers data? In CustomerViewModel constructor?
I would do it in the constructor of the viewmodel and use a IoC Container to get the instance.
Application start
SimpleIoc.Default.Register<IDataService, DataService>();
SimpleIoc.Default.Register<MyViewModel>();
ViewModel
public MyViewModel(IDataService DataService)
{
Mydata = DataService.GetData(); // Edit: Could also be done in a property with lazy load
}
Locator
public MyViewModel MyVM
{
get
{
return SimpleIoc.Default.GetInstance<MyViewModel>();
}
}

MVP, struggling to populate View's RibbonDropDown with Interface Property

I think my question is fairly trivial, but I'm not getting my problem right. I want to use a passive-Presenter in the MVP pattern. My present concrete view is an Excel Add-in, but I don't want this to limit me in future.
On this view I have a RibbonDropDown, the items I need to get from the Model, and pass to the View via the Presenter. I also want an Interface between the Presenter and View so that they are more loosely coupled.
This MVP example was quite useful, but it seems I can't bind the datasource like they've done here to their example of a list of products:
public IList<ProductItem> Products
{
set
{
this.uiProducts.DataSource = value;
this.uiProducts.DataBind();
}
}
So, if this is my Presenter:
public class Presenter
{
private IViewInterface iView;
public Presenter(IViewInterface pView)
{
iView = pView;
}
public void Initialize()
{
List<String> ServArr = new List<String>();
ServArr.Add("a server 1");
ServArr.Add("a server 2");
iView.ServArr = ServArr;
}
}
and this is the interface for the View:
public interface IViewInterface
{
List<String> ServArr { get; set; }
}
and this is a snippet of the View:
public partial class SympivotyRibbon : IViewInterface
{
private Presenter presenter;
public List<String> ServArr
{
set { ServArr = new List<String>(value); }
//get { return ServArr; }
}
private void SympivotyRibbon_Load(object sender, RibbonUIEventArgs e)
{
presenter = new Presenter(this);
presenter.Initialize();
for (int i = 0; i < ServArr.Count; i++)
{
RibbonDropDownItem item = Globals.Factory.GetRibbonFactory().CreateRibbonDropDownItem();
item.Label = ServArr[i];
dropDownServer.Items.Add(item);
}
}
}
Then I get the compiler error in the for-loop:
The property or indexer 'SympivotyRibbon.ServArr' cannot be used in this context because it lacks the get accessor
I don't know how to put in a getter together with the setter, so that I can populate the control. If I uncomment the get-part above, I get a StackOverflow exception, probably because it is circular.
I assume the answer should be easy, I just don't know how.

Markup extension in XAML for binding to ISubject<string>

If I have the following view model
class Foo : INotifyPropertyChanged {
ISubject<string> Name { ... }
}
and some imagined XAML code
<TextBox Text="{my:Subscribe Path=Name}/>
I wish the two way binding to behave that
Subject.onNext is called when the text box is updated in the UI
the text box is updated by subscribing to the Subject.Subscribe
As WPF only supports INPC directly my idea is to create a proxy INPC object
in via a markup extension
class WPFSubjectProxy : INotifyPropertyChanged{
string Value { ... }
}
The proxy would be wired up to the subject as so
subject.Subscribe(v=>proxy.Value=v);
proxy
.WhenAny(p=>p.Value, p.Value)
.Subscribe(v=>subject.OnNext(v))
Note WhenAny is a ReactiveUI helper for subscribing to
INPC events.
But then I would need to generate a binding and return
that via the markup extension.
I know what I want to do but can't figure out the
Markup extension magic to put it all together.
It's hard to say without seeing specifically what you're struggling with, but perhaps this helps?
EDIT
The solution I (bradgonesurfing) came up with is below thanks to the pointer in the
assigned correct answer.
    Nodes
     
and the implementing code. It has a dependency on ReactiveUI and a helper function in a private library for binding ISubject to a mutable property on an INPC supporting object
using ReactiveUI.Subjects;
using System;
using System.Linq;
using System.Reactive.Subjects;
using System.Windows;
using System.Windows.Data;
using System.Windows.Markup;
namespace ReactiveUI.Markup
{
[MarkupExtensionReturnType(typeof(BindingExpression))]
public class SubscriptionExtension : MarkupExtension
{
[ConstructorArgument("path")]
public PropertyPath Path { get; set; }
public SubscriptionExtension() { }
public SubscriptionExtension(PropertyPath path)
{
Path = path;
}
class Proxy : ReactiveObject
{
string _Value;
public string Value
{
get { return _Value; }
set { this.RaiseAndSetIfChanged(value); }
}
}
public override object ProvideValue(IServiceProvider serviceProvider)
{
var pvt = serviceProvider as IProvideValueTarget;
if (pvt == null)
{
return null;
}
var frameworkElement = pvt.TargetObject as FrameworkElement;
if (frameworkElement == null)
{
return this;
}
object propValue = GetProperty(frameworkElement.DataContext, Path.Path);
var subject = propValue as ISubject<string>;
var proxy = new Proxy();
Binding binding = new Binding()
{
Source = proxy,
Path = new System.Windows.PropertyPath("Value")
};
// Bind the subject to the property via a helper ( in private library )
var subscription = subject.ToMutableProperty(proxy, x => x.Value);
// Make sure we don't leak subscriptions
frameworkElement.Unloaded += (e,v) => subscription.Dispose();
return binding.ProvideValue(serviceProvider);
}
private static object GetProperty(object context, string propPath)
{
object propValue = propPath
.Split('.')
.Aggregate(context, (value, name)
=> value.GetType()
.GetProperty(name)
.GetValue(value, null));
return propValue;
}
}
}

Inject ViewModel and its property

I have ViewModel in my Silverlight project
public class MainViewModel : BaseNotify, IMainViewModel
{
[Dependency("AccountID")]
public Guid AccountID { get; set; }
...
public MainViewModel()
{
if (AccountID != Guid.Empty)
{
PreSelectedAccount = new Account() { ID = AccountID };
SingleAccountMode = true;
}
}
....
}
I'm using Unity this way:
public static class IoC
{
static IoC()
{
Current = new UnityContainer();
Current.RegisterType<IMainViewModel, MainViewModel>();
}
public static UnityContainer Current { get; set; }
}
public partial class App : Application
{
[Dependency]
public IMainViewModel ViewModel { get; set; }
private void Application_Startup(object sender, StartupEventArgs e)
{
Guid accountId = "1234-5678-1234-5678-1234";
IoC.Current.BuildUp(this);
}
}
After calling BuildUp method,I have instance of MainViewModel in the App.ViewModel, but how I can set up Unity to inject also some value for MainViewModel.AccountId property value during BuildUp?
You need to resolve/buildup with an override:
IoC.Current.BuildUp(this, new PropertyOverride("AccountID", accountId));
Your code looks like you are using the ServiceLocator anti-pattern. A pattern like constructor injection is most often preferable.
Another question: Why do you hook up your MainViewModel with your App class? Usually you would use it as the DataContext for your MainView.
And it is not common to inject primitive values like Guids using a DI container.
As #nemesv mention you might need to change your BuildUp method call, but doing so, I don't think you can achieve what you need.
The problem is first instance of the MainViewModel is created and then AccountId is passed into it, so your logic in the constructor is never true, e.g.
if(AccountID != Guid.Empty)
is never true. Alternativly you can add this logic to Setter of the AccountID property, something inline with this:
public Guid AccountId {
get { return _accountId; }
set {
_accountId = value;
OnAccountIdChanged();
}
}
protected virtual void OnAccountIdChanged() {
if(AccountId != Guid.Empty) {
//do your thing here
}
}

Categories

Resources