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).
Related
I'm using MVVM in a Xamarin application, I have an interface to navigate between pages:
public interface INavigate
{
INavigate Next();
INavigate Previous();
string ViewTitle { get; }
}
In the implementing views:
public partial class V2Upload : ContentView, INavigate
{
public string ViewTitle => "Upload photos";
public INavigate Next()
=> new V3AdDetail();
public INavigate Previous()
=> new V1Agreement();
}
and in the view model
I have a property of type INavigate:
public INavigate CurrentAddItemStep
{
get { return _currentAddItemStep; }
set { Set(ref _currentAddItemStep, value); }
}
and the Content property of the parent view is bound to this property:
when next button is clicked I execute this code:
CurrentAddItemStep = CurrentAddItemStep.Next();
ViewTitle = CurrentAddItemStep.ViewTitle;
now a validation method is required before navigating to the next page for all the Content views..
I want to keep the MVVM pattern as clean as possible by not writing business code in the view, for example in the V2Upload view the File1 and File2 properties of the view model shouldn't be null:
private bool ValidateFiles(){
return (File1 ?? File2) != null;
}
but since the navigating is done dynamically in run-time, I can't know which view is the current view.
I'm thinking to use reflection , to know what is the name of the view (but this will break the whole design)
Another option is to provide a function parameter to the Next method, but also how to provide it in the design time from the view model?
This is what I'm doing now:
public INavigate Next()
{
if (((ViewModel.AddItemViewModel)BindingContext).ValidateFiles())
return new V3AdDetail();
else
return this;
}
but again, I'm accessing the view model from the view (and had to change the ValidateFiles method from private to public), which I want to avoid
I am developing a small app to test MVVM pattern with WinForms, using ReactiveUI (6.5, latest version so far). I made some progress with commands (ReactiveCommand) and some Bindings between properties and TextBoxes.
I am stuck now trying to bind a ReactiveList of items to a listbox (my intention is to automatically update the listbox, once an element is added to the list, and see the new element inside the listbox).
Here the code:
ViewModel:
public class PersonViewModel : ReactiveUI.ReactiveObject
{
(...)
public ReactiveList<int> Ids { get; private set; }
public PersonViewModel ()
{
Ids = new ReactiveList<int>();
(...)
}
//The command that adds a new item inside the list
private void AddPerson(int id)
{
Ids.Add(id);
}
}
MainForm
public partial class MainForm : Form, IViewFor<PersonViewModel>
{
public MainForm()
{
InitializeComponent();
ViewModel = new PersonViewModel();
//PersonsListBox.DataSource = ViewModel.Ids; -> this was an idea, it doesn't work either
this.WhenActivated(d =>
{
d(this.Bind(ViewModel, x => x.Ids, x => x.PersonsListBox.DataSource)); // Binding attempt, doesn't seem to be working
d(this.BindCommand(ViewModel, x => x.AddPersonCommand, x => x.AddPersonButton)); // Command, it works
});
}
public PersonViewModel ViewModel { get; set; }
object IViewFor.ViewModel
{
get { return ViewModel; }
set { ViewModel = (PersonViewModel)value; }
}
}
Any ideas on that? My intention is to use it in different controls that make sense to be used with lists (dataGrids, listViews, listBoxes etc.), and hopefully there is a method to do it, in the same way it is done with textboxes.
You should use ReactiveBindingList instead ReactiveList.
Ok so I'm fairly new to this. I followed along with this MVVM tutorial from YouTube. It was pretty good and straightforward. Basically it sets up a very basic program with a Model class, DataAcess class, 3 viewmodels (Main window, Employee and ViewModelBase) and finally a view which has a stackpanel and a couple of text boxes that are bound to the FirstName and LastName in the Model.
It all works how it's meant to and I have been through it a number of times and I'm pretty sure I understand how it all works but the trouble that I am having is adding new Employees.
In the DataAccess class (Employee Repository) Employees are added as shown below.
class EmployeeRepository
{
readonly List<Employee> _employee;
public EmployeeRepository()
{
if (_employee == null)
{
_employee = new List<Employee>();
}
_employee.Add(Employee.CreateEmployee("Bob", "Jones"));
_employee.Add(Employee.CreateEmployee("Sarah", "Marshall"));
_employee.Add(Employee.CreateEmployee("Peter", "Piper"));
}
public List<Employee> GetEmployees()
{
return new List<Employee>(_employee);
}
}
And in the Model there is a method call CreateEmployee as such
public class Employee
{
public static Employee CreateEmployee(string firstName, string lastName)
{
return new Employee { FirstName = firstName, LastName = lastName };
}
public string FirstName { get; set; }
public string LastName { get; set; }
}
So I thought I would add a button to the MainWindow and then add another name to the list. Hopping the view would update as an item is updated. Just to see if it would work I just used the code behind.
I thought I could just add a new employee the same way I did in the EmployeeRepository so I tried this
readonly List<Employee> _employee = new List<Employee>();
private void btnAdd_Click(object sender, RoutedEventArgs e)
{
_employee.Add(Employee.CreateEmployee("John", "Smith"));
}
I have tried many many ways of doing this, to no avail. I have watched and read many tutorials and questions, but nothing that I have tried as worked.
What am I missing? I initially thought that it was not working because I am adding the item to the List in the repository, but not to the ObservableCollection that is in the viewmodel. And the AllEmployees ObservableCollection is the ItemSource for view.
readonly EmployeeRepository _employeeRepository;
public ObservableCollection<Model.Employee> AllEmployees
{
get;
private set;
}
public EmployeeListViewModel(EmployeeRepository currentWindowRepository)
{
if (currentWindowRepository == null)
{
throw new ArgumentException("currentWindowRepository");
}
_employeeRepository = currentWindowRepository;
this.AllEmployees = new ObservableCollection<Model.Employee>(_employeeRepository.GetEmployees());
}
But in the button code I tried to implement something similar, but no.
I can also add the view xaml code and MainViewModel codes so that you can see how it's all bound if you like.
Thanks in advance for any help!
You can't do it in "one operation".
When you add a new Employee in the UI, you first need to instantiate your Employee class and add it to the observable collection.
If in valid state, then persist it to in the repository.
private ICommand addEmployeeCommand;
public ICommand AddEmployeeCommand { get { return addEmployeeCommand; } }
public ObservableCollection<Employee> Employees { get; protected set; }
private void AddEmployee()
{
// Get the user input that's bound to the viewmodels properties
var employee = Employee.Create(FirstName, LastName);
// add it to the observable collection
// Note: directly using model in your ViewModel for binding is a pretty bad idea, you should use ViewModels for your Employees too, like:
// Employee.Add(new EmployeeViewModel(employee));
Employees.Add(employee);
// add it to the repository
this.employeeRepository.AddOrUpdate(employee);
}
// in constructor
this.addEmployeeCommand = new DelegateCommand(AddEmployee, CanExecuteAddEmployee);
As noted, avoid directly using your model inside the ViewModel bindings, it has several disadvantages, like you view now depend on your viewmodel. each and every change in the model needs to be reflected in the view, this beats the purpose of a viewmodel which is meant to decouple view, viewmodel and model.
Another disadvantage is, that typically your models are do not implement INotifyPropertyChanged and this will cause memory leaks in the view.
In your EmployeelistViewModel you are creating ObservableCollection , and you think that it will get repopulated automatically upon addition/deletion of employees. secondly in your GetEmployees method you are creating a new list. you should use obser.coll directly in place of List (_employee). And return this ocoll from your method.
One solution to this is to add INPC to your models and to then have your view models watch their models and update themselves accordingly i.e. something like this:
public class MyListType
{
// some data
}
public class MyModel
{
public IList<MyListType> MyListItems { get; set; }
public MyModel()
{
this.MyListItems = new ObservableCollection<MyListType>();
}
}
public class MyListTypeViewModel : ViewModelBase
{
public MyListType Model {get; set;}
// INPC properties go here
}
public class MyViewModel
{
public IList<MyListTypeViewModel> MyListItemViewModels { get; set; }
public MyViewModel(MyModel model)
{
(model.MyListItems as INotifyCollectionChanged).CollectionChanged += OnListChanged;
// todo: create initial view models for any items already in MyListItems
}
private void OnListChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// create any new elements
if (e.NewItems != null)
foreach (MyListType item in e.NewItems)
this.MyListItemViewModels.Add(new MyListTypeViewModel{Model = item});
// remove any new elements
if (e.OldItems != null)
foreach (MyListType item in e.OldItems)
this.MyListItemViewModels.Remove(
this.MyListItemViewModels.First(x => x.Model == item)
);
}
Now your list of view models will automatically stay synched with your list of models. The main problem with this approach is that your models will typically originate from your ORM (database) code, so you will need to work with whatever framework you're using to inject INPC at creation time e.g. if you're using NHibernate then you'll need to use a binding interceptor for INPC and a collection convention to make the lists ObservableCollections.
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>();
}
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.