I want to bind a listview to my viewmodel but I get ArgumentNullException.
I use xamarin.forms and the exception appears in android and ios projects but not in uwp.
Itemsource returns an exception when I use binding with the viewmodel. When i don't use binding in my xaml the exception disappears.
System.ArgumentNullException has been thrown
Value cannot be null.
Parameter name: element
ViewModel
private ObservableCollection<T> pages = new ObservableCollection<T>();
public ObservableCollection<T> Pages
{
get { return pages; }
set
{
pages = value;
OnPropertyChanged("Pages");
}
}
VM Constructor
public ViewModel()
{
_service = new Service();
someitems = _service.getitems();
Pages = new ObservableCollection<T>(someitems);;
}
Service
return new ObservableCollection<T>(items);
View
ItemsSource="{Binding Pages}"
The problem seems to be the setter pages = value;
What is wrong?
Based on the code posted it seems you are trying to initialize the Pages collection with null value.
public ViewModel()
{
Pages = new ObservableCollection<T>(someitems);;
}
In your ViewModel constructor someitems seems to be null. I assume this since you are using Generics and at that moment you probably don't know the type is gonna be used.
If you want/need to initialize it with a value you can do it passing a parameter in the ViewModel constructor:
public ViewModel(IList<T> someitems)
{
Pages = new ObservableCollection<T>(someitems);
}
Note: You don't need to create a backing field when working ObservableCollection, with an Auto-Property would be enough, but be advised that using this you must keep the same instance and when replacing objects you will do it clearing Clear() and adding Add() the new items.
public ObservableCollection<T> Pages { get; set; }
Hope this helps!
I changed my service to an async data source and finally I used:
Constructor
public ViewModel()
{
InitPages();
}
Async Init()
private async void Init()
{
Pages = await ServiceFunction();
}
Related
I have the strangest issue with an observable collection. I set my collection with some dummy data and it loads on the content page as expected however when I attempt to get the data from the data context its always null.
I debugged the code in the ViewModel and I can see the collection as null. Its clearly not null because I populates on the form.
Is there something im missing here !
private ObservableCollection<Company> _CompanyCollection;
public ObservableCollection<Company> CompanyCollection
{
get { return _CompanyCollection; }
set
{
if (value != null)
{
_CompanyCollection = value;
OnPropertyChanged();
}
}
}
Loading data
public void LoadTestCompanies()
{
CompanyCollection = new ObservableCollection<Company>()
{
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"}
};
}
Calling Viewmodel from event in page.cs
CompaniesVM viewModel = (CompaniesVM)BindingContext;
var results = viewModel.CompanyCollection.Where(x => x.Name.ToLower().Contains(searchBar.Text.ToLower()));
This is the code behind
public Companies ()
{
InitializeComponent ();
BindingContext = new CompaniesVM(this.Navigation);
}
ViewModel calls loatTestCompanies
public CompaniesVM(INavigation navigation)
{
// Navigation
Navigation = navigation;
LoadTestCompanies();
}
Ive tried many other ways of initialising the collection and use .Add(object> but nothing seems to be working.
Any ideas would be great.
Thank you
Two advises that may solve your problem:
1
Use a self-declared readonly property when referring to collections:
public ObservableCollection<Company> CompanyCollection { get; }
2
This change will force you to create the instance of CompanyCollection directly in the constructor:
public CompaniesVM(INavigation navigation)
{
Navigation = navigation;
CompanyCollection = new ObservableCollection<Company>();
LoadTestCompanies();
}
And then...:
public void LoadTestCompanies()
{
CompanyCollection.AddRange(new[]
{
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"},
new Company() { Name="The Suit Lounge"}
});
}
I believe that changing the reference itself for bound properties implies in ViewModel using an object instance and the View using another one. So the view 'stops' to listen to VM changes for that property.
I've never got into the deep of ItemsSources Views implementations, but I guess they kind of observe the items when binding collections - or the collection instance's properties in some cases - when getting changes notification.
With this changes, I guess your code should work fine.
Hope it helps.
When populating an observable collection, I can see that the "return" is not being called when I "set" the new data in the collection. It does work if I set the data from a different location in the program so I must be not understanding some nuance of the way it works. The part that works is when I take out the commented code under "This works", "ChooseFile()" does not. In the debugger I can see the OptionsToChoose has data in both cases. When it works the XAML is updated correctly.
class ScripterViewModel : BindableBase
{
public ScripterViewModel()
{
ScripterModel scripterModel = new ScripterModel();
ObservableCollection<string> tabsChoice = new ObservableCollection<string>();
tabsChoice.Add("Tabs");
tabsChoice.Add("Buttons");
Tabs = tabsChoice;
this.OpenFileBtn = new DelegateCommand(chooseFile, canChooseFile).ObservesProperty(() => OpenFile);
this.SaveFileBtn = new DelegateCommand(saveFile, canSaveFile).ObservesProperty(() => SaveFile);
//This works
//var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
//OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public void chooseFile()
{
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose = new ObservableCollection<Tabbed>(myJSONDoc.TabbedBtns);
}
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
_optionsToChoose = value;
}
}
}
When you are creating the OptionsToChoose in the constructor it will be initialized when the viewmodel is used by the view.
In the example that is not working, you are just replacing the ObservableCollection with a new one instead clearing it and adding the items. Therefore you need to notify that the property has been changed like V.Leon pointed out in his answer.
Or just clear the existing collection and populate it with the values from the json.
var myJSONDoc = JsonConvert.DeserializeObject<JSONclass>(File.ReadAllText(#"C:\Users\mike\Documents\Haas\Scripter\settings.json"));
OptionsToChoose.Clear();
foreach (var item in myJSONDoc.TabbedBtns)
{
OptionsToChoose.Add(item);
}
You are not raising PropertyChanged event in the setter of OptionsToChoose. You already extend BindableBase, so raising PropertyChanged event can be done by replacing your current OptionsToChoose property implementation with the following:
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
set
{
SetProperty(ref _optionsToChoose, value);
}
}
See BindableBase.SetProperty Method
Ideally, you should not change the whole reference of ObservableCollection after it is binded. Instead clear items in it and then add new items in it.
public ObservableCollection<Tabbed> _optionsToChoose = new ObservableCollection<Tabbed>();
public ObservableCollection<Tabbed> OptionsToChoose
{
get
{
return _optionsToChoose;
}
}
OptionsToChoose.Clear();
OptionsToChoose.Add(foo);
As has already been brought up, given your code you would need to make the property for your collection raise PropertyChanged if you were resetting the collection. That said ObservableCollection is really not an ideal collection type to use. What I would recommend is including MvvmHelpers in your project and using the ObservableRangeCollection
public class MyPageViewModel : BindableBase
{
public MyPageViewModel()
{
OptionsToChoose = new ObservableRangeCollection<Tabbed>();
SomeCommand = new DelegateCommand(OnSomeCommandExecuted);
}
public DelegateCommand SomeCommand { get; }
public ObservableRangeCollection<Tabbed> OptionsToChoose { get; }
private void OnSomeCommandExecuted()
{
// get some updated data
IEnumerable<Tabbed> foo = DoFoo();
OptionsToChoose.ReplaceRange(foo);
}
}
You get a couple of benefits there. One you're not allocating and deallocating your collection. Also the ObservableRangeCollection updates the full list before raising PropertyChanged or CollectionChanged events this results in few UI notifications and better app performance.
I'm using mvvmcross and xamarin to bind an ObservableCollection to a UITableView. The collection is updated in place using the Add, Remove and Move methods. These calls correctly trigger INotifyCollectionChanged events and the TableView is updated as expected the first time the view containing the table is shown. If the user navigates away from the original view as part of the normal application flow but later returns the correct data is loaded into the table but calls to add, move and remove no longer update the table.
The INotifyCollectionChanged events are still being fired when the collection is updated
If I manually subscribe to these events in my subclass of MvxStandardTableViewSource and try and call ReloadData on the UITableView still does not update
My presenter is creating a new instance of the viewmodel and view each time the page is visited.
I'm also using Xamarin-Sidebar (https://components.xamarin.com/view/sidebarnavigation) for navigation in my application with a custom presenter to load the views but as far as I can tell the view is initialised via exactly the same code path whether it's the first or subsequent visit.
My presenters Show() method looks like this:
public override void Show(MvxViewModelRequest request)
{
if (request.PresentationValues != null)
{
if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.ClearStack))
{
MasterNavigationController.ViewControllers = new UIViewController[0];
base.Show(request);
}
else if(NavigationFactory.CheckNavigationMode(request.PresentationValues, NavigationFactory.LoadView))
{
var root = MasterNavigationController.TopViewController as RootViewController;
var view = this.CreateViewControllerFor(request) as UIViewController;
root.SidebarController.ChangeContentView(view);
}
}
else
{
base.Show(request);
}
}
The binding in my ViewController looks like this:
public override void ViewDidLoad()
{
base.ViewDidLoad();
View.AutoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight;
var source = new TracksTableSource(TableView, "TitleText Title; ImageUrl ImageUrl", ViewModel);
TableView.Source = source;
var set = this.CreateBindingSet<TracksViewController, TracksViewModel>();
set.Bind(source).To(vm => vm.PlaylistTable);
set.Apply();
}
And my viewmodel is as below where PlaylistTable is a subclass of ObservableCollection with the Update method using add, move and remove to keep the collection up to date.
public class TracksViewModel : MvxViewModel
{
private readonly IPlaylistService _playlistService;
private readonly IMessengerService _messengerService;
private readonly MvxSubscriptionToken _playlistToken;
public PlaylistTable PlaylistTable { get; set; }
public TracksViewModel(IPlaylistService playlistService, IMessengerService messengerService)
{
_playlistService = playlistService;
_messengerService = messengerService;
if (!messengerService.IsSubscribed<PlaylistUpdateMessage>(GetType().Name))
_playlistToken = _messengerService.Subscribe<PlaylistUpdateMessage>(OnDirtyPlaylist, GetType().Name);
}
public void Init(NavigationParameters parameters)
{
PlaylistTable = new PlaylistTable(parameters.PlaylistId);
UpdatePlaylist(parameters.PlaylistId);
}
public async void UpdatePlaylist(Guid playlistId)
{
var response = await _playlistService.Get(playlistId);
PlaylistTable.Update(new Playlist(response));
}
private void OnDirtyPlaylist(PlaylistUpdateMessage message)
{
UpdatePlaylist(message.PlaylistId);
}
}
This setup works perfectly the first time the view is initialised and updates the table correctly, it's only the second and subsequent times the view is initialised that the table fails to update. Can anyone explain why the binding fails when it appears the view is created using the same techniques in both instances?
I can post additional code if required but I believe the issue will be how I'm using the presenter since the code I've not posted from PlaylistTable functions correctly in unit tests and on first viewing.
Recently I've discovered a strange behavior when I assign a ViewModel which comes back from a WCF service call to a DataContext in a UserControl element. When I first assign the ViewModel everything is fine. All XAML bindings are showing the correct and expected values if available.
But let's say I've saved changes in the ViewModel via another service call and get back an updated version of the ViewModel and now like to reassign the new ViewModel to the DataContext sometimes the DataContext and the ViewModel (which has been assigned) are losing attribute values (they are null after assigning).
Let me show you a simplified example to demonstrate
namespace MyProject.ViewModels
{
[DataContract]
public class MyViewModel : ViewModelBase //ViewModelBase implements INotifyPropertyChanged
{
#region Properties
private MyViewModelListItem _myViewModelListItem; //another nested ViewModel
[DataMember]
public MyViewModelListItem myViewModelListItem { get { return _myViewModelListItem; } set { _myViewModelListItem = value; RaisePropertyChanged(() => myViewModelListItem); } }
private string _aStringVariable; //just a simple string
[DataMember]
public string aStringVariable { get { return _aStringVariable; } set { _aStringVariable = value; RaisePropertyChanged(() => aStringVariable); } }
private MyEnum _myEnum; //an enumeration
[DataMember]
public MyEnum myEnum { get { return _myEnum; } set { _myEnum = value;RaisePropertyChanged(() => myEnum); } }
#endregion
}
}
Here is an extract of a simplified code behind of an xaml UserControl
//some other code parts not related to the problem
// get the VM
MyViewModel vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = vm; //everything is fine. all values are set as expected. Databinding works like a charm
}
Now let's imagine some changes in the vm via user input and data binding. The viewmodel is saved (calling a WCF service method) and after that I call the service again to get an updated version of the VM:
// get the VM again
vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = vm;
}
When I set a breakpoint before the assignment of the new ViewModel all values of any attribute of the ViewModel are set (because the service filled them with values)
But then right after the assignment some of the attribute values are null. I cannot say which one. I'm using a lot of different ViewModels in my project and in this example maybe the string is not null and the nested ViewModel also. But the enumeration is suddenly null. And at another place in the project maybe something else is suddenly null.
I assume that the DataContext variable has a setter method which is doing something strange (or I get it totally wrong).
Does anybody know what's happening here? And in addition what could be a "good" way of preventing this (maybe reassigning is the wrong approach).
For now I just set the DataContext to null before reassigning the ViewModel but it seems not the best way, does it?
vm = service.getMyViewModel();
if(vm != null){
//assign the datacontext
userControl.Grid.DataContext = null;
userControl.Grid.DataContext = vm;
}
Edit:
just to clarify: No viewmodels are saved into a database. They are properly converted to corresponding entities and then these are saved.
changed this to userControl.Grid to be more specific in the examples
How can I add or delete items from my DataContext? This is my code:
class WallModel
{
public WallModel()
{
WallItems = new ObservableCollection<Wall>();
Initialization = InitializeAsync();
}
public Task Initialization { get; private set; }
public async Task InitializeAsync()
{
WallItems.Add(new Wall { id = 2, user = 3 });
}
public ObservableCollection<Wall> WallItems { get; set; }
}
And MainPage.xaml.cs:
public MainPage()
{
this.InitializeComponent();
DataContext = new WallModel();
lvMain.DataContext = DataContext;
}
We don't generally add or remove items from a DataContext directly. Instead, (in MVVM) we try to create a class that incorporates all of the properties that we want to display in the UI and methods that perform the required functionality. Then we set an instance of this class as the DataContext.
Of course, you can just set a simple collection property as the DataContext of one control and in that case, you could just add or remove items from that collection as normal. However, it is generally preferred to manipulate the data item(s) set as the DataContext rather than the DataContext object itself.
You can use for example:
((WallModel)DataContext).WallItems.Remove(item);
or
((WallModel)DataContext).WallItems.RemoveAt(index);
....
Also if lvMain is in the MainPage you do not need to set its datacontext because it gets inherited.
As Sheridan mentions use a viewmodel and a Delete command which removes the item directly in the viewmodel.
((WallModel)DataContext).WallItems.Add(new Wall { id = 2, user = 3 });