I have a ComboBox that it looks like this:
<ComboBox
ItemsSource="{Binding JobList}"
SelectedValue="{Binding Job,UpdateSourceTrigger=PropertyChanged,Mode=TwoWay}"
DisplayMemberPath="Title"
SelectedValuePath="Id"
IsEditable="True"
StaysOpenOnEdit="True"
/>
and its binding to my ViewModel that looks like this one:
public class ViewModel {
// this will fill from a database record for a person
public Job Job {
get { return _job; }
set {
if(value == _job) return;
_job = value;
OnPropertyChanged( () => Job );
}
}
// this will fill from all jobs records in database
public ObservableCollection<Job> JobList
{ /* do same as Job to implementing INotifyPropertyChanged */ }
}
and the Job is:
public class Job {
public int Id { get; set; }
public string Title { get; set; }
}
Really, I want to fill the ComboBox with job's list. So if the user's specified Job was in list, user can select it from list, otherwise, he enter a new Job.Title in ComboBox, the view model notify on it, and create a new Job item and also add it to JobList.
Have you any idea? can you help me please?
Create a string property in the viewModel something like 'SelectedJobName'
Bind this property to Combobox.Text
Wherever you want to use the entered value (Command, Presenter), check if selected value is not null and selectedJobName property value is not/matching.
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'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 problem with binding to SelectedItem property of ComboBox.
There is an ObservableCollection, which is binded to ItemsSource property and
another object field, which I want to bind to SelectedItem property, in the app.
But the application doesn't even start, because of Target Invocation Exception.
I don't know, if is it important to bind SelectedItem with one of the property of one instance of ItemsSource or I can use declare another property in viewmodel for it. I tried both variants. Didn't help. I've read some threads about such problem, but those solutions doesn't solve this.
<ComboBox x:Name="CategoryComboBox"
ItemsSource="{Binding CategoryList}"
DisplayMemberPath="Name"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectionChanged="CategoryComboBox_SelectionChanged"
/>
public ObservableCollection<IItem> CategoryList { get; set; }
public IItem SelectedCategory
{
get
{
return _selectedCategory;
}
set
{
_selectedCategory = value;
RaisePropertyChangedEvent(nameof(SelectedCategory));
}
}
public interface IItem
{
int Id { get; set; }
string Name { get; set; }
}
private void CategoryComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
var catName = (e.AddedItems[0] as IItem).Name;
vm.SelectedCategory = vm.CategoryList.Where(w => w.Name == catName).Select(s => s.Id).FirstOrDefault();
}
public void LoadLanguageList()
{
LanguageList = Repository.Current.GetLanguageList();
_selectedLanguage = LanguageList.FirstOrDefault(i => i.Id == 1);
RaisePropertyChangedEvent(nameof(SelectedLanguage));
}
In the code upper you can see the way, how I try to bind, then the collection property, selected item property and the type of items as interface.
I know, that it's impossible to create an instance of interface, but I don't know if binding object of such type is incorrect. But I tried to bind to another object type of class, which implements this interface, and the result was the same.
SelectedCategory= CategoryList [0];
vm.SelectedCategory = vm.CategoryList.Where(w => w.Name == catName).FirstOrDefault();
these 2 need to be changed
Note: you don't have to create an event for SelectionChanged. if the item is changed in ui it will automatically assign to SelectedCategory im assuming ur using MVVM so u set data context
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 am studying mvvm and face this problem with dynamic addition - in simple win forms I have done this easily with one loop and just few specification to DataRow..
So, the task is - to put all elements from List of strings to Grid/DataGrid, that contains two columns - first for check box and second for the string-based control.
I think the best idea is to use DataGrid. So I created a wpf dialog with this DataGrid and buttons and a separate file for ViewModel.
Now my ViewModel class contains a List of strings.
And I stuck.. I have read about some ObservableCollection<UIElement> that must hold DataGridRow (??) with two controls in my case..
Edit: I am trying <DataGridCheckBoxColumn for check box and <DataGridTemplateColumn for control. So the question now is binding this two columns with a list of strings - pass value of the string to control and all OK.
Need I use an ObservableCollection for that?
When i am binding a datagrid in wpf using mvvm rather than looking at it as a collection of rows and columns i see it as a collection of objects - each row represents an individual object, and each column represents a property of that object. So in your case, you should make a class to represent what you are showing in your grid, and it will have a boolean and a string property in it (to use in the 2 columns you have stated).
public class MyListItem : ImplementPropertyChangedStuff
{
private string _myString;
private bool _myBool;
public MyListItem()
{ }
public string MyStringProperty
{
get { return _myString; }
set
{
_myString = value;
this.RaisePropertyChanged("MyStringProperty");
}
}
public bool MyBoolProperty
{
get { return _myBool; }
set
{
_myBool = value;
this.RaisePropertyChanged("MyBoolProperty");
}
}
}
Now in your viewmodel, rather than having separate lists for each column, you can have a single list. If you wish to add/remove/edit rows, then you should use the observable collection, as this has the propertychanged stuff inbuilt and will update the ui when any changes are made to the collection.
public class MyViewModel
{
private ObservableCollection<MyListItem> _items;
public ObservableCollection<MyListItem> Items
{
get { return _items; }
set
{
_items = value;
this.RaisePropertyChanged("Items");
}
}
public MyViewModel()
{
this.Items = new ObservableCollection<MyListItem>();
this.LoadMyItems();
}
public void LoadMyItems()
{
this.Items.Add(new MyListItem { MyBoolProperty = true, MyStringProperty = "Hello" };
}
}
And finally the DataGrid binding:
<DataGrid ItemsSource="{Binding Path=Items, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridCheckBoxColumn Header="MyBoolProperty" Binding="{Binding Path=MyBoolProperty}"
<DataGridTextColumn Header="MyStringProperty" Binding="{Binding Path=MyStringProperty"/>
</DataGrid.Columns>
</DataGrid>
You need a view model for data row. Something like this:
public class DataRowViewModel
{
public bool? IsChecked { get; set; }
public string Text { get; set; }
}
Then, instead of List<string>, view model for dialog should expose List<DataRowViewModel>, or, if you're planning to modify this list from code, ObservableCollection<DataRowViewModel>:
public class DialogViewModel
{
// other code here
public ObservableCollection<DataRowViewModel> DataRows
{
get { return dataRows ?? (dataRows = new ObservableCollection<DataRowViewModel>(yourStringList.Select(s => new DataRowViewModel { Text = s }))); }
}
private ObservableCollection<DataRowViewModel> dataRows;
}
Next, setup DataGrid to be bound to DataRows collection, and bind its columns to IsChecked and Text respectively.
Note, that for simplicity I've omitted INPC implementation in DataRowViewModel. This will work, but if you're going to change data row properties from view model's code, you should implement INPC.