I'm trying to put an universal app together and I'm using mvvm light but I'm getting the following error when compiling my app:
Error 1 Type not found in cache: MyApp.Model.LocationModel
...\MyApp.WindowsPhone\Views\LocationPage.xaml 10 5 MyApp.WindowsPhone
It does compile successfully but I can't figure out what's causing the problem. I've found a couple of article on stackoverflow:
SimpleIoC - Type not found in cache: Windows.UI.Xaml.Controls.Frame
MVVM Light “Type Not Found in cache”
But neither one apply to my problem. The first thing I've noticed is that the error is somehow displaying a Model where the problem resides rather than a ViewModel.
Error 1 Type not found in cache: MyApp.Model.LocationModel.
...\MyApp\MyApp.WindowsPhone\Views\LocationPage.xaml 10 5 MyApp.WindowsPhone
The error in my xaml occurs on the line where I defined my DataContext:
<Page
....
DataContext="{Binding Source={StaticResource Locator}, Path=LocationViewModel}">
My LocationViewModel class is defined as follows:
public class LocationViewModel : ViewModelBase
{
private RelayCommand _saveCommand;
private RelayCommand _cancelCommand;
#region Properties
public int Id
{
get
{
return this.Location.Id;
}
}
public string Title
{
get
{
return this.Location.Title;
}
}
public string Description
{
get
{
return this.Location.Description;
}
}
public string CreatedDateFormatted
{
get
{
return this.Location.CreatedDate.ToString("d");
}
}
public string LastUpdatedDateFormatted
{
get
{
return Location.LastUpdatedDate.ToString("d");
}
}
public string ImagePath
{
get
{
return this.Location.ImagePath;
}
}
public LocationModel Location
{
get;
private set;
}
#endregion
#region Constructors
public LocationViewModel(LocationModel model)
{
this.Location = model;
this.Location.PropertyChanged += (s, e) =>
{
if (e.PropertyName == LocationModel.DescriptionPropertyName)
{
RaisePropertyChanged(() => Description);
}
if (e.PropertyName == LocationModel.TitlePropertyName)
{
RaisePropertyChanged(() => Title);
}
if (e.PropertyName == LocationModel.ImagePathPropertyName)
{
RaisePropertyChanged(() => ImagePath);
}
if (e.PropertyName == LocationModel.CreatedDateStringPropertyName)
{
RaisePropertyChanged(() => CreatedDateFormatted);
}
if (e.PropertyName == LocationModel.LastUpdatedDateStringPropertyName)
{
RaisePropertyChanged(() => LastUpdatedDateFormatted);
}
};
}
#endregion
public RelayCommand SaveCommand
{
get
{
return this._saveCommand ?? (this._saveCommand = new RelayCommand(ExecuteSaveCommand));
}
}
public RelayCommand CancelCommand
{
get
{
return this._cancelCommand ?? (this._cancelCommand = new RelayCommand(ExecuteCancelCommand));
}
}
private void ExecuteSaveCommand()
{
}
private void ExecuteCancelCommand()
{
}
}
and my property for my LocationViewModel is defined as follows in my ViewModelLocator class:
public LocationViewModel LocationViewModel
{
get
{
return ServiceLocator.Current.GetInstance<LocationViewModel>();
}
}
and is registered in the ViewModelLocator's constructor:
SimpleIoc.Default.Register<LocationViewModel>();
and when this code is called, it registers my LocationViewModel correctly.
When click on my "add" button, it navigate to the page where the LocationViewModel is set as the DataContext and the error occurs at run-time.
The code I'm calling from LocationsViewModel (not LocationViewModel) that's calling the navigation is:
private void ExecuteAddCommand()
{
_navigationService.Navigate(typeof(LocationPage));
}
When debugging the above, it creates the LocationPage, followed by calling the LocationViewModel from the ViewModelLocator and this is when the same error occurs but at run-time i.e.
return ServiceLocator.Current.GetInstance<LocationViewModel>();
When I move my mouse over the , it displays the following:
Message: "Type not found in cache: MyApp.Model.LocationModel."
InnerException: at GalaSoft.MvvmLight.Ioc.SimpleIoc.DoGetService
(Type serviceType, String key) at
GalaSoft.MvvmLight.Ioc.SimpleIoc.GetInstance[TService]()
at Inventory.ViewModel.ViewModelLocator.get_LocationViewModel()
Actually, I've just realized that the error is generated much earlier but no error is thrown. It is actually generated when registering the LocationViewModel in the constructor of ViewModelLocator:
SimpleIoc.Default.Register<LocationViewModel>();
Any ideas?
Thanks.
The LocationViewModel constructor has dependency on LocationModel. The SimpleIoc container couldn't create the view model instance as the constructor requires a LocationModel object which you can't pass directly. You can probably use MVVMLight Messenger to decouple the LocationModel object from the LocationViewModel constructor.
public LocationViewModel()
{
MessengerInstance.Register<LocationModel>(this,m=>{model=m;
//PropertyChanged code
});
}
In the LocationsViewModel, send the LocationModel object you wanted to use in the LocationViewModel constructor by just sending it.
public void ExecuteAddCommand()
{
MessengerInstance.Send<LocationModel>(LocationModelObj);
_navigationService.navigate(tyepof(LocationPage));
}
For this to succeed though, you'd need to register LocationViewModel to register to receive LocationModel object before sending the object from LocationsViewModel. So, you need to create your view model immediately by using an overload of SimpleIoc's Register method.
SimpleIoc.Default.Register<LocationViewModel>(true);
Based on what #Shridhar said The SimpleIoc container couldn't create the view model instance as the constructor requires a LocationModel object which you can't pass directly, I thought I'd try adding a parameterless constructor but I got another error i.e.
Cannot register: Multiple constructors found in LocationViewModel but
none marked with PreferredConstructor.
So I marked my parameterless constructor with the PreferredConstructor as such:
[PreferredConstructor]
public LocationViewModel()
{
}
This sorted my problem but as mentioned to #Shridar, I'm not sure whether or not this is the correct solution so I will spend more time investigating and see if this works as expected and doesn't have any side effects.
I'll update as soon as I have something.
I also experienced a similar error while trying to use MVVMLight DialogService; the solution was to make sure it is registered in the ViewModelLocator
public ViewModelLocator()
{
SimpleIoc.Default.Register<IDialogService, DialogService>();
}
Related
I have question. I use to use Send to pass the viewmodel to show other page as shown below:
public class UsersViewModel : IUsersViewModel
{
void ShowCars()
{
MessagingCenter.Send<IUsersViewModel>(this, "ShowCarsViewPage");
}
}
As you see above, i use this. Now i have situation that i need to pass diffrent viewmoodel inside UsersViewModel. I want to add ShowBuildings inside UsersViewModel. The problem is as it's diffrent viewmodel to be passed i cannot use this which leds me to use new keyword and pass all dependencies. How can i overcome that?
void ShowBuildings()
{
MessagingCenter.Send<IBuildingsViewModel>(new Buildings(new DataStorage()), "ShowBuildingsViewPage");
}
My first thought is to pass that view model i need to use into UsersViewModel's ctor but not sure if this is right way like to insert another view model into other view model?:
public class UsersViewModel : IUsersViewModel
{
private readonly IBuildingsViewModel _buildingviewmodel;
UsersViewMode(IBuildingsViewModel buildingviewmodel)
{
_buildingviewmodel = buildingviewmodel;
}
//So then:
void ShowBuildings()
{
MessagingCenter.Send<IBuildingsViewModel>(_buildingviewmodel, "ShowBuildingsViewPage");
}
}
Check xamarin publisher documentation. The third parameter is the payload data that is being sent.
MessagingCenter.Send<MainPage, string>(this, "Hi", "John");
Tip: Try to bundle multiple ViewModels with an interface and pass that. In this way you will not be tied to specific ViewModels interfaces.
interface IBuildingCollection : IEnumerable<Building>
{
}
class ViewModel1 : IBuildingsViewModel, IBuildingCollection
{
}
class ViewModel2 : IBuildingsViewModel, IBuildingCollection
{
}
class UsersViewModel : IUsersViewModel
{
void ShowBuildings(IBuildingCollection collection)
{
MessagingCenter.Send<IUsersViewModel, IBuildingCollection>(this, "ShowBuildingsViewPage", collection);
}
}
class ReceiverViewModel : IReceiverViewModel
{
public ReceiverViewModel()
{
MessagingCenter.Subscribe<IUsersViewModel, IBuildingCollection>(this, "ShowBuildingsViewPage", myDelegate);
}
public void myDelegate(IBuildingCollection buildings)
{
// Do something with buildings
}
}
I have an application with user-login which representates a "lifetime" object till the user logs out. I'm using MVVM Light but don't know how to use the SimpleIoC (with the Messenger) correctly.
Currently I save the object in the MainViewModel as CurrentUser Property of my custom User object and pass it to other ViewModels via constructor and save it there as a Property too. But with that I have the issue, that at some places the object gets overwritten / created new.
This is probably the wrong way to do it as i figured out that the SimpleIoC can simply return the registered instances with GetInstance<>() and if needed a Key.
So the right way is it to create a User object with Key to get it in needed classes, modify and save it so the other classes can get the updated value when calling (or PropertyChanged possible?), right?
I tried to create it like that, but the object is obviously not updated:
public MainViewModel()
{
RegisterMessenger();
CurrentUser = SimpleIoc.Default.GetInstance<User>("123");
_dataService = SimpleIoc.Default.GetInstance<IDataService>();
Messenger.Default.Send(new NotificationMessage("GoToLoginPage"));
}
public User CurrentUser
{
get { return _currentUser; }
set { Set(ref _currentUser, value); }
}
public User CurrentPageViewModel
{
get { return _currenPageViewModel; }
set { Set(ref _currentPageViewModel, value); }
}
public void RegisterMessenger()
{
Messenger.Default.Register<NotificationMessage>(this, message =>
{
switch (message.Notification)
{
case "GoToLoginPage":
if (_loginViewModel == null)
_loginViewModel = new LoginViewModel(_dataService);
CurrentPageViewModel = _loginViewModel;
break;
// Other cases...
});
}
}
// -------------------------------------------------------------------------
public LoginViewModel(IDataService dataService)
{
UserObj = SimpleIoc.Default.GetInstance<User>("123");
_dataService = dataService;
LoginCommand = new RelayCommand(LoginExecute, LoginCanExecute);
}
Would it also be better, if I call the ViewModels with GetInstance<SomeViewModel>().. but then, how I pass paramters to them and let them stay alive / recreate / destroy?
Would be nice if someone can point me out the right way, thanks!
I have done some searching and I can't find anyone with my specific problem.
I have a Caliburn.Micro project and I successfully have a main view with sub-views inside it which is not a problem. My View Models are in a different assembly to my views.
This meant I had to override SelectAssemblies to include my view models project:
protected override IEnumerable<Assembly> SelectAssemblies()
{
var assemblies = base.SelectAssemblies().ToList();
assemblies.Add(typeof(OrderViewModel).Assembly);
return assemblies;
}
Now, this is where my confusion starts. I successfully have a OrderView showing the OrderViewModel. Inside that there is a KeyboardViewModel with a KeyboardView. This all works fine so caliburn is finding the right assemblies etc.
However when I come to use the window manager to display a new view/viewmodel which is passed into the order view. I am getting a screen with the text "Cannot find view model for XX.ViewModels.Model."
This is my OrderViewModel
[Export(typeof(OrderViewModel))]
public class OrderViewModel : Screen
{
private readonly IWindowManager windowManager;
private ISession session;
[ImportingConstructor]
public OrderViewModel(IWindowManager windowManager, KeyboardViewModel keyboardViewModel)
{
TillDatabase.CreateInstance(ApplicationConfiguration.Instance.DatabaseConnectionString);
this.windowManager = windowManager;
this.Keyboard = keyboardViewModel;
this.Keyboard.Order = this;
this.Keyboard.Home();
}
public void ChangePriceBand()
{
windowManager.ShowWindow(new PriceBandSelectionViewModel(this));
}
}
The thing is, I even tried this in ChangePriceBand
windowManager.ShowWindow(new OrderViewModel(this.windowManager, new KeyboardViewModel()));
And this gets the same error. Even though a view has already been associated with the OrderViewModel previously!!
This is the PriceBandSelectionViewModel just in case.
[Export(typeof(PriceBandSelectionViewModel))]
public class PriceBandSelectionViewModel : Screen
{
private OrderViewModel order;
[ImportingConstructor]
public PriceBandSelectionViewModel(OrderViewModel order)
{
this.order = order;
}
public ObservableCollection<PriceBandButtonViewModel> Buttons
{
get
{
var list = new ObservableCollection<PriceBandButtonViewModel>();
var priceBands = this.order.Session.QueryOver<Application_Model_PriceBand>().List();
foreach (var priceBand in priceBands)
{
PriceBandButtonViewModel button = new PriceBandButtonViewModel(priceBand, this);
list.Add(button);
}
return list;
}
}
public void ProcessButtonClick(Application_Model_PriceBand button)
{
this.order.ChangeCurrentPriceBand(button);
base.TryClose();
}
}
I'm just really confused to how Caliburn is setting up my main view, but the window manager isn't even though its the same ViewModel?
have you tried to remove OrderViewModel or put a breakpoint there, cant find view error might happen if it encountered error when initialising the exported class
public PriceBandSelectionViewModel()
{
// this.order = order;
}
or add
assemblies.Add(typeof(PriceBandSelectionViewModel).Assembly);
This may be the same problem as I am experiencing as described here: Caliburn.Micro HelloWindowManager Sample - View location not working
To see if it is the same problem, try changing the call from
windowManager.ShowWindow(new PriceBandSelectionViewModel(this));
to
windowManager.ShowDialog(new PriceBandSelectionViewModel(this));.
In my case, ShowDialog was able to locate the view no problem, but ShowWindow and ShowPopup were not.
I am using MVVM (prism) to develop wpf application.
One of my model class "StandardContact" has its properties directly bound to the view. I use IDataErrorInfo to track and notify whether the model has any error. If there are any errors in Model, I disable the "Save" Command.
As the user enters some data, I use the StandardContact.PropertyChanged handler to see if "Save" command can execute (i.e if the model data entered by user is valid). The problem is that the StandardContact.PropertyChanged handler is called before the IDataErrorInfo's validation code, so CanExecute for "Save" command does not correctly reflect whether the command can be executed or not. What I am looking for is that, before the CanExecute executes, the IDataErrorInfo validation should run so that the CanExecute will query on the latest data in model and decide whether it is enabled or not. Here is the sample code that I am using
Model:
public class StandardContact :EntityBase, IDataErrorInfo
{
public virtual string Name
{
get { return _name; }
set { SetField(ref _name, value, () => Name); }
}
//...
//Validators
public string this[string propertyName]
{
get
{
string error = null;
//....
}
ViewModel
public class SContactEditViewModel : NotificationObject, INavigationAware
{
//....
StandardContact.PropertyChanged +=
new PropertyChangedEventHandler(StandardContact_PropertyChanged);
void StandardContact_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
//Requery if command can execute
SaveNewCommand.RaiseCanExecuteChanged();
}
}
I just inspected our priprietary MVVM library. Inside the ViewModels indexer (in your case this is the Models indexer) the requested Property is validated:
public string this[string propertyName]
{
get
{
string result = null;
if (CanDataErrorValidated(propertyName))
{
int errorCount = CurrentValidationAdapter.ErrorCount();
result = ValidateProperty(propertyName, GetValidateValue(propertyName));
// if the error flag has been changed after validation
if (errorCount != CurrentValidationAdapter.ErrorCount())
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
}
else
{
RaisePropertyChanged(PropHasError);
RaisePropertyChanged(PropError);
}
return result;
}
}
So the solution of your problem seems to validate the requested property on the fly.
I don't use prism, but if it exposes some sort of IsValid method or property you can use that to trigger your error checking. And if it doesn't you can write your own.
The basic idea without prism is to have to leverage IDataErrorInfo.Error by doing
bool IsValid{ get{return string.IsNullOrEmpty(Error) } // trigger validation
Then inside your Save.CanExecute method
return IsValid; // trigger validation on demand
HTH,
Berryl
On the main window onClick I have
AddNoticeAboutWrongCity addNoticeAboutWrongCity = new AddNoticeAboutWrongCity();
addNoticeAboutWrongCity.DataContext = ((VerificationViewModule)this.DataContext).WrongCityNotice;
addNoticeAboutWrongCity.ShowDialog();
At popup window there a lot of textboxes and two buttons
Delete object:
this.DataContext = null;
And second option "Save edited notice" which is not usable , because every change of user affection datacontext on main window,and this is demand from design department :)
I don't know why first option(it's "implementation" doesn't work.
Second explanation:
On the ParentWindow I have list of Notices and I can click EditSelectedNotice.
On the EditNoticeWindow I can edit Notice or delete Notice.
Editinig works(After closing EditNoticeWindow I see changed notice on the ParentWindow), but deleting doesn't (Notice is still in collection - on control and in this.DataContext)
My ViewModel:
class VerificationViewModule
{
public ObservableCollection<ReporterNotice> ReporterNotices { get; set; }
public ReporterNotice OtherNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Other).FirstOrDefault();
}
}
public ReporterNotice DuplicateNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.Duplicate).FirstOrDefault();
}
}
public ReporterNotice WrongCityNotice
{
get
{
return ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault();
}
set { if(value==null)
{
ReporterNotices.Remove(ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First());
}
else
{
if (ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).FirstOrDefault()==null)//there is always only max one instance of this type of notice
{
ReporterNotices.Add(value);
}
else
{
var c = ReporterNotices.Where(n => n.Type == ReporterNoticeType.WrongCity).First();
c = value;
}
}}
}
public VerificationViewModule()
{
ObservableCollection<ReporterNotice> loadedReporterNotices = new ObservableCollection<ReporterNotice>();
loadedReporterNotices.Add(new ReporterNotice() { Content = "Dublic", Type = ReporterNoticeType.WrongCity });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Hilton", Type = ReporterNoticeType.Duplicate });
loadedReporterNotices.Add(new ReporterNotice() { Content = "Another notice", Type = ReporterNoticeType.Other });
ReporterNotices = loadedReporterNotices;
}
}
You can try the following. Implement the mediator to display windows and make sure that you use view models for the DataContext for both the main and edit windows. It is important to tell the main view model that the object is being deleted. This is done via a callback and routing that through a command on the EditNoticeViewModel
//This viewmodel is on the main windows datacontext
public class ParentViewModel
{
private readonly IWindowMediator _mediator;
public ParentViewModel(IWindowMediator mediator)
{
_mediator = mediator;
}
public ObservableCollection<Notice> Notices { get; private set; } //bound to list in xaml
public void OpenNotice(Notice notice)
{
//open the window using the Mediator pattern rather than a new window directly
_mediator.Open(new EditNoticeViewModel(notice, DeleteNotice));
}
private void DeleteNotice(Notice notice)
{
//This will remove it from the main window list
Notices.Remove(notice);
}
}
//view model for EditNoticeWindow
public class EditNoticeViewModel
{
public EditNoticeViewModel(Action<Notice> deleteCallback, Notice notice)
{
Model = notice;
DeleteCommand = new DelegateCommand((a) => deleteCallback(Model));
}
//Bind in xaml to the Command of a button
DelegateCommand DeleteCommand { get; private set; }
//bound to the controls in the xaml.
public Notice Model { get; private set; }
}
//This is a basic interface, you can elaborate as needed
//but it handles the opening of windows. Attach the view model
//to the data context of the window.
public interface IWindowMediator
{
void Open<T>(T viewModel);
}
Depending on implementation you might want to close the view when the delete button gets pushed. You can do this by implementing something like the as described here with respect to WorkspaceViewModel
Why don't you wrap the WrongCityNotice in a viewModel implementing IReporterNotice and having a reference to the parent viewmodel and a Delete method:
public void Delete() { _parentvm.Delete(_wrongCityNotice); }
You can use this wrapper as DataContext.
You're trying to destroy the DataContext. C# doesn't work that way. Setting an object reference to null doesn't delete the object, it only removes the reference to it. (When nothing references an object anymore it gets garbage collected, but you can't destroy an object directly).
DataContext = null only means that locally your DataContext doesn't point to any object any more. The main view model still has a reference however so nothing changes there. You'll have to ask the main view model to remove the notification from it's collection (probably through a callback method (Action) is best so you don't have to know about the parent view model).