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>();
}
}
Related
I have got my MainWindow which loads new UserControls and there ViewModel's into it's ContentControl, so the Views are switched.
However, I need to access a property in my MainWindow ViewModel from a ViewModel within the ContentControl.
MainWindowViewModel
namespace PhotoManagement
{
public class MainWindowViewModel : NotifyUIBase
{
public ObservableCollection<ViewVM> Views { get; set; }
private ObservableCollection<Logged> loggedUsers;
public ObservableCollection<Logged> LoggedUsers
{
get
{
return loggedUsers;
}
set
{
loggedUsers.Add(value[0]);
//There is a user logged in, switch to home and display menu
if (loggedUsers.Count > 0)
{
//Display menu, switch Windows
MessageBox.Show("Someone is logged in!");
}
else
{
MessageBox.Show("No-one is logged in!");
}
}
}
Below you can see the LoginViewModel which is in the MainWindow ContentControl, I have added a comment where i'm trying to add this new user to the ObservableCollection.
#region Login Methods
private LoginVM loginVM;
public LoginVM LoginVM
{
get
{
return loginVM;
}
set
{
loginVM = value;
editEntity = editVM.TheEntity;
RaisePropertyChanged();
}
}
protected override void DoLogin()
{
//Check if email exists
var exist = db.Users.Count(a => a.Email == LoginVM.TheEntity.Email);
if (exist != 0)
{
//Fecth user details
var query = db.Users.First(a => a.Email == LoginVM.TheEntity.Email);
if (Common.Security.HashGenerator.CalculateHash(LoginVM.TheEntity.ClearPassword, query.Salt) == query.Hash)
{
//Password is correct
MessageBox.Show("Details correct!");
//Set properties
LoginVM.TheEntity.FirstName = query.FirstName;
LoginVM.TheEntity.LastName = query.LastName;
LoginVM.TheEntity.UID = query.UID;
//Add the LoginVM to LoggedUsers
Edit:
This is where I add the Views in MainWindowViewModel
namespace PhotoManagement
{
public class MainWindowViewModel : NotifyUIBase
{
public ObservableCollection<ViewVM> Views { get; set; }
private ObservableCollection<Logged> loggedUsers;
public ObservableCollection<Logged> LoggedUsers
{
get
{
return loggedUsers;
}
set
{
loggedUsers.Add(value[0]);
//There is a user logged in, switch to home and display menu
if (loggedUsers.Count > 0)
{
//Display menu, switch Windows
MessageBox.Show("Someone is logged in!");
}
else
{
MessageBox.Show("No-one is logged in!");
}
}
}
public string Version
{
get { return System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); }
}
public MainWindowViewModel()
{
ObservableCollection<ViewVM> views = new ObservableCollection<ViewVM>
{
new ViewVM { IconGeometry=App.Current.Resources["home4"] as Geometry, ViewDisplay="Home", ViewType = typeof(LoginView), ViewModelType = typeof(LoginViewModel)},
new ViewVM { IconGeometry=App.Current.Resources["instagram3"] as Geometry, ViewDisplay="Images", ViewType = typeof(LoginView), ViewModelType = typeof(LoginView)},
new ViewVM { IconGeometry=App.Current.Resources["money674"] as Geometry, ViewDisplay="Sales", ViewType = typeof(LoginView), ViewModelType = typeof(LoginViewModel)},
new ViewVM { IconGeometry=App.Current.Resources["printing1"] as Geometry, ViewDisplay="Print Queue", ViewType = typeof(LoginView), ViewModelType = typeof(LoginViewModel)},
new ViewVM { IconGeometry=App.Current.Resources["cog2"] as Geometry, ViewDisplay="Settings", ViewType = typeof(IconLibaryView), ViewModelType = typeof(IconLibaryViewModel)},
new ViewVM { IconGeometry=App.Current.Resources["upload40"] as Geometry, ViewDisplay="Upload", ViewType = typeof(IconLibaryView), ViewModelType = typeof(IconLibaryViewModel)}
};
Views = views;
RaisePropertyChanged("Views");
views[0].NavigateExecute();
}
}
}
You simply need to use Ancestor binding from within ContentControl's any child element :
{Binding RelativeSource={RelativeSource AncestorType={x:Type Window}},Path=DataContext.AnyPropertyOfMainWindowViewModel}
If Window has MainWindowViewModel as DataContext.
I would go with events for ViewModel-to-ViewModel communication, and I prefer the IEventAggregator, available as a PubSub nuget package from Microsoft, but there are plenty to choose from (or roll your own if you prefer).
public MainViewModel() {
Aggregator.GetEvent<UserLoggedInEvent>().Subscribe(user => ...do your magic);
}
And in your LoginViewModel, publish it after the user has logged in:
public DoLogin() {
... do other stuff here...
Aggregator.GetEvent<UserLoggedInEvent>().Publish(userDetails);
}
Using the IEventAggregator from Prism, the event class is simple:
public class UserLoggedInEvent : PubSubEvent<User> {}
Btw - One of the main purposes for MVVM or any design pattern is to abstract UI from business code, so if you can remove all your App.Current.Resources stuff from your VM using a converter or something else then you've abstracted it from WPF (much more easily ported to other platforms like UWP).
I have a problem using the IDataErrorInfo in combination with IReactiveBinding.Bind(). I hope someone here can help me.
I have a ViewModel that is inherited from ReactiveObject and implements the IDataErrorInfo interface.
public class MainWindowViewModel : ReactiveUI.ReactiveObject, IDataErrorInfo
{
private string username = string.Empty;
public string Username
{
get { return this.username; }
set { this.RaiseAndSetIfChanged(ref this.username, value); }
}
public MainWindowViewModel()
{
this.Validator = new MainWindowViewModelValidator();
}
public AbstractValidator<MainWindowViewModel> Validator { get; set; }
#region IDataErrorInfo Members
string IDataErrorInfo.Error
{
get
{
return Validator != null ? string.Join(Environment.NewLine, Validator.Validate(this).Errors.Select(x => x.ErrorMessage).ToArray())
: string.Empty;
}
}
string IDataErrorInfo.this[string propertyName]
{
get
{
if (Validator != null)
{
var results = Validator.Validate(this, propertyName);
if (results != null
&& results.Errors.Count() > 0)
{
var errors = string.Join(Environment.NewLine, results.Errors.Select(x => x.ErrorMessage).ToArray());
return errors;
}
}
return string.Empty;
}
}
#endregion
}
The MainWindowViewValidator ensures that the Usernameproperty is not empty.
The ViewModel is connected to the View in the code behind of the XAML-File:
public partial class MainWindow : IViewFor<MainWindowViewModel>
{
public MainWindow()
{
InitializeComponent();
this.ViewModel = new MainWindowViewModel();
this.Bind(this.ViewModel, viewmodel => viewmodel.Username, view => view.Username.Text);
}
public MainWindowViewModel ViewModel
{
get { return (MainWindowViewModel)GetValue(ViewModelProperty); }
set { SetValue(ViewModelProperty, value); }
}
public static readonly DependencyProperty ViewModelProperty = DependencyProperty.Register("ViewModel", typeof(MainWindowViewModel), typeof(MainWindow), new PropertyMetadata(null));
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (MainWindowViewModel)value; }
}
}
The problem is now that the model validation is not called, as I don't specify the databinding in the XAML file directly.
Does anybody has a neat solution for this problem?
The problem is now that the model validation is not called, as I don't specify the databinding in the XAML file directly.
ReactiveUI doesn't participate in IDataErrorInfo and friends for binding. It used to, but really if you think about it, Validation itself is a derived property of the form.
ReactiveUI is already really good at describing how properties are related to each other (via WhenAny/ToProperty), so you should just construct an ValidationError property that displays the error message.
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 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.
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.