Xamarin forms - MVVM observable collection always null - c#

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.

Related

ObservableCollection Binding Exception - ListView ItemSource Xamarin

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();
}

ObservableCollection not returning the new data after it is set

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.

Set SelectedItem of ComboBox from object

I'm building an MVVM Light WPF app in Visual Studio 2015 with Entity Framework 6 (EF) providing the data. I have a ComboBox that displays the reasons why someone needs to take a drug test and it looks like this:
<ComboBox ItemsSource="{Binding ReasonsForTest}"
SelectedItem="{Binding Path=ReasonsForTestVm,
UpdateSourceTrigger=PropertyChanged}"
DisplayMemberPath="Description" />
The ReasonsForTest is of type ReasonForTestViewModel class:
public class ReasonForTestViewModel: ViewModelBase
{
private int _ReasonForTestId;
private string _ReasonForTestAbbr;
private string _description;
public int ReasonForTestId
{
get { return _ReasonForTestId; }
set
{
if (value == _ReasonForTestId) return;
_ReasonForTestId = value;
RaisePropertyChanged();
}
}
public string ReasonForTestAbbr
{
get { return _ReasonForTestAbbr; }
set
{
if (value == _ReasonForTestAbbr) return;
_ReasonForTestAbbr = value;
RaisePropertyChanged();
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged();
}
}
}
I have a data service class that contains the following code to fetch the data for the valid values of the ComboBox:
public async Task<ObservableCollection<ReasonForTestViewModel>> GetReasonsForTest()
{
using (var context = new MyEntities())
{
var query = new ObservableCollection<ReasonForTestViewModel>
(from rt in context.ReasonForTests
orderby rt.description
select new ReasonForTestViewModel
{
ReasonForTestId = rt.ReasonForTestID,
ReasonForTestAbbr = rt.ReasonForTestAbbr,
Description = rt.description,
});
return await Task.Run(() => query);
}
}
The view model populates the ComboBox using this:
var dataService = new TestDataService();
ReasonsForTest = await dataService.GetReasonsForTest();
The ComboBox has the correct data; however, it's not selecting the correct value when the app starts -- it's showing blank on load. The SelectedItem (ReasonsForTestVm) is also of that class type ReasonForTestViewModel and gets populated from the database with the one item for this person. I've stepped through the code to ensure ReasonsForTestVm has the correct data, and it does.
Here's the property for ReasonsForTestVm:
public ReasonForTestViewModel ReasonForTestVm
{
get
{
return _reasonForTestVm;
}
set
{
if (Equals(value, _reasonForTestVm)) return;
_reasonForTestVm = value;
RaisePropertyChanged();
}
}
What am I doing wrong here? I'm about to lose my mind!
Update: Sorry for the confusing name in the property above. Fixed.
Any WPF items control that extends Selector (such as ComboBox and ListBox) has two properties that are often used in conjunction: ItemsSource and SelectedItem.
When you bind a collection to ItemsSource, a representation of those items are shown in the UI. Each one of the representations is bound to an instance found within the collection bound to ItemsSource. If, for an example, you're using a DataTemplate to create that representation, you'll find within each that the DataContext will be one of those instances from the collection.
When you select one of these representations, the SelectedItemproperty now holds the instance from the collection that was bound to that representation.
This works perfectly through user interaction with the UI. However, there's one important caveat when interacting with these controls programmatically.
It's a very common pattern to bind these properties to similar properties in your view model.
public class MuhViewModel
{
public MuhItems[] MuhItems {get;} = new[]{ new Item(1), new Item(2) };
// I don't want to show INPC impls in my sample code, kthx
[SuperSlickImplementINotifyPropertyChangedAttribute]
public MuhSelectedItem {get;set;}
}
bound to
<ComboBox ItemsSource="{Binding MuhItems}"
SelectedItem="{Binding MuhSelectedItem}" />
If you try to manually update the selected item this way...
muhViewModel.MuhSelectedItem = new Item(2);
The UI will not change. The Selector sees that ItemsSource has changed, yes, but it doesn't find that instance in the ItemsSource collection. It doesn't know that one instance of Item with a value of 2 is equivalent to any other Item with the same value. So it does nothing. (That's a bit simplistic for what really happens. You can bust out JustDecompile and see for yourself. It gets real convoluted down in there.)
What you should be doing in this situation is updating SelectedItem with an instance found within the collection bound to ItemsSource. In our example,
var derp = muhViewModel.MuhItems.FirstOrDefault(x => x.MuhValue == 2);
muhViewModel.MuhSelectedItem = derp;
Side note, when tracking instances within a debug session, it helps to use Visual Studio's Make Object ID feature.

MVVM loses command binding

I have an unusual problem with my view model. I have a list of the items and I need to to have a button with attached command to each item. I'm using ItemsSource and each item is represented with this view model:
public class CarItemViewModel : ViewModelBase, ICarItemViewModel
{
public void Init(Car definition, Action<Car> onSelection)
{
Wehicle = definition;
SelectCarCommand = new RelayCommand(() => onSelection(definition));
}
public Car Wehicle { get; private set; }
public ICommand SelectCarCommand { get; private set; }
}
Then in my ViewModel for page I'm calling method below to populate list in OnNavigatedTo or Loaded event:
public void ShowCars()
{
var newCar = new Car()
{
Make = "Mazda",
Model = "MX-5"
};
var carVM = new CarItemViewModel();
carVM.Init(newCar, SelectCar);
Cars.Add(carVM);
}
Binding for data is working fine. I can see names etc but button with bound command is sometimes inactive and it won't hit a break point in SelectCar method. When I do a little trick and before calling ShowCars() I add Task.Delay(200) it will be fine.
I'm developing for Windows Phone 8 Silverlight and using newest MVVM Light. Anyone got similar issue?

Add/Delete Items from DataContext

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 });

Categories

Resources