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 });
Related
I'm trying to make a listview in xamarin show data from a restapi but have the option to filter the list or sort it based upon last name.
I've set the bindingcontext equal to the apiviewmodel which works. But I want to set the itemssource to a list which can be manipulated later instead of the binding context.
Here is the code that works:
Xaml:
<ListView x:Name="DirectoryListView" ItemsSource="{Binding ContactsList}" IsPullToRefreshEnabled="True">
Xaml.cs:
LocalAPIViewModel = new APIViewModel();
BindingContext = LocalAPIViewModel;
APIViewModel.cs:
private List<MainContacts> _ContactsList { get; set; }
public List<MainContacts> ContactsList
{
get
{
return _ContactsList;
}
set
{
if(value != _ContactsList)
{
_ContactsList = value;
NotifyPropertyChanged();
}
}
}
public class MainContacts
{
public int ID { get; set; }
public string FirstName { get; set; }
}
This all works fine. It's only when I add the following lines that it stops displaying the data in the listview:
xaml.cs:
LocalList = LocalAPIViewModel.ContactsList;
DirectoryListView.ItemsSource = LocalList;
I think I need to add these lines so that I can manipulate the list that's being displayed. Why is the list not being displayed? Is this not how it should be done?
According to your description and code, you use MVVM to bind ListView firstly, it works fine, now you want to use Viewmodel to bind ListView itemsource in xaml.cs directly, am I right?
If yes,I do one sample according to your code, that you can take a look, the data can display successfully.
public partial class Page4 : ContentPage
{
public APIViewModel LocalAPIViewModel { get; set; }
public Page4 ()
{
InitializeComponent ();
LocalAPIViewModel = new APIViewModel();
listview1.ItemsSource = LocalAPIViewModel.ContactsList;
}
}
public class APIViewModel
{
public ObservableCollection<MainContacts> ContactsList { get; set; }
public APIViewModel()
{
loadddata();
}
public void loadddata()
{
ContactsList = new ObservableCollection<MainContacts>();
for(int i=0;i<20;i++)
{
MainContacts p = new MainContacts();
p.ID = i;
p.FirstName = "cherry"+i;
ContactsList.Add(p);
}
}
}
public class MainContacts
{
public int ID { get; set; }
public string FirstName { get; set; }
}
so I suggest you can check ContactsList if has data.
Update:
I want to be able to search the list with a search bar and also order it by first or last names. I also want to be able to click on one of the contacts and open up a separate page about that contact
I do one sample that can meet your requirement, you can take a look:
https://github.com/851265601/xf-listview
So, to answer all your questions...
First, the binding.
Once you set the ItemsSource="{Binding ContactsList}" this means that anytime you signal that you have changed your ContactsList by calling OnPropertyChanged(), that is going to be reflected on the ItemsSource property (so, update the UI - that is why we put the OnPropertyChanged() into the setter). Thus, you do not need to manually set the ItemsSource every time you change it. (Especially from the View, as the View should have no knowledge of how the ContactsList is defined in the ViewModel.)
So you can completely remove those lines from the View's code-behind.
Next, the ordering and searching.
What OnPropertyChanged() does, is that it re-requests the bound property from the ViewModel, and updates the View according to that. So, just after OnPropertyChanged() is called, the getter of the bound property (ContactsList) is called by the View.
So, a good idea is to put the sorting mechanism into the getter of the public property. (Or the setter, when resetting the property.) Something like this:
public class ViewModel {
private ObserveableCollection<MainContacts> contactList { get; set; }
public ObserveableCollection<MainContacts> ContactList {
get {
return new ObservableCollection<MainContacts>(contactList
.Where(yourFilteringFunc)
.OrderBy(yourOrderingFunc));
}
set {
contactsList = value;
OnPropertyChanged();
}
}
//...
}
So, whenever your public property is called, it will sort the private property and return the collection that way.
Change public List<MainContacts> ContactsList to public ObservableCollection<MainContacts> ContactsList
in xaml.cs
instead of LocalList = LocalAPIViewModel.ContactsList;, put
ContactsList = new ObservableCollection(LocalAPIViewModel.ContactsList);
I think this will work, instead of setting ListView's Itemsource to 'LocalList'
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.
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();
}
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 am really struggling to understand binding. I know there are loads of other threads with much the same title as this one, but they're all trying to do something more complex than I am, and all the answers assume a whole pile of stuff that I just don't get :(
I'm trying to display a dynamically updated message log. I've defined a Message class:
public class Message
{
public DateTime Timestamp { get; private set; }
public string Value { get; private set; }
public int Severity { get; private set; }
public Message(string value, int severity)
{
Timestamp = DateTime.Now;
Value = value;
Severity = severity;
}
}
I've defined a MessageLog class as simply:
public class MessageLog: ObservableCollection<Message>
{
public MessageLog(): base()
{ }
}
In my MainWindow constructor I have a Log property:
public MessageLog Log { get; private set; }
In the MainWindow constructor I initialise Log:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
// and so on
}
In the XAML for the main window I have:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding MessageLog}" IsEnabled="False"/>
Now if I add Message instances to the MessageLog I expected to see them appear in the ListBox. They don't. What have I missed?
Thanks in advance (and if you can point me somewhere that explains bindings clearly -- especially the view that XAML has of the code and where it can look for things -- then many more thanks on top. At the moment I'm using Matthew McDonald's "Pro WPF 4.5 in C#" and I'm just not getting it.)
Change your constructor:
public MainWindow()
{
InitializeComponent();
DataContext = this;
Log = new Model.MessageLog();
}
to this:
public MainWindow()
{
InitializeComponent();
Log = new Model.MessageLog(); // <- This line before setting the DataContext
DataContext = this;
}
Explanation:
Setting properties after having set the DataContext requires your class to implement INotifyPropertyChanged and raise change notifications after properties are set.
Since you're setting the DataContext before setting the property, the value of this.Log is null at the time of DataBinding, and WPF is never notified that it ever changed.
That being said, you don't usually put Data inside UI Elements (such as Window). The accepted and recommended approach to WPF is MVVM, where you usually create a ViewModel and set that as the Window's DataContext:
public class MyViewModel
{
public MessageLog Log {get;set;}
public MyViewModel()
{
Log = new MessageLog();
}
}
Window Constructor:
public MainWindow
{
DataContext = new MyViewModel();
}
Your collection property name is Log which is what you should be binding to in ItemsSource property; and if you have not done a typo in your question then you are binding wrongly to MessageLog, and change Binding as below:
<ListBox Name="MessagePanel" Height="100" ItemsSource="{Binding Log}" IsEnabled="False"/>
For more information and learning on Data Binding in WPF (4.5), see MSDN Data Binding Overview
The datacontext of the view must be the viewmodel.