EDIT: This is a Windows Store (8.1) application
I have a person class as shown below which I am using as a model
public class Person : BaseModel
{
private string _FirstName;
public string FirstName
{
get { return _FirstName; }
set
{
if (_FirstName == value)
return;
_FirstName = value;
RaisePropertyChanged("FirstName");
}
}
private string _MiddleName;
public string MiddleName
{
get { return _MiddleName; }
set
{
if (_MiddleName == value)
return;
_MiddleName = value;
RaisePropertyChanged("MiddleName");
}
}
private string _LastName;
public string LastName
{
get { return _LastName; }
set
{
if (_LastName == value)
return;
_LastName = value;
RaisePropertyChanged("LastName");
}
}
}
where BaseModel is defined as below
using GalaSoft.MvvmLight;
public class BaseModel: ObservableObject
{
}
I am using the Model in a ViewModel class as shown below.
using GalaSoft.MvvmLight;
public class PersonViewModel : ViewModelBase
{
/// <summary>
/// List of searched People
/// </summary>
private ObservableCollection<Person> _People;
public ObservableCollection<Person> People
{
get { return _People; }
set
{
if (_People== value)
return;
_People= value;
RaisePropertyChanged("People");
}
}
}
I am binding the People collection to a GridView as shown below.
<GridView
x:Name="PeopleSearchResultsGridView"
ItemsSource="{Binding People}">
</GridView>
When the search completes I get back a list of people which I add to the list as follows.
var list = await p.SearchPeople();
People = new ObservableCollection<Person>(list);
I see that the setter for the People collection is firing and the RaisePropertyChanged("People") event is also firing however that is not updating the GridView. Can anyone tell me what is wrong here ?
Related
I have two listboxes. One source listbox which is binded to ObservableCollection<Person> MyNetwork The other listbox is my target listbox which is binded to ObservableCollection<Person> Crew.Each time I drop the item in the target listbox I create a new instance of the sourceItem.
Now I would like to update the properties of new istances, but it doesn't seem to work. Is there a way to make the copies of the sourceItems to update when I change one of the sourceItems(FirstName) properties. I'm pretty new to WPF and MVVM and wonder if this is possible or is there a work around to achieve this?
Here what I have so far:
in the ViewModel
Source ListBox:
private ObservableCollection<Person> _myNetwork = new ObservableCollection<Person>();
public ObservableCollection<Person> MyNetwork
{
get { return _myNetwork; }
set { _myNetwork = value; RaisePropertyChanged(); }
}
Target ListBox:
private ObservableCollection<Person> _crew = new ObservableCollection<Person>();
public ObservableCollection<Person> Crew
{
get { return _crew; }
set { _crew = value; RaisePropertyChanged("Crew");}
}
void IDropTarget.Drop(IDropInfo dropInfo)
{
Person sourceItem = dropInfo.Data as Person;
if (dropInfo.Data is Person)
{
Person person = new Person(sourceItem.FirstName,
sourceItem.LastName,
sourceItem.Profession);
Crew.Add(person);
}
}
The Model:
public Person(string FirstName, string LastName, string Profession)
{
_firstName = FirstName;
_lastName = LastName;
_profession = Profession;
}
private string _firstName;
public string FirstName
{
get { return this._firstName; }
set { this._firstName = value; RaisePropertyChanged("FirstName"); }
}
private string _lastName;
public string LastName
{
get { return _lastName; }
set { _lastName = value; RaisePropertyChanged("LastName"); }
}
private string _profession;
public string Profession
{
get { return _profession; }
set { _profession = value; RaisePropertyChanged("Profession"); }
}
I would recommend using a library that wraps the PropertyChanged event so it's easier to update your properties when you need to call them.
Once example is Caliburn for WPF. You can use NotifyOfPropertyChange(() => FirstName) from within your code to update the FirstName property however you need to (it doesn't have to just be used in the setter).
Here is a good article on how to use it.
Example:
using Caliburn.Micro;
namespace CaliburnMicroExample
{
public class ShellViewModel : PropertyChangedBase
{
private string _message;
public string Message
{
get { return _message; }
set
{
_message = value;
NotifyOfPropertyChange(() => Message);
}
}
public ShellViewModel()
{
Message = "Hello World";
}
}
}
I have an ObservableCollection<Person> in my viewmodel. This is bound as an ItemsSource to a DataGrid in the view. The class Person only has threeProperties:
public class Person : ViewModelBase
{
private Guid id;
public Guid Id
{
get { return this.id; }
set
{
this.id = value;
OnPropertyChanged("Id");
}
}
private string firstname;
public string Firstname
{
get { return this.firstname; }
set
{
this.firstname = value;
OnPropertyChanged("Firstname");
}
}
private string lastname;
public string Lastname
{
get { return this.lastname; }
set
{
this.lastname = value;
OnPropertyChanged("Lastname");
}
}
}
The class ViewModelBase implements INotifyPropertyChanged.
The items in the collection are updated perfect if I add or remove an entry in the dategrid. The item is then also removed in the collection.
My problem is that the content of an person-item is updated, but I don't know how I can react on this.
Do I have to add an event or something else to the person-class to get informed or is there another way to do this?
Implement INotifyPropertyChanged interface on your class Person so that any change in Person properties gets reflected back on UI.
Sample -
public class Person : INotifyPropertyChanged
{
private Guid id;
public Guid Id
{
get { return id; }
private set
{
if(id != value)
{
id = value;
NotifyPropertyChanged("Id");
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
Let's say I have following models:
class Worker
{
int Id;
string firstname;
string lastname;
}
class Department
{
string title;
string description;
List<Worker> workers;
}
I want to display, on UI, department's title,description and list of workers inside listbox (in listbox I want to display only firstname and lastname).
Do I need to create ONE viewmodel that will wrap this relation or I must I create a viewmodel for every model that I have?
You can create on ViewModel Which wrap both of them like:
namespace XXXX.ViewModel
{
public class MainViewModel : ViewModelBase
{
private int _id;
private string _total;
private string _description;
private ObservableCollection<Worker> _workers;
public int Id
{
get { return _id; }
set
{
if (value == _id) return;
_id = value;
RaisePropertyChanged("Id");
}
}
public string Total
{
get { return _total; }
set
{
if (value == _total) return;
_total = value;
RaisePropertyChanged("Total");
}
}
public string Description
{
get { return _description; }
set
{
if (value == _description) return;
_description = value;
RaisePropertyChanged("Description");
}
}
public ObservableCollection<Worker> Workers
{
get { return _workers; }
set
{
if (value == _workers) return;
_workers = value;
RaisePropertyChanged("Workers");
}
}
//****************** You Logic *************************
public MainViewModel()
{
Department department = new Department();
}
//****************** You Logic *************************
}
}
You wouldn't have ViewModel for every Model, in MVVM you should have a unique ViewModel for almost every view. You would then map the Model to the ViewModel.
For example:
public class DepartmentViewModel
{
public string title { get; set; }
public string description { get; set; }
public IEnumerable<Worker> workers { get; set; }
//Additional ViewModel properties here
//These may or may not be items that exist in your Model
/// <summary>
/// Mapped to the description but truncated to 10 characters and followed by an elispe (...)
/// </summary>
public string ShortDescription
{
get
{
return description.Substring(0,10) + "...";
}
}
}
I realize at first this looks a little redundant. However, there could be other less 1:1 type of views you might create from the model.
Also check out automapper.org, this is a great tool for mapping object to object.
You have 1 view model that contains both the workers and the department.
If the view only wants to show certain attributes of the workers, then the view should do that filtering. Try using an item template:
<ListBox x:Name="_workers" ItemsSource="{Binding Workers}">
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding firstname}" />
<TextBlock Text=" " />
<TextBlock Text="{Binding lastname}" />
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
The view model should contain:
private string _title;
public string Title {
get {return _title;}
set {_title = value; RaisePropertyChanged("Title");}
}
private string _description;
public string Description {
get {return _description;}
set {_description= value; RaisePropertyChanged("Description");}
}
public ObservableCollection Workers {get; private set;}
public Constructor()
{
Workers = new ObservableCollection();
}
//This method is called by the model once it has fetched data.
//This can be done as a callback or in an event handler
public CalledByTheModelAfterLoadingData(Department department)
{
Title = department.Title;
Description = department.Description;
foreach (var worker in department.Workers)
{
Workers.Add(worker);
}
}
I have two classes:
public class Person
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public ObservableCollection<AccountDetail> Details
{
get { return details; }
set { details = value; }
}
public ObservableCollection<AccountDetail> Phones
{
get
{
ObservableCollection<AccountDetail> phones;
phones = new ObservableCollection<AccountDetail>();
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
phones.Add(detail);
}
}
return phones;
}
set
{
ObservableCollection<AccountDetail> phones;
phones = value;
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
Details.Remove(detail);
}
}
foreach (AccountDetail detail in phones)
{
if (!string.IsNullOrEmpty(detail.Value))
{
Details.Add(detail);
}
}
}
}
private string firstName;
private string lastName;
private ObservableCollection<AccountDetail> details;
}
and
public class AccountDetail
{
public DetailType Type
{
get { return type; }
set { type = value; }
}
public string Value
{
get { return this.value; }
set { this.value = value; }
}
private DetailType type;
private string value;
}
In my XAML file I have a ListBox named PhonesListBox which is data bound to the phones list (a property of the Person object):
<Window.Resources>
<!-- Window controller -->
<contollers:PersonWindowController
x:Key="WindowController" />
</Window.Resources>
...
<ListBox
Name="PhonesListBox"
Margin="0,25,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Path=SelectedPerson.Phones,
Source={StaticResource ResourceKey=WindowController}}"
HorizontalContentAlignment="Stretch" />
...
In its code behind class, there's a handler for a button which adds a new item to that PhonesListBox:
private void AddPhoneButton_Click(object sender, RoutedEventArgs e)
{
ObservableCollection<AccountDetail> phones;
phones = (ObservableCollection<AccountDetail>)PhonesListBox.ItemsSource;
phones.Add(new AccountDetail(DetailType.Phone));
}
The problem is, the newly added list box item is not added in the person's details observable collection, i.e. the Phones property is not updated (set is never called). Why? Where am I making a mistake?
Thanks for all the help.
UPDATE: I changed the AddPhoneButton_Click method to:
private void AddPhoneButton_Click(object sender, RoutedEventArgs e)
{
PersonWindowController windowController;
ObservableCollection<AccountDetail> details;
windowController = (PersonWindowController)this.FindResource("WindowController");
details = windowController.SelectedPerson.Details;
details.Add(new AccountDetail(DetailType.Phone));
}
This updates the appropriate collection, which is details not Phones (as phones is just a view or a getter of a subset of detail items). Also, I realized I don't even need the Phones setter. The problem I am facing now is that my UI is not updated with the changes made to the details collection (and subsequently phones). I don't know how or where to call for the property changed as neither details nor phones are changing; their collection members are. Help. Please.
Why do you create a new ObservableCollection<AccountDetail> each time the Phones property is retrieved? Typically the Person class would have a member field of type ObservableCollection<AccountDetail> that would just be returned in the getter for the Phones property, instead of creating a new one each time. You could populate this collection when an instance of Person is constructed, for example.
I don't know if this would fix your problem or not, but it seems like it should help.
it sounds like you have an ObservableCollection<AccountDetail> with more than just phones in it, so it looks like you actually need a CollectionViewSource with a Filter added:
public class Person
{
public ObservableCollection<AccountDetail> Details { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
<Window.Resources>
<CollectionViewSource x:Key="phonesSource"
Source="{StaticResource ResourceKey=WindowController}"
Path="SelectedPerson.Details" />
</Window.Resources>
<Grid>
<ListBox
Name="PhonesListBox"
Margin="0,25,0,0"
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
ItemsSource="{Binding Source={StaticResource phonesSource}}"
HorizontalContentAlignment="Stretch" />
</Grid>
public MainWindow()
{
InitializeComponent();
CollectionViewSource source =
(CollectionViewSource)FindResource("phonesSource");
source.Filter += (o, e) =>
{
if (((AccountDetail) e.Item).Type == DetailType.Phone)
e.Accepted = true;
};
}
Changing you Person class to something like following should work :
public class Person
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public ObservableCollection<AccountDetail> Details
{
get { return details; }
set { details = value; }
}
public ObservableCollection<AccountDetail> Phones
{
get
{
if (phones == null)
{
phones = new ObservableCollection<AccountDetail>();
}
phones.Clear();
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
phones.Add(detail);
}
}
return phones;
}
set
{
phones.Clear();
foreach (var item in value)
{
phones.Add(item);
}
foreach (AccountDetail detail in Details)
{
if (detail.Type == DetailType.Phone)
{
Details.Remove(detail);
}
}
foreach (AccountDetail detail in phones)
{
if (!string.IsNullOrEmpty(detail.Value))
{
Details.Add(detail);
}
}
}
}
private string firstName;
private string lastName;
private ObservableCollection<AccountDetail> details;
public ObservableCollection<AccountDetail> phones;
}
The code is not tested and it may require a few changes from you to actually work.
Try something like:
public class Person : INotifyPropertyChanged
{
public string FirstName
{
get { return firstName; }
set { firstName = value; }
}
public string LastName
{
get { return lastName; }
set { lastName = value; }
}
public ObservableCollection<AccountDetail> Details
{
get { return details; }
set { details = value; }
}
public void AddDetail(AccountDetail detail) {
details.Add(detail);
OnPropertyChanged("Phones");
}
public IEnumerable<AccountDetail> Phones
{
get
{
return details.Where(d => d.Type == DetailType.Phone);
}
}
private string firstName;
private string lastName;
private ObservableCollection<AccountDetail> details;
/// <summary>
/// Called when a property changes.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected void OnPropertyChanged(string propertyName)
{
PropertyChangedEventHandler propertyChanged = this.PropertyChanged;
if (propertyChanged != null)
{
propertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
#region INotifyPropertyChanged Members
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
#endregion
}
You could call the AddDetail method from you button event handler:
private void AddPhoneButton_Click(object sender, RoutedEventArgs e)
{
PersonWindowController windowController;
ObservableCollection<AccountDetail> details;
windowController = (PersonWindowController)this.FindResource("WindowController");
windowController.SelectedPerson.AddDetail(new AccountDetail(DetailType.Phone));
}
Raising the OnPropertyChanged event for the Phones property will simply cause the WPF binding framework to requery the propery, and the Linq query to be re-evaluated, after you added a Phone detail to the list of Details.
I've a class like this:
public class PersonViewModel : ViewModelBase //Here is the INotifyPropertyChanged Stuff
{
public PersonViewModel(Person person)
{
PersonEntity = person;
}
public Person PersonEntity {
get { return PersonEntity.Name; }
private set { PersonEntity.Name = value; RaisePropertyChanged("PersonEntity");
}
public string Name {
get { return PersonEntity.Name; }
set { PersonEntity.Name = value; RaisePropertyChanged("Name");
}
public int Age{
get { return PersonEntity.Age; }
set { PersonEntity.Age= value; RaisePropertyChanged("Age");
}
public void ChangePerson(Person newPerson)
{
//Some Validation..
PersonEntity = newPerson;
}
My TextBoxes are bound to Name and Age of the ViewModel.
If I change the _person object in the ViewModel, do I have to call for each Property a RaisePropertyChanged again or is there a way to do this automaticly (in my concret example I have about 15 Properties..)?
Thanks for any help.
Cheers
Joseph
You can indicate all properties have changed by using null or string.Empty for the property name in PropertyChangedEventArgs. This is mentioned in the documentation for PropertyChanged.
One other solution I used to tackle the problem of: first setting the value and then calling the PropertyChangedEventArgs is by adding a Set function in my ViewModelBase which looks like this:
public class ViewModelBase : INotifyPropertyChanged
{
protected bool Set<T>(ref T backingField, T value, [CallerMemberName] string propertyname = null)
{
// Check if the value and backing field are actualy different
if (EqualityComparer<T>.Default.Equals(backingField, value))
{
return false;
}
// Setting the backing field and the RaisePropertyChanged
backingField = value;
RaisePropertyChanged(propertyname);
return true;
}
}
Instead of doing this:
public string Name {
get { return PersonEntity.Name; }
set { PersonEntity.Name = value; RaisePropertyChanged("Name");
}
You can now achieve the same by doing this:
public string Name {
get { return PersonEntity.Name; }
set { Set(ref PersonEntity.Name,value);
}