Bind User Input from Text boxes to Collection of Objects via TabControl - c#

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!!

Related

How to set listview itemssource to a viewmodel in Xamarin?

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'

Binding to an object

I have a simple Person model:
public class Person
{
public string FirstName { get; set; }
public DateTime LastUpdated { get; set; }
}
Lets say that I have a View that has a TextBox, which is binded to LastUpdated field:
<TextBox Grid.Column="1" Margin="5" Text="{Binding Person.FirstName}"/>
Now I need to implement PropertyChanged somehow. Let's use Prism Snippet.
What I need is to perform SetProperty on a Person class:
private Person person;
public Person Person
{
get { return person; }
set { SetProperty(ref person, value); }
}
and NOT on the field LastUpdated in Person class:
private DateTime? lastUpdated;
public DateTime? LastUpdated
{
get { return lastUpdated; }
set { SetProperty(ref lastUpdated, value); }
}
This is not the matter of dependencies in the model. I got the model through DataContract ( WCF service ) so I cannot changed it. So is there a way to observe a class for changes and bind class field to some UI control.
So is there a way to observe a class for changes and bind class field to some UI control.
No. You need to raise the PropertyChanged event for the object of the property that you are actually binding to.
If you get the Person object from some third-party service for which you cannot modify the code to raise the PropertyChanged event in the setter of the FirstName property, you should not bind to these objects.
Instead you should create your own view model class and bind to this one. The view model can simply wrap the WCF class, e.g.:
public class PersonViewModel
{
private readonly Person _person;
public PersonViewModel(Person person)
{
_person = person;
}
public string FirstName
{
get { return _person.FirstName; }
set { _person.FirstName = value; RaisePropertyChanged(); }
}
}
If you're using Prism, then you likely are using the MVVM pattern. If so, then the one approach is using the view model for binding. Instead of exposing Person as a property, expose the individual properties you want to bind against - FirstName and LastUpdated:
Create a property on the view model that forwards calls to your model.
Bind your view to the view model property.
You can freely implement your change notifications in the view model.

Add Tag to ComboBox Items From Model

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.

How to Add CRUD Operations to a Second Entity in WPF

I'm working on a WPF program, I use EF and MVVM. I have two entities : Student and Grade. Each student can have multiple grades. I have one window and one ViewModel(Student). I can add/update/delete student info using the program. When I add grades from database, I can see grades on the screen. I don't know how to modify my program to enable user enter,modify,delete grades on that screen. Can you give me some tips or advices about that? I'm open to new architecture suggestions. Should I add another user control or something like that? Here is my program currently looks like. Thanks.
YOu should start by modeling the viewmodels in a nice way (forget everything about database for now, this has nothing to do with viewmodels & views):
public sealed class MainViewModel : INotifyPropertyChanged
{
public ObservableCollection<StudentViewModel> Students {get; set;}
}
public sealed class StudentViewModel : INotifyPropertyChanged
{
public ObservableCollection<GradeViewModel> Grades {get; set;}
public StudentViewModel(){
Grades = new ObservableCollection<GradeViewModel>();
}
}
public sealed class GradeViewModel : INotifyPropertyChanged
{
public string Name { get; set; }
public int Value { get; set; }
}
Once you've modelled everything nicely, you'll eventually have list of students somewhere.
You should add another view for upgrading grades. I am assuming that your are familiar with how to switch views and viewmodels.
In your second view you should add a ComboBox/ListBox. Populate this with from students table.
In your xaml
<ComboBox ItemsSource={Binding Students} SelectedItem={Binding SelectedStudent} />
In your view model.
public ObservableCollection Students
{
get { return DatabaseContext.Students.ToList(); }
}
And when you select item in combobox, get grades for the selected student
private Student _selectedStudent;
public Student SelectedStudent
{
get { return _selectedStudent; }
set
{
_selectedStudent = value;
GetGradesList();
}
}
private void GetGradesList()
{
var grades = DatabaseContext.Grades.Where(g => g.Student = SelectedStudent);
// populate this to grid view like you have done in your current view.
// And for selected row in the grid you will get values in your textboxes, update those values,
// and then upgrade that to the database.
}
public void UpgradeGrade()
{
var grade = DatabaseContext.Grades.FirstOrDefault(g => g == SelectedGrade);
grade.Value = UpdatedValue;
DatabaseContext.SaveChanges();
}

binding an editable combobox and detect inserted text in wpf

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.

Categories

Resources