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.
Related
I can see that the constructor of the ViewModelLocator is executed first when my application starts, but how to make the constructors of my ViewModels run after that, because inside them I have a registering that I want to happen in the beggining of my app. The constructor of my CustomViewModel runs when I enter the View because of the binding. The binding is to a master class called CompositeViewModel that contains my viewModels.
CompositeViewModel:
class CompositeViewModel
{
public static CustomViewModel customViewModel { get; set; }
static CompositeViewModel()
{
customViewModel = new CustomViewModel();
}
}
Here is my ViewModelLocator
public ViewModelLocator()
{
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
SimpleIoc.Default.Register<CustomViewModel>();
SimpleIoc.Default.Register<MainViewModel>();
}
public CustomViewModelTripTypeView
{
get
{
return ServiceLocator.Current.GetInstance<CustomViewModel>();
}
}
This is the code in my CustomViewModels constructor:
public CustomViewModel()
{
Messenger.Default.Register<ObservableCollection<MyType>>
(
this,
(action) => ReceiveMessage(action)
);
}
private void ReceiveMessage(ObservableCollection<MyType> action)
{
DispatcherHelper.CheckBeginInvokeOnUI(() =>
{
this.MyDataSource.Clear();
foreach (MyTypet mt in action)
{
this.MyDataSource.Add(mt);
}
});
}
There is an overloaded method for Register method with bool argument
SimpleIOC.Default.Register<MainViewModel>(true);
This statement will immediately create an instance of MainViewModel.
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>();
}
}
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.
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.
I need a simple example on how to make one viewmodel update a property on another viewmodel.
Here' is the situation. I got a view and viewmodel responsable fo displaying a list of albums. I got another view and viewmodel responsable for added a new albums ( couple of textboxes and a button ) now when the new album gets added how do I tell the Collection in the other view that the a new album has been added to it?
I read about frameworks that can do this for me, but Im trying to learn so I won't use a framework for the being..
here are some pieces of the puzzle, lifted from Josh Smith's classic demo app, showing how events can be used to support the testability and loose couple afforded by mvvm
The data
This isn't a view model of cource, but most interesting apps have data & it has to come from somewhere! And it is an obvious and convenient candidate for holding an event for when a new item has been added:
public class CustomerRepository
{
...
/// <summary>Raised when a customer is placed into the repository.</summary>
public event EventHandler<CustomerAddedEventArgs> CustomerAdded;
/// <summary>
/// Places the specified customer into the repository.
/// If the customer is already in the repository, an
/// exception is not thrown.
/// </summary>
public void AddCustomer(Customer customer)
{
if (customer == null) throw new ArgumentNullException("customer");
if (_customers.Contains(customer)) return;
_customers.Add(customer);
if (CustomerAdded != null)
CustomerAdded(this, new CustomerAddedEventArgs(customer));
}
...
}
The Shell
Consider having a view model that coordinates a given presentation, perhaps by managing workspaces. Some people might call it a Manager (yuk!), or MainViewModel. I like ShellViewModel. This view model has a command to create new items:
public class MainWindowViewModel : WorkspaceViewModel
{
readonly CustomerRepository _customerRepository;
public MainWindowViewModel(...)
{
_customerRepository = new CustomerRepository(customerDataFile);
}
void _createNewCustomer()
{
var newCustomer = Customer.CreateNewCustomer();
var workspace = new CustomerViewModel(newCustomer, _customerRepository);
Workspaces.Add(workspace);
_setActiveWorkspace(workspace);
}
ObservableCollection<WorkspaceViewModel> _workspaces;
void _setActiveWorkspace(WorkspaceViewModel workspace)
{
var collectionView = CollectionViewSource.GetDefaultView(Workspaces);
if (collectionView != null)
collectionView.MoveCurrentTo(workspace);
}
}
The model object
Did you notice the static factory constructor method (Customer.CreateNewCustomer)? It makes clear what it's purpose is, and gives an opportunity to encapsulate any complexities involved in creating the new customer.
The model object ViewModel wrapper
This typically derives from a base class that makes INPC notification simple to use, since it is the basis for data binding. Notice the Email property is a direct pass through to the model object's email property, yet DisplayNAme is purely UI driven. In the case of adding a new item, it appropriately says..."New Cistomer":
public class CustomerViewModel : WorkspaceViewModel, IDataErrorInfo
{
public CustomerViewModel(Customer customer, CustomerRepository customerRepository)
{
if (customer == null) throw new ArgumentNullException("customer");
if (customerRepository == null) throw new ArgumentNullException("customerRepository");
_customer = customer;
_customerRepository = customerRepository;
}
readonly Customer _customer;
public string Email
{
get { return _customer.Email; }
set
{
if (value == _customer.Email) return;
_customer.Email = value;
base.OnPropertyChanged("Email");
}
}
public override string DisplayName
{
get {
if (IsNewCustomer)
{
return Strings.CustomerViewModel_DisplayName;
}
...
return String.Format("{0}, {1}", _customer.LastName, _customer.FirstName);
}
}
#region Save Command
/// <summary>
/// Returns a command that saves the customer.
/// </summary>
public ICommand SaveCommand
{
get
{
return _saveCommand ??
(_saveCommand = new RelayCommand(param => _save(), param => _canSave));
}
}
RelayCommand _saveCommand;
/// <summary>
/// Returns true if the customer is valid and can be saved.
/// </summary>
bool _canSave
{
get { return String.IsNullOrEmpty(_validateCustomerType()) && _customer.IsValid; }
}
/// <summary>
/// Saves the customer to the repository. This method is invoked by the SaveCommand.
/// </summary>
void _save()
{
if (!_customer.IsValid)
throw new InvalidOperationException(Strings.CustomerViewModel_Exception_CannotSave);
if (IsNewCustomer)
_customerRepository.AddCustomer(_customer);
base.OnPropertyChanged("DisplayName");
}
}
The ViewModel collection of ViewModels
This might support filtering, sorting, summing. In the case of adding a new customer, notice it is subscribing to that event we added to the Repository. Notice also that it uses an ObservableCollection, since it has built in support for databinding.
public class AllCustomersViewModel : WorkspaceViewModel
{
public AllCustomersViewModel(CustomerRepository customerRepository)
{
if (customerRepository == null) throw new ArgumentNullException("customerRepository");
_customerRepository = customerRepository;
// Subscribe for notifications of when a new customer is saved.
_customerRepository.CustomerAdded += OnCustomerAddedToRepository;
// Populate the AllCustomers collection with CustomerViewModels.
_createAllCustomers();
}
/// <summary>
/// Returns a collection of all the CustomerViewModel objects.
/// </summary>
public ObservableCollection<CustomerViewModel> AllCustomers
{
get { return _allCustomers; }
}
private ObservableCollection<CustomerViewModel> _allCustomers;
void _createAllCustomers()
{
var all = _customerRepository
.GetCustomers()
.Select(cust => new CustomerViewModel(cust, _customerRepository))
.ToList();
foreach (var cvm in all)
cvm.PropertyChanged += OnCustomerViewModelPropertyChanged;
_allCustomers = new ObservableCollection<CustomerViewModel>(all);
_allCustomers.CollectionChanged += OnCollectionChanged;
}
void OnCustomerViewModelPropertyChanged(object sender, PropertyChangedEventArgs e)
{
const string IsSelected = "IsSelected";
// Make sure that the property name we're referencing is valid.
// This is a debugging technique, and does not execute in a Release build.
(sender as CustomerViewModel).VerifyPropertyName(IsSelected);
// When a customer is selected or unselected, we must let the
// world know that the TotalSelectedSales property has changed,
// so that it will be queried again for a new value.
if (e.PropertyName == IsSelected)
OnPropertyChanged("TotalSelectedSales");
}
readonly CustomerRepository _customerRepository;
void OnCustomerAddedToRepository(object sender, CustomerAddedEventArgs e)
{
var viewModel = new CustomerViewModel(e.NewCustomer, _customerRepository);
_allCustomers.Add(viewModel);
}
}
Check out the full article and download the code!
HTH,
Berryl
There several ways:
1) AlbumsVM is aware of CreateAlbumVM (for example first opens the second). In this case you can simply add album into AlbumsVM using details provided by CreateAlbumVM
2) CreateAlbumVM is aware of AlbumsVM. Then it can insert albums into AlbumsVM itself.
3) AlbumsVM receives albums as ObservableCollection from somewhere. Then CreateAlbumVM can insert new album into original ObservableCollection which will be reflected in AlbumsVM
4) There is some mediator between these viewModels which provide event AlbumWasAdded.
Just implement your properties like this.
private bool _checked;
public bool Checked
{
get { return _checked; }
set
{
if (value != _checked)
{
_checked = value;
OnPropertyChanged("Checked");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
public virtual void OnPropertyChanged(string propertyName)
{
var handler = PropertyCHanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
Then subscribe your other viewmodel to the propertyChangedEvent and do what you need to do with it.
Make sure your ViewModels implement INotifyPropertyChanged
You give the impression that you think the Viewmodels for each View should be isolated classes. They are not. Viewmodels repackage the underlying data in such a way that the View can bind to it. So, make an ObservableCollection<Album> and have both Viewmodels reference it. Done!