PropertyChanged event doesn't work with collection created by yield - c#

why does not working the following code?
I have this class:
public class ExportSetting : INotifyPropertyChanged
{
public Guid Guid { get; set; }
public string Name { get; set; }
private bool export;
public bool Export
{
get { return export; }
set
{
export = value;
NotifyPropertyChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged([CallerMemberName]string propertyName = null)
{
if (!string.IsNullOrEmpty(propertyName) && PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And I created collection by using command yield:
public IEnumerable<ExportSetting> SequencesToExport
{
get { return sequencesToExport; }
set { sequencesToExport = value; }
}
...
sequencesToExport = FillSequencesSetting(sequences);
private IEnumerable<ExportSetting> FillSequencesSetting(List<MTFSequence> sequences)
{
foreach (var item in sequences)
{
yield return new ExportSetting(item.Id, item.Name, true);
}
}
When the property Export (from class ExportSetting) is changed from UI should be raised the event PropertyChanged but this event is null.
(ListBox in XAML has ItemsSource binding to property SequenceToExport)
When I modified creation of collection like this everything is working properly:
private IEnumerable<ExportSetting> FillSequencesSetting(List<MTFSequence> sequences)
{
List<ExportSetting> tmp = new List<ExportSetting>();
foreach (var item in sequences)
{
tmp.Add(new ExportSetting(item.Id, item.Name, true));
}
return tmp;
}
Why the command yield causes the event PropertyChanged isn't registered and is null and when i create collection by using the generic List the event is working properly?
Thanks for your answers.

You need to materialize the enumerable. With your current code, the foreach loop is run every time the enumerable is enumerated, creating new ExportSetting objects every single time.
Fix:
sequencesToExport = FillSequencesSetting(sequences).ToArray();
Or:
sequencesToExport = FillSequencesSetting(sequences);
private IEnumerable<ExportSetting> FillSequencesSetting(List<MTFSequence> sequences)
{
return sequences.Select(x => new ExportSetting(x.Id, x.Name, true)).ToArray();
}

You miss notification for when SequencesToExport property is changed:
public bool Export
{
get { return export; }
set
{
export = value;
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(SequencesToExport));
}
}

Related

How do I detect changes in nested properties?

In C#, I have a suffiently complex Model. I already have a WPF Client to manipulate that model. I'm using MVVM. All objects in that model support INotifyPropertyChanged and all properties that are collections support INotifyCollectionChanged.
Take this as a simplied example:
using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
namespace CollectionTest1
{
public class PropertyChangedSupport : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void FirePropertyChange([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
public class Company : PropertyChangedSupport
{
private string name;
public String Name { get { return name; } set { name = value; FirePropertyChange(); } }
public ObservableCollection<Employee> Employees { get; } = new ObservableCollection<Employee>();
}
public class Employee : PropertyChangedSupport
{
private string name;
public String Name { get { return name; } set { name = value; FirePropertyChange(); } }
public ObservableCollection<PresentTimespan> PresentTimespans { get; } = new ObservableCollection<PresentTimespan>();
public Boolean IsPresentAt(DateTime t)
{
foreach (PresentTimespan pt in PresentTimespans)
{
if (pt.Start.CompareTo(t) <= 0 && pt.Finish.CompareTo(t) >= 0) return true;
}
return false;
}
}
public class PresentTimespan : PropertyChangedSupport
{
private string comment;
public String Comment { get { return comment; } set { comment = value; FirePropertyChange(); } }
private DateTime start;
public DateTime Start { get { return start; } set { start = value; FirePropertyChange(); } }
private DateTime finish;
public DateTime Finish { get { return finish; } set { finish = value; FirePropertyChange(); } }
}
public class CompanyStatusView : PropertyChangedSupport
{
private DateTime currentTime;
public DateTime CurrentTime { get { return currentTime; } set { currentTime = value; FirePropertyChange(); } }
private Company currentCompany;
public Company CurrentCompany { get { return currentCompany; } set { currentCompany = value; FirePropertyChange(); } }
public ObservableCollection<Employee> PresentEmployees { get; } = new ObservableCollection<Employee>();
public CompanyStatusView()
{
UpdatePresentEmployees();
}
private void UpdatePresentEmployees()
{
PresentEmployees.Clear();
foreach (Employee e in CurrentCompany.Employees) {
if (e.IsPresentAt(currentTime)) PresentEmployees.Add(e);
}
}
}
}
I'd like to have UpdatePresentEmployees called whenever there are changes in:
Collection Company.Employees.PresentTimespans
Property Company.Employees.PresentTimespans.Start
Property Company.Employees.PresentTimespans.Finish
Collection Company.Employees
Property CurrentTime
Property CurrentCompany
So it's basically any property or collection read by UpdatePresentEmployees.
My best solution so far included registering a lot of event handlers to all the objects mentioned above. That included to have a couple of Dictionary instances to track which added objects I have to subscribe to and especially which I have to unsubscribe from.
The most difficult and annoying part was to subscribe to all the PresentTimespan objects to listen for property changes and all the PresentTimespans collections of Employee to listen for collection changes.
My guess is that there has to be a better way to do this.
After all, in JFace (Java) there is a very interesting solution that uses ObservableTracker. So there you'd only provide the code for UpdatePresentEmployees and ObservableTracker tracks which objects have been read and automatically makes you listen for changes in any of these and also correctly unsubscribes from irrelevant objects. So there are better approaches to this problem in general. What is C# offering? Can it do better than my best solution I mentioned above? Can I avoid some of the boilerplate code? Can it be done with .net provided classes or do I need some additional classes/libraries?
Thanks for your kind help and advice in advance!
You could use BindingList instead of ObservableCollection and attach to the the ListChanged Event. But keep in mind that BindingList has some disadvantages like not being very fast. For further information this could be interesting: difference between ObservableCollection and BindingList
If you dont wanna use BindingList you have to wire your items with events.
As pointed out by Nikhil Agrawal, Rx or ReactiveUI is a good framework for my purpose. So I consider that to be a solution.

Wpf Observable collection and DataGrid not updating changes

I have an observable collection in the view model that implements Bindable Base as follows Please have a look at the MoveUp and MoveDown methods where they are bound to two buttons in the view. When ever up button is pressed I want the selected row in the datagrid to move one step up in the based on the sequence column in the database and for down one step down.. Both the methods works PERFECTLY. Problem is the changes get shown in the datagrid only when the entire view is refreshed. My requirement is when the button is clicked I want the view to be automatically refreshed. I apologize for such long code. Please Help!!!!. I have some cs code as well for the both up and down buttons specified below the viewmodel. Only pointers in the code that needs to be emphasized is the ObservableCollection JobEntities, MoveUp and MoveDown commands.
ViewModel.cs:
public class JobConfigurationViewModel : BindableBase
{
public JobConfigurationLogic JobConfigurationLogic =
new JobConfigurationLogic(new JobConfigurationResultsRepository());
public SrcDestConfigurationLogic SrcDestConfigurationLogic =
new SrcDestConfigurationLogic(new SrcDestCofigurationRepository());
private string _enterprise;
public string Enterprise
{
get { return _enterprise; }
set { SetProperty(ref _enterprise, value); }
}
private int currentJobID;
private int currentSequence;
private int previousJobID;
private int previousSequence;
private string _site;
public string Site
{
get { return _site; }
set { SetProperty(ref _site, value); }
}
private int _siteID;
public int SiteID
{
get { return _siteID; }
set { SetProperty(ref _siteID, value); }
}
private ObservableCollection<JobConfigurationResults> _jobEntities;
public ObservableCollection<JobConfigurationResults> JobEntities
{
get { return _jobEntities; }
set
{
SetProperty(ref _jobEntities, value);
this.OnPropertyChanged("JobEntities");
}
}
//Source System List for Job
private List<SourceSiteSystem> _lstJobSrcSystems;
public List<SourceSiteSystem> LstJobSrcSystems
{
get { return _lstJobSrcSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobSrcSystems, value);
}
}
//Deestination System List for Job
private List<DestinationSiteSystem> _lstJobDestSystems;
public List<DestinationSiteSystem> LstJobDestSystems
{
get { return _lstJobDestSystems; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _lstJobDestSystems, value);
}
}
//the Selected Source Site system ID
private int _selectedSrcSiteSystemId = 0;
public int SelectedSrcSiteSystemId
{
get { return _selectedSrcSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedSrcSiteSystemId, value);
}
}
//the Selected Source Site system from the dropdown
private SourceSiteSystem _selectedSrcSiteSystem;
public SourceSiteSystem SelectedSrcSiteSystem
{
get { return _selectedSrcSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedSrcSiteSystem, value);
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
}
}
}
//the Selected Destination Site system ID
private int _selectedDestSiteSystemId = 0;
public int SelectedDestSiteSystemId
{
get { return _selectedDestSiteSystemId; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
SetProperty(ref _selectedDestSiteSystemId, value);
}
}
//the Selected Destination Site system from the dropdown
private DestinationSiteSystem _selectedDestSiteSystem;
public DestinationSiteSystem SelectedDestSiteSystem
{
get { return _selectedDestSiteSystem; }
set
{
//Using bindable base setproperty method instead of older inotify prop changed method
if (value != null)
{
SetProperty(ref _selectedDestSiteSystem, value);
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
}
}
}
private JobConfigurationResults _jeJobConfigurationResults;
public JobConfigurationResults JEJobConfigurationResults
{
get { return _jeJobConfigurationResults; }
set { _jeJobConfigurationResults = value; }
}
private List<JobTaskConfiguration> _taskSelectionList = new List<JobTaskConfiguration>();
private CancellationTokenSource _source;
private RelayCommand<object> _commandSaveInstance;
private RelayCommand<object> _hyperlinkInstance;
private RelayCommand<object> _commandRunJob;
private RelayCommand<object> _upCommand;
private RelayCommand<object> _downCommand;
private IEventAggregator _aggregator;
/// <summary>
/// This is a Subscriber to the Event published by EnterpriseViewModel
/// </summary>
/// <param name="agg"></param>
public JobConfigurationViewModel(IEventAggregator agg)
{
_aggregator = agg;
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
//evt.Unsubscribe();
StartPopulate();
}
private async void StartPopulate()
{
await TaskPopulate();
}
//This is to ensure that the publisher has published the data that is needed for display in this workspace
private bool TaskProc()
{
Thread.Sleep(500);
PubSubEvent<Message> evt = _aggregator.GetEvent<PubSubEvent<Message>>();
evt.Subscribe(message => Enterprise = message.Enterprise.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => Site = message.Site.ToString(), ThreadOption.BackgroundThread);
evt.Subscribe(message => SiteID = message.SiteID, ThreadOption.BackgroundThread);
return DoPopulate();
}
private Task<bool> TaskPopulate()
{
_source = new CancellationTokenSource();
return Task.Factory.StartNew<bool>(TaskProc, _source.Token);
}
/// <summary>
/// This method handles the populating of the Source and Destination Dropdowns and the Job entity and Task Datagrid
/// This is mainly driven by the Site selected in the previous workspace
/// </summary>
/// <returns></returns>
private bool DoPopulate()
{
PopulateSourceDestinations(this.SiteID);
return true;
}
/// <summary>
/// this method displays all entities and tasks for the site.
/// This is done async so that the Publisher thread is not held up
/// </summary>
public void GetJobConfigurationResults()
{
if (SelectedSrcSiteSystem == null)
{
SelectedSrcSiteSystem = LstJobSrcSystems[0];
}
if (SelectedDestSiteSystem == null)
{
SelectedDestSiteSystem = LstJobDestSystems[0];
}
SelectedSrcSiteSystemId = SelectedSrcSiteSystem.SiteSystemId;
SelectedDestSiteSystemId = SelectedDestSiteSystem.SiteSystemId;
var jobConfigurationResults = new JobConfigurationResults
{
SourceId = SelectedSrcSiteSystemId,
DestinationId = SelectedDestSiteSystemId
};
JobEntities = new ObservableCollection<JobConfigurationResults>();
JobEntities = JobConfigurationLogic.GetResults(jobConfigurationResults.SourceId,
jobConfigurationResults.DestinationId);
_taskSelectionList = new List<JobTaskConfiguration>(JobEntities.Count * 3);
}
/// <summary>
/// //Adding a method to pupulate the Source and Destination dropdown lists.
/// This is done async so that the Publisher thread is not held up
/// </summary>
///
///
public async void PopulateSourceDestinations(int siteId)
{
this.LstJobSrcSystems = SrcDestConfigurationLogic.LoadSourceSiteSystems(siteId);
this.LstJobDestSystems = SrcDestConfigurationLogic.LoadDestinationSystems(siteId);
GetJobConfigurationResults();
}
public ICommand HyperlinkCommand
{
get
{
if (_hyperlinkInstance == null)
_hyperlinkInstance = new RelayCommand<object>(openDialog);
return _hyperlinkInstance;
}
}
private void openDialog(object obj)
{
JobConfigurationResults results = obj as JobConfigurationResults;
JEJobConfigurationResults = JobEntities.SingleOrDefault(x => x.JobEntityId == results.JobEntityId);
}
public ICommand CommandSave
{
get
{
if (_commandSaveInstance == null)
_commandSaveInstance = new RelayCommand<object>(saveJobConfigurationChanges);
return _commandSaveInstance;
}
}
public ICommand CommandRunJob
{
get { return _commandRunJob ?? (_commandRunJob = new RelayCommand<object>(RunJob)); }
}
/// <summary>
/// this saves all the changes in the selection made by the user
/// </summary>
/// <param name="ob"></param>
public void saveJobConfigurationChanges(object ob)
{
foreach (var job in JobEntities)
{
int jobEntityId = job.JobEntityId;
foreach (var task in job.TaskDetails)
{
int id = task.JobTask_ID;
bool isSelected = task.IsSelected;
_taskSelectionList.Add(task);
}
}
JobConfigurationLogic.UpdateTaskSelection(_taskSelectionList);
}
public ICommand UpCommand
{
get
{
if (_upCommand == null)
_upCommand = new RelayCommand<object>(MoveUp);
return _upCommand;
}
}
private void MoveUp(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder - 1;
try
{
JobConfigurationResults res = _jobEntities.SingleOrDefault(n => n.SequenceOrder == currentSequence);
previousJobID = res.JobEntityId;
previousSequence = res.SequenceOrder + 1;
// JobConfigurationLogic.UpdateSequence(currentJobID, previousSequence, previousJobID, currentSequence);
JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID);
OnPropertyChanged("JobEntities");
}
catch (NullReferenceException)
{
MessageBox.Show("Can't move the top record");
}
}
else
{
MessageBox.Show("Please Select a row that you want to sort");
}
}
public ICommand DownCommand
{
get
{
if (_downCommand == null)
_downCommand = new RelayCommand<object>(MoveDown);
return _downCommand;
}
}
private void MoveDown(object obj)
{
if (obj != null)
{
JobConfigurationResults results = obj as JobConfigurationResults;
currentJobID = results.JobEntityId;
currentSequence = results.SequenceOrder + 1;
try
{
JobConfigurationResults res = _jobEntities.SingleOrDefault(a => a.SequenceOrder == currentSequence);
previousJobID = res.JobEntityId;
previousSequence = res.SequenceOrder - 1;
JobConfigurationLogic.UpdateSequence(currentSequence, currentJobID, previousSequence, previousJobID);
OnPropertyChanged("JobEntities");
}
catch (NullReferenceException)
{
MessageBox.Show("You have reached the end");
}
}
else
{
MessageBox.Show("Please Select a row that you want to sort");
}
}
/// <summary>
/// Execute an etl job using the current job id
/// </summary>
private void RunJob(object obj)
{
JobEngine jobEngine = new JobEngine();
var jobId = JobEntities[0].JobId;
jobEngine.ProcessJob(jobId);
}
}
CS CODE:
private void btnup_Click(object sender, RoutedEventArgs e)
{
dgEntities.Items.Refresh();
//dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget();
}
private void btndown_Click(object sender, RoutedEventArgs e)
{
dgEntities.GetBindingExpression(DataGrid.ItemsSourceProperty).UpdateTarget();
}
An ObservableCollection will notify on change. There's no reason to do it manually, so you can remove all of the OnPropertyChanged("JobEntities");. This will get you to a cleaner solution.
MSDN
WPF provides the ObservableCollection class, which is a built-in
implementation of a data collection that implements the
INotifyCollectionChanged interface.
The next part is that an ObservableCollection will only notify on changes to the collection itself (add/remove). Any modifications to an element within the list will not have have the notify message sent. To do this, the simplest method is to implement the INotifyPropertyChanged to the elements used in the Observable Collection
I'm using PRISM 5 in the example, so it should be pretty equal to what you're doing. There's a couple of major design changes to you're code. First, I'm using a straight property for my Observable Collection. We know the framework will handle any add/remove operations to this collection. Then to notify when I change a property within the entity in an observable collection, I've used a notify property within the TestEntity class itself.
public class MainWindowViewModel : BindableBase
{
//Notice no OnPropertyChange, just a property
public ObservableCollection<TestEntity> TestEntities { get; set; }
public MainWindowViewModel()
: base()
{
this.TestEntities = new ObservableCollection<TestEntity> {
new TestEntity { Name = "Test", Count=0},
new TestEntity { Name = "Test1", Count=1},
new TestEntity { Name = "Test2", Count=2},
new TestEntity { Name = "Test3", Count=3}
};
this.UpCommand = new DelegateCommand(this.MoveUp);
}
public ICommand UpCommand { get; private set; }
private void MoveUp()
{
//Here is a dummy edit to show the modification of a element within the observable collection
var i = this.TestEntities.FirstOrDefault();
i.Count = 5;
}
}
Here's my entity, notice the BindableBase and the fact I notify on change. This allows the DataGrid or whatever you're using to be notified that the property changed.
public class TestEntity : BindableBase {
private String _name;
public String Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
//Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing)
private Int32 _count;
public Int32 Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
}
Now really all the TestEntity needs to have implemented the INotifyPropertyChanged for this to work, but I'm using the PRISM BindableBase as an example.
EDIT
I found a similar question on SO. I think yours is slightly different, but they overlap on the concepts. It may help to look over it.
Observable Collection Notify when property changed in MVVM
EDIT
If the datagrid is sorted the previous method will not update the grid. To handle this you need to refresh the grid's view, but are unable to directly access it using MVVM. So to handle this you'll want to use a CollectionViewSource.
public class MainWindowViewModel : BindableBase
{
//This will bind to the DataGrid instead of the TestEntities
public CollectionViewSource ViewSource { get; set; }
//Notice no OnPropertyChange, just a property
public ObservableCollection<TestEntity> TestEntities { get; set; }
public MainWindowViewModel()
: base()
{
this.TestEntities = new ObservableCollection<TestEntity> {
new TestEntity { Name = "Test", Count=0},
new TestEntity { Name = "Test1", Count=1},
new TestEntity { Name = "Test2", Count=2},
new TestEntity { Name = "Test3", Count=3}
};
this.UpCommand = new DelegateCommand(this.MoveUp);
//Initialize the view source and set the source to your observable collection
this.ViewSource = new CollectionViewSource();
ViewSource.Source = this.TestEntities;
}
public ICommand UpCommand { get; private set; }
private void MoveUp()
{
//Here is a dummy edit to show the modification of a element within the observable collection
var i = this.TestEntities.FirstOrDefault();
i.Count = 5;
//Now anytime you want the datagrid to refresh you can call this.
ViewSource.View.Refresh();
}
}
The TestEntity class does not change, but here's the class again:
public class TestEntity : BindableBase
{
private String _name;
public String Name
{
get { return _name; }
set { SetProperty(ref _name, value); }
}
//Notice I've implemented the OnPropertyNotify (Prism uses SetProperty, but it's the same thing)
private Int32 _count;
public Int32 Count
{
get { return _count; }
set { SetProperty(ref _count, value); }
}
}
For clarification, here's my XAML showing the binding to the new CollectionViewSource.
<DataGrid Grid.Row="1" ItemsSource="{Binding ViewSource.View}"></DataGrid>
For further reading you can refer to the MSDN article on this.
Here's another relevant question/answer - Re-sort WPF DataGrid after bounded Data has changed

WPF Binding Combobox to LINQ Populated Observable Collection

This is the first experience with WPF so please forgive me, I know this is pretty basic but I can't get it to work. I'm simply trying to bind a combobox to an LINQ to EF populated ObservableCollection. When I step through the code I see that the collection is populated, but the combo box doesn't display the contents of the collection.
Here is my ViewModel:
public class MainWindowViewModel : ViewModelBase
{
# region ObservableCollections
private ObservableCollection<Site> _sitescollection;
public ObservableCollection<Site> SiteCollection
{
get { return _sitescollection;}
set {
if (value == _sitescollection) return;
_sitescollection = value;
RaisePropertyChanged("SiteCollection");
}
}
# endregion
public MainWindowViewModel()
{
this.PopulateSites();
}
// Get a listing of sites from the database
public void PopulateSites()
{
using (var context = new Data_Access.SiteConfiguration_Entities())
{
var query = (from s in context.SITE_LOOKUP
select new Site(){Name = s.SITE_NAME, SeqId = s.SITE_SEQ_ID });
SiteCollection = new ObservableCollection<Site>(query.ToList());
}
}
}
My Site Class:
public class Site : INotifyPropertyChanged
{
#region Properties
string _name;
public string Name
{
get
{
return _name;
}
set
{
if (_name != value)
{
_name = value;
RaisePropertyChanged("Name");
}
}
}
private int _seqid;
public int SeqId
{
get {
return _seqid;
}
set {
if (_seqid != value)
{
_seqid = value;
RaisePropertyChanged("SeqId");
}
}
}
#endregion
#region Constructors
public Site() { }
public Site(string name, int seqid)
{
this.Name = name;
this.SeqId = seqid;
}
#endregion
void RaisePropertyChanged(string prop)
{
if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(prop)); }
}
public event PropertyChangedEventHandler PropertyChanged;
}
And my XAML Bindings:
<ComboBox Margin="10"
ItemsSource="{Binding Sites}"
DisplayMemberPath="Name"
SelectedValuePath="SeqId" />
What am I doing wrong? Any assistance would be greatly appreciated.
You bound to path "Sites" but your property name was "SiteCollection".
You bind to properties, so the names have to match. Also make sure your data context is set to your view model object.

Updating the View when items are added or removed from a bound ObservableCollection

I am currently writing a Windows 8.1 app that uses MVVM. I've stayed away from the model simply because I have never properly been able to update the View when data bound to the View changes. No number of websites or tutorials have been able to explain how to properly use INotifyPropertyChanged and I'm just lost at this point. I have the following class (including the method for adding an item of that type).
public class Organization
{
public Guid Id { get; set; }
public bool IsShared { get; set; }
public string Name { get; set; }
public ObservableCollection<Event> Events { get; set; }
public async static void Add(string Name)
{
Guid Id = Guid.NewGuid();
string FileName = Id.ToString() + ".txt";
var Folder = ApplicationData.Current.LocalFolder;
try
{
var Organizations = await Folder.CreateFolderAsync("Organizations", CreationCollisionOption.FailIfExists);
StorageFile File = await Organizations.CreateFileAsync(FileName, CreationCollisionOption.ReplaceExisting);
await FileIO.WriteTextAsync(File, JsonConvert.SerializeObject(new Organization { Id = Id, Name = Name, Events=new ObservableCollection<Event>() }));
}
catch
{
}
}
}
The following is my ViewModel:
public class OrganizationsViewModel : Base
{
private ObservableCollection<Organization> _List = new ObservableCollection<Organization>();
public ObservableCollection<Organization> List
{
get
{
Retrieve();
return _List;
}
set
{
}
}
public async void Retrieve()
{
var Folder = ApplicationData.Current.LocalFolder;
try
{
StorageFolder Organizations = await Folder.GetFolderAsync("Organizations");
var List = await Organizations.GetFilesAsync();
foreach (StorageFile i in List)
{
try
{
using (Stream s = await i.OpenStreamForReadAsync())
{
using (StreamReader sr = new StreamReader(s))
{
var item = JsonConvert.DeserializeObject<Organization>(await sr.ReadToEndAsync());
_List.Add(item);
}
}
}
catch
{
}
}
}
catch
{
}
}
}
The Base being:
public class Base : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void NotifyPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
// property changed
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
What code do I need in my ViewModel and/or my class definition that will allow the View to properly update when items are added or removed from my ObservableCollection and more importantly why does that given code work? Thanks in advance!
With databinding View will automatically updated as long as it notified that properties it bound to has been changed. So what you need is to raise property changed event whenever binding source property value changed. For example :
public class OrganizationsViewModel : Base
{
private ObservableCollection<Organization> _List = new ObservableCollection<Organization>();
public ObservableCollection<Organization> List
{
get
{
Retrieve();
return _List;
}
set
{
if(_List != value)
{
_List = value;
NotifyPropertyChanged("List");
}
}
}
...
...
}
However, ObservableCollection should automatically notify View whenever item added to or removed from collection without you raise the event manually. So I am not 100% sure where is the problem in your code. Just try to call NotifyPropertyChanged on setter of every property and see if the problem solved. At least you know how to use INotifyPropertyChanged now :)

Before set or get a property, a method should executed, which is not known at compile time?

Before set or get a property, a method should executed, which is not known at compile time?
Is it possible to add methodes as runtime?
public string MyName
{
get
{
//A Methode should called here
return m_ASD;
}
set
{
//A Methode should called here
m_ASD = value;
}
}
The simplest solution would be to provide hooks that can be set at runtime:
public Action OnMyNameSet { get; set; }
public Action OnMyNameGet { get; set; }
public string MyName
{
get
{
if (OnMyNameGet != null) { OnMyNameGet(); }
return m_ASD;
}
set
{
if (OnMyNameSet != null) { OnMyNameSet(); }
m_ASD = value;
}
}
Usage:
MyClass c = new MyClass();
c.OnMyNameSet = NameOfSomeMethod;
c.MyName = ...; // also executes NameOfSomeMethod();
Alternatively, you can create events that the consumer can attach to. This is how hooks are usually implemented in the .NET framework.
public event EventHandler OnMyNameSet;
public event EventHandler OnMyNameGet;
public string MyName
{
get
{
if (OnMyNameGet != null) { OnMyNameGet(this, EventArgs.Empty); }
return m_ASD;
}
set
{
if (OnMyNameSet != null) { OnMyNameSet(this, EventArgs.Empty); }
m_ASD = value;
}
}
The usage is similar; note, however, the use of += instead of =:
MyClass c = new MyClass();
c.OnMyNameSet += NameOfSomeMethod;
c.MyName = ...; // also executes NameOfSomeMethod(sender, eventArgs);

Categories