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'
Related
I'm relatively new to WPF and the MVVM architecture, as such I'm having issues with this data binding scenario.
I'm currently creating an application that allows users to create new People via a TabControl. When a user creates a new tab, it auto-populates with custom data fields such as First Name, Last Name, Age, etc. I then need to take this information, insert it into an ObservableCollection and use it to populate a WPF report (which I'm not having any issues with). I'm using a tab control so the user can go back and forth and edit the data as needed.
My issues is that only the first item in the tab control actually populates into the ObservableCollection. I have a button that creates a new tab, populates the content with a UserControl. I used the SelectedPerson so that I can go back in the list and access the Person that corresponds with the TabIndex that's currently being viewed so I can change/add/update the string information.
Here is my XAML code:
<TextBox ... Text="{Binding SelectedPerson.FirstName}"/>
<TabControl ... SelectedIndex="{Binding PersonIndex} ... />
My Caregiver class is as follows:
public class Person{
public string FirstName { get; set; }
public string LastName { get; set; }
}
And my C# code looks like:
private int _PersonIndex;
private Person _SelectedPerson;
private ObservableCollection<Person> Persons = new ObservableCollection<Persons>();
public void AddPerson (Person p){
SelectedPerson = p;
Persons.Add(p);
PersonIndex = Persons.Count - 1;
}
public Person SelectedPerson {
get {
return _SelectedPerson;
}
set {
_SelectedPerson = value;
OnPropertyChanged("SelectedPerson");
}
}
public int PersonIndex{
get {
return _PersonIndex;
}
set {
SelectedPerson = Persons[value];
OnPropertyChanged("PersonIndex");
}
}
Any help would be greatly appreciated!!
I have a TypeOfContact model that is made up of an ID, and Text. For example, one type would be Telephone and the ID would be 1. Another type would be Email and the ID 2.
What I would like to do is add the text of the TypeOfContact as an item and the ID as a tag. I imagine it would look something like this, however this isn't working;
contactTypeComboBox.Items.Clear();
foreach (TypeOfContact c in ContactTypes)
{
contactTypeComboBox.Items.Add(c.ContactTypeText);
foreach (ComboBoxItem item in contactTypeComboBox.Items)
{
item.Tag = c.ContactTypeID;
}
}
The reason I want to do this is that when someone selects one of the ComboBox items I want to store the text and the ID. I could do this all through XAML but ContactTypes is a list that is populated by the user, so I cannot hard code the values into the ComboBox as maintaining it and adding new TypesOfContact would be difficult.
I fixed this issue myself by first adding;
DisplayMemberPath="ContactTypeText" SelectedValuePath="ContactTypeID"
to the XAML of the ComboBox then accessing the ID like;
contactTypeComboBox.SelectedValue
In your situation i would bind the list of your TypeOfContacts as ItemsSource to the ComboBox. After that you could set the tag, but i think you don't will need it, because when you also bind the SelectedItem you got back the whole item (ID, type, ...) and can work with it in other parts of your code.
Example for simplifying without a ViewModel (but you should use one):
Codebehind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = this;
FillListWithSomeExamples();
}
private void FillListWithSomeExamples()
{
TypesOfContacts.Add(new TypesOfContact {Id = 1, Type = "Email"});
TypesOfContacts.Add(new TypesOfContact { Id = 2, Type = "Telephone" });
}
public TypesOfContact SelectedTypesOfContact { get; set; }
public ObservableCollection<TypesOfContact> TypesOfContacts { get; set; } = new ObservableCollection<TypesOfContact>();
}
TheTestmodel:
public class TypesOfContact
{
public int Id { get; set; }
public string Type { get; set; }
}
XAML
<Grid>
<ComboBox ItemsSource="{Binding TypesOfContacts}" SelectedItem="{Binding SelectedTypesOfContact}" DisplayMemberPath="Type"/>
</Grid>
Now you can read the selected item in any other method of the MainWindow by looking at SelectedTypesOfContact.
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?
I'm currently Binding my Listbox to a DTO. Following MVVM patterns, How do I interact with what was selected from the Listbox.
I want something like this [1 being the "FileName"
If (Listbox.Selecteditem[1] == "samplefilename")
{ Messagebox.Show("Files matched"}
But how exactly is that done using MVVM? Do I have to create SelectedValue bindings/properties?
public class FilesDTO : IDTO
{
public int Id { get; set; }
public string FileName { get; set; }
public string FileExtension { get; set; }
public byte[] FileArray { get; set; }
}
You just need to add a "SelectedFileDTO" to your ViewModel and in your XAML, make sure it's set to TwoWay. When it changes in the View, your SelectedFileDTO setter will be hit in your ViewModel.
<ListBox ItemsSource={Binding Files} SelectedItem={Binding SelectedFileDTO, Mode=TwoWay}/>
public FilesDTO SelectedFileDTO
{
get...
set...
}
Yes, you should bind the SelectedValue property to a DependencyProperty. Then whenever you want to use it, it is right there at your disposal. You could also give the DependencyProperty changed events to utilize.
I am using a ListCollectionView as a datacontext to a tab control. I have added a GroupDescription to it based on an enum and I want a particular group to appear as the first tab in the tab control however now it is always being put on the bottom.
Profiles = new ListCollectionView(_profiles);
Profiles.GroupDescriptions.Add(new PropertyGroupDescription("ProfileType"));
Profiles.SortDescriptions.Add(new SortDescription("ProfileName", ListSortDirection.Ascending));
_profiles is an ObservableCollection of my profile ViewModels.
My Enum looks like:
public enum ProfileTypeEnum
{
CurrentSettings,
CustomSettings,
DefaultSettings
}
So how can I force the CurrentSettings group to always be first?
Try to use an auxiliary property:
public class Item
{
public ProfileTypeEnum ProfileType { get; set; }
public string ProfileName { get; set; }
public int ProfileTypeValue { get { return (int)ProfileType; } }
}
give values to the enumeration:
public enum ProfileTypeEnum
{
CurrentSettings=0,
CustomSettings=1,
DefaultSettings=2
}
and add a sort description:
Profiles.SortDescriptions.Add(new SortDescription("ProfileTypeValue", ListSortDirection.Ascending));
Profiles.SortDescriptions.Add(new SortDescription("ProfileName", ListSortDirection.Ascending));
this way you can use enumeration's values to alter the order and force one on top.