ISupportIncrementalLoading doesn't stop after navigating away - c#

I suddenly found a strange behavior of collection implementing ISupportIncrementalLoading.
Let's say we have a main page with ISupportIncrementalLoading collection bound to ListView. And we have another page where we can navigate to.
When navigating to main page, the ISupportIncrementalLoading starts loading items until ListView thinks it's enough. I navigate to new page BEFORE ListView loaded all items it needs.
My expected behavior: ListView stops loading new items as the page isn't visible now.
Real behavior: ListView continues to load items endlessly, even after going away from the page. And it won't stop until gets HasMore == false.
Can anyone help with this? This is absolutely wrong behavior.
PS
If I while navigation, set in ViewModel the collection to null and then restore it when coming back -- it seams to help, but that is too much to do, I think.
Here's the code of my basic ISupportIncrementalLoading collection:
public abstract class BaseIncrementalSupportCollection<T> :IList<T>,IList,INotifyCollectionChanged, ISupportIncrementalLoading, INotifyPropertyChanged
{
protected readonly List<T> storage;
private bool isLoading;
public bool IsLoading
{
get
{
return isLoading;
}
set
{
if (isLoading != value)
{
isLoading = value;
RaisePropertyChanged();
}
}
}
public bool failed;
public bool IsFailed
{
get { return failed; }
set
{
if (failed != value)
{
failed = value;
RaisePropertyChanged();
}
}
}
public bool IsEmpty
{
get { return !HasMoreItems && Count == 0; }
}
protected BaseIncrementalSupportCollection()
{
storage = new List<T>();
}
public virtual IAsyncOperation<LoadMoreItemsResult> LoadMoreItemsAsync(uint count)
{
return Task.Run(()=>LoadMoreItems(count)).AsAsyncOperation();
}
public abstract bool HasMoreItems { get; }
private async Task<LoadMoreItemsResult> LoadMoreItems(uint count)
{
IsLoading = true;
IsFailed = false;
try
{
var items = await LoadMoreItemsOverride(count);
if (items == null)
return new LoadMoreItemsResult() {Count = 0};
if (items.Count > 0)
{
var prevEmptyState = IsEmpty;
foreach (var item in items)
{
var currItem = item;
await DispatchHelper.RunOnUiIfNecessary(async () =>
{
storage.Add(currItem);
RaiseCollectionChanged(
new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, currItem,
storage.Count - 1));
});
}
if(prevEmptyState!=IsEmpty)
RaisePropertyChanged("IsEmpty");
}
return new LoadMoreItemsResult() {Count = (uint) items.Count};
}
catch (Exception e)
{
var aggregate = e as AggregateException;
if (aggregate != null)
e = aggregate.Flatten().InnerException;
IsFailed = true;
var handler = OnError;
if (handler != null)
DispatchHelper.RunOnUiIfNecessary(
() => handler(this, new IncrementallCollectionLoadErrorEventArgs(e)));
return new LoadMoreItemsResult() {Count = 0};
}
finally
{
IsLoading = false;
}
}
protected virtual void RaiseCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (CollectionChanged != null)
CollectionChanged(this, e);
}
protected abstract Task<IList<T>> LoadMoreItemsOverride(uint count);
[NotifyPropertyChangedInvocator]
protected virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
{
var handler = PropertyChanged;
if (handler != null)
DispatchHelper.RunOnUiIfNecessary(()=>handler(this, new PropertyChangedEventArgs(propertyName)));
}
public event PropertyChangedEventHandler PropertyChanged;
public event EventHandler<IncrementallCollectionLoadErrorEventArgs> OnError;
public event NotifyCollectionChangedEventHandler CollectionChanged;
}

I've just found another way, adding additional bool field isStopped with methods Start, ForceStop setting it to false/true. This value is used when getting HasMoreItems like
bool HasMoreItems{get{return !isStopped && DetermineIfHasMore()};}
And simply by calling those to methods I can stop or continue loading the same collection generator.
Another way is provided here https://social.msdn.microsoft.com/Forums/ru-RU/be17357d-faac-4f49-acf4-e916fcdace9d/w81isupportincrementalloading-doesnt-stop-after-navigating-away?forum=winappswithcsharp

Related

On Publishing error - is not allowed here because it does not extend class 'System.Web.UI.UserControl'

I have got following UserControl that work just fine but when I publish the project I am facing this error.
.. is not allowed here because it does not extend class 'System.Web.UI.UserControl'
How to fix it?
ASCX
<%# Control Language="C#" AutoEventWireup="true" CodeBehind="DataPagerGridView.ascx.cs" Inherits="VerInformes.DataPagerGridView" %>
C#
public partial class DataPagerGridView : GridView, IPageableItemContainer
{
private static readonly object EventTotalRowCountAvailable = new object();
public int MaximumRows
{
get { return this.PageSize; }
}
public int StartRowIndex
{
get { return this.PageSize * this.PageIndex; }
}
public event EventHandler<PageEventArgs> TotalRowCountAvailable
{
add { base.Events.AddHandler(DataPagerGridView.EventTotalRowCountAvailable, value); }
remove { base.Events.RemoveHandler(DataPagerGridView.EventTotalRowCountAvailable, value); }
}
public void SetPageProperties(int startRowIndex, int maximumRows, bool databind)
{
int newPageIndex = (startRowIndex / maximumRows);
this.PageSize = maximumRows;
if (this.PageIndex != newPageIndex)
{
bool isCanceled = false;
if (databind)
{
// create the event arguments and raise the event
GridViewPageEventArgs args = new GridViewPageEventArgs(newPageIndex);
this.OnPageIndexChanging(args);
isCanceled = args.Cancel;
newPageIndex = args.NewPageIndex;
}
// if the event wasn't cancelled change the paging values
if (!isCanceled)
{
this.PageIndex = newPageIndex;
if (databind)
this.OnPageIndexChanged(EventArgs.Empty);
}
if (databind)
this.RequiresDataBinding = true;
}
}
protected virtual void OnTotalRowCountAvailable(PageEventArgs e)
{
EventHandler<PageEventArgs> handler = (EventHandler<PageEventArgs>)base.Events[DataPagerGridView.EventTotalRowCountAvailable];
if (handler != null)
{
handler(this, e);
}
}
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int rows = base.CreateChildControls(dataSource, dataBinding);
// if the paging feature is enabled, determine the total number of rows in the datasource
if (this.AllowPaging)
{
// if we are databinding, use the number of rows that were created,
// otherwise cast the datasource to an Collection and use that as the count
int totalRowCount = dataBinding ? rows : ((ICollection)dataSource).Count;
// raise the row count available event
IPageableItemContainer pageableItemContainer = this as IPageableItemContainer;
this.OnTotalRowCountAvailable(new PageEventArgs
(pageableItemContainer.StartRowIndex, pageableItemContainer.MaximumRows, totalRowCount));
// make sure the top and bottom pager rows are not visible
if (this.TopPagerRow != null)
this.TopPagerRow.Visible = false;
if (this.BottomPagerRow != null)
this.BottomPagerRow.Visible = false;
}
return rows;
}
protected void Page_Load(object sender, EventArgs e)
{
}
}
I just reorganized the code so it is correct now.
public class MyDataPagerGridView : GridView, IPageableItemContainer
{
private static readonly object EventTotalRowCountAvailable = new object();
public int MaximumRows
{
get { return this.PageSize; }
}
public int StartRowIndex
{
get { return this.PageSize * this.PageIndex; }
}
public event EventHandler<PageEventArgs> TotalRowCountAvailable
{
add { base.Events.AddHandler(MyDataPagerGridView.EventTotalRowCountAvailable, value); }
remove { base.Events.RemoveHandler(MyDataPagerGridView.EventTotalRowCountAvailable, value); }
}
public void SetPageProperties(int startRowIndex, int maximumRows, bool databind)
{
int newPageIndex = (startRowIndex / maximumRows);
this.PageSize = maximumRows;
if (this.PageIndex != newPageIndex)
{
bool isCanceled = false;
if (databind)
{
// create the event arguments and raise the event
GridViewPageEventArgs args = new GridViewPageEventArgs(newPageIndex);
this.OnPageIndexChanging(args);
isCanceled = args.Cancel;
newPageIndex = args.NewPageIndex;
}
// if the event wasn't cancelled change the paging values
if (!isCanceled)
{
this.PageIndex = newPageIndex;
if (databind)
this.OnPageIndexChanged(EventArgs.Empty);
}
if (databind)
this.RequiresDataBinding = true;
}
}
protected virtual void OnTotalRowCountAvailable(PageEventArgs e)
{
EventHandler<PageEventArgs> handler = (EventHandler<PageEventArgs>)base.Events[MyDataPagerGridView.EventTotalRowCountAvailable];
if (handler != null)
{
handler(this, e);
}
}
protected override int CreateChildControls(IEnumerable dataSource, bool dataBinding)
{
int rows = base.CreateChildControls(dataSource, dataBinding);
// if the paging feature is enabled, determine the total number of rows in the datasource
if (this.AllowPaging)
{
// if we are databinding, use the number of rows that were created,
// otherwise cast the datasource to an Collection and use that as the count
int totalRowCount = dataBinding ? rows : ((ICollection)dataSource).Count;
// raise the row count available event
IPageableItemContainer pageableItemContainer = this as IPageableItemContainer;
this.OnTotalRowCountAvailable(new PageEventArgs
(pageableItemContainer.StartRowIndex, pageableItemContainer.MaximumRows, totalRowCount));
// make sure the top and bottom pager rows are not visible
if (this.TopPagerRow != null)
this.TopPagerRow.Visible = false;
if (this.BottomPagerRow != null)
this.BottomPagerRow.Visible = false;
}
return rows;
}
}
public partial class DataPagerGridView : UserControl
{
public MyDataPagerGridView DataPagerGrid = new MyDataPagerGridView();
protected void Page_Load(object sender, EventArgs e)
{
}
}

Xamarin element fails to load when using SetBinding

This works (the graph is properly loaded):
var oxyPlotView = new OxyPlotView{ Model = GetPlotModelSynch() };
This doesn't (the graph remains empty):
var oxyPlotView = new OxyPlotView();
// Here PlotModel will be loaded asynchronously from the BindingContext:
oxyPlotView.SetBinding(OxyPlotView.ModelProperty, new Binding(nameof(GraphViewModel.PlotModel)));
I have made proper isolated tests to ensure that INotifyPropertyChanged is working properly with my ViewModel. So the problem seems to be that OxyPlotView is built properly only if it has al the info from its inception (?). Is that even possible?
Here is the full ViewModel. INotifyPropertyChanged works because Title is behaving as intended (Title is binded to a Label in the same view).
class GraphViewModel : INotifyPropertyChanged
{
IGraphSeriesGroupRepository _graphSeriesGroupRepository;
private GraphSeriesGroup _graphSeriesGroup;
private ulong _sensorId;
public event PropertyChangedEventHandler PropertyChanged;
private PlotModel _plotModel;
public PlotModel PlotModel
{
get { return _plotModel; }
set
{
if (_plotModel != value)
{
_plotModel = value;
OnPropertyChanged(nameof(PlotModel));
}
}
}
private string _title;
public string Title
{
get { return _title; }
set
{
if (_title != value)
{
_title = value;
OnPropertyChanged(nameof(Title));
}
}
}
private bool _isLoading;
public bool IsLoading
{
get { return _isLoading; }
set
{
_isLoading = value;
OnPropertyChanged(nameof(IsLoading));
}
}
public GraphViewModel(IGraphSeriesGroupRepository graphSeriesGroupRepository, ulong sensorId)
{
_graphSeriesGroupRepository = graphSeriesGroupRepository;
_sensorId = sensorId;
Load();
}
public PlotModel GetPlotModelSynch()
{
_graphSeriesGroup = _graphSeriesGroupRepository.GetGraphSeriesGroup(_sensorId);
return GetPlotModel(_graphSeriesGroup);
}
private async void Load()
{
IsLoading = true;
await Task.Delay(5000);
_graphSeriesGroup = await _graphSeriesGroupRepository.GetGraphSeriesGroupAsync(_sensorId);
ApplyChanges();
IsLoading = false;
}
private void ApplyChanges()
{
// ---
Title = _graphSeriesGroup.Title;
PlotModel = GetPlotModel(_graphSeriesGroup);
}
private PlotModel GetPlotModel(GraphSeriesGroup graphSeriesGroup)
{
...
}
public void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Update: The only way I've found to make it work is:
private void chatter_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if(e.PropertyName == nameof(GraphViewModel.PlotModel))
{
_oxyPlotView = new OxyPlotView
{
Model = _graphViewModel.PlotModel
};
_stackLayout.Children.Add(_oxyPlotView);
}
}
...even updating an _oxyPlotView (which was already added to the StackLayout) and calling _oxyPlotView.InvalidateDisplay() didn't work.

How to update a ListBox if an element was changed c#

Hi,
I'm struggling a bit using the ListBox.DataSource and the INotifyPropertyChanged Interface. I checked several posts about this issue already but I cannot figure out, how to update the view of a ListBox if an element of the bound BindingList is changed.
I basically want to change the color of an IndexItem after the content has been parsed.
Here the relevant calls in my form:
btn_indexAddItem.Click += new EventHandler(btn_indexAddItem_Click);
lst_index.DataSource = Indexer.Items;
lst_index.DisplayMember = "Url";
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
private void btn_indexAddItem_Click(object sender, EventArgs e)
{
Indexer.AddSingleURL(txt_indexAddItem.Text);
}
private void lst_index_DrawItem(object sender, DrawItemEventArgs e)
{
IndexItem item = lst_index.Items[e.Index] as IndexItem;
if (item != null)
{
e.DrawBackground();
SolidBrush brush = new SolidBrush((item.hasContent) ? SystemColors.WindowText : SystemColors.ControlDark);
e.Graphics.DrawString(item.Url, lst_index.Font, brush, 0, e.Index * lst_index.ItemHeight);
e.DrawFocusRectangle();
}
}
Indexer.cs:
class Indexer
{
public BindingList<IndexItem> Items { get; }
private object SyncItems = new object();
public Indexer()
{
Items = new BindingList<IndexItem>();
}
public void AddSingleURL(string url)
{
IndexItem item = new IndexItem(url);
if (!Items.Contains(item))
{
lock (SyncItems)
{
Items.Add(item);
}
new Thread(new ThreadStart(() =>
{
// time consuming parsing
Thread.Sleep(5000);
string content = item.Url;
lock (SyncItems)
{
Items[Items.IndexOf(item)].Content = content;
}
}
)).Start();
}
}
}
IndexItem.cs
class IndexItem : IEquatable<IndexItem>, INotifyPropertyChanged
{
public int Key { get; }
public string Url { get; }
public bool hasContent { get { return (_content != null); } }
private string _content;
public string Content {
get
{
return (hasContent) ? _content : "empty";
}
set
{
_content = value;
ContentChanged();
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void ContentChanged()
{
if (this.PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs("Content"));
}
}
public IndexItem(string url)
{
this.Key = url.GetHashCode();
this.Url = url;
}
public override bool Equals(object obj)
{
return Equals(obj as IndexItem);
}
public override int GetHashCode()
{
return Key;
}
public bool Equals(IndexItem other)
{
if (other == null) return false;
return (this.Key.Equals(other.Key)) ||
((hasContent || other.hasContent) && (this._content.Equals(other._content)));
}
public override string ToString()
{
return Url;
}
}
Any ideas what went wrong and how to fix it? I'll appreciate any hint...
It seems to me that the control should redraw when it raises the ListChanged event for that item. This will force it to do so:
lst_index.DrawItem += new DrawItemEventHandler(lst_index_DrawItem);
Indexer.Items.ListChanged += Items_ListChanged;
private void Items_ListChanged(object sender, ListChangedEventArgs e)
{
lst_index.Invalidate(); // Force the control to redraw when any elements change
}
So why doesn't it do that already? Well, it seems that the listbox only calls DrawItem if both DisplayMember changed, and if the INotifyPropertyChanged event was raised from the UI thread. So this also works:
lock (SyncItems)
{
// Hacky way to do an Invoke
Application.OpenForms[0].Invoke((Action)(() =>
{
Items[Items.IndexOf(item)].Url += " "; // Force listbox to call DrawItem by changing the DisplayMember
Items[Items.IndexOf(item)].Content = content;
}));
}
Note that calling PropertyChanged on the Url is not sufficient. The value must actually change. This tells me that the listbox is caching those values. :-(
(Tested with VS2015 REL)

XAML custom collection binding async collection update

I have a custom class inheriting from ObservableCollection and INotifyPropertyChanged (i.e. the custom class also has properties) that serves as a Collection<T> where T also inherits from INotifyPropertyChanged:
public class CustomCollection<T> : ObservableCollection<T>, INotifyPropertyChanged where T: INotifyPropertyChanged {
private string _name;
public string Name {
get {
return _name;
}
set {
if (_name != value) {
_name = value;
NotifyPropertyChanged("Name");
}
}
}
private int _total;
public int Total {
get {
return _total;
}
set {
if (_total != value) {
_total = value;
NotifyPropertyChanged("Total");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
And T item class:
public class DataItem : INotifyPropertyChanged {
private string _fname;
public string Fname {
get {
return _fname;
}
set {
if (value != _fname) {
_fname = value;
NotifyPropertyChanged("Fname");
}
}
}
private int_value;
public int Value {
get {
return _value;
}
set {
if (value != _value) {
_value = value;
NotifyPropertyChanged("Value");
}
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(String propertyName) {
PropertyChangedEventHandler handler = PropertyChanged;
if (null != handler) {
handler(this, new PropertyChangedEventArgs(propertyName));
}
}
}
And the ViewModel:
public class ViewModel : ViewModelBase {
private readonly IService _dataService;
private bool _isLoading;
public bool IsLoading {
get {
return _isLoading;
}
private set {
_isLoading = value;
RaisePropertyChanged("IsLoading");
}
}
private CustomCollection<DataItem> _items;
public CustomCollection<DataItem> Items
{
get
{
return _items;
}
set
{
_items= value;
RaisePropertyChanged("Items");
}
}
public ViewModel(IService dataService) {
_dataService = dataService;
}
public void Refresh() {
if (!this.IsLoading) {
this.IsLoading = true;
_dataService.RefreshData(
this, (error) => {
if (error != null) {
return;
}
if (!IsInDesignMode)
this.IsLoading = false;
}
);
}
}
public void GetData() {
if (Games == null) {
Games = new CustomCollection<DataItem>();
} else {
Games.Clear();
}
if (!this.IsLoading) {
this.IsLoading = true;
_dataService.GetData(
this, (error) => {
if (error != null) {
return;
}
if (!IsInDesignMode)
this.IsLoading = false;
}
);
}
}
And I have bound the CustomCollection<T> to a control in my View (xaml). Everything works fine initially, upon navigating to the page, the ViewModel calls for a DataService to retrieve the data and populate the CustomCollection<T>. However, when refreshing the data, the View is not updated until all the data has been iterated over and refreshed/updated!
Here is the code for the refresh/updated (keep in mind, I'm retrieving the data via a web service, and for the purposes of testing have just manually updated the Value property in DataItem at each passover of the CustomCollection<T>):
public async RefreshData(ViewModel model, Action<Exception> callback) {
if (model.Items == null) return;
// ... retrieve data from web service here (omitted) ...
foreach (DataItem item in retrievedItems) { // loop for each item in retrieved items
DataItem newItem = new DataItem() { Fname = item.Fname, Value = item.Value };
if (model.Items.contains(newItem)) { // override for .Equals in CustomCollection<T> allows for comparison by just Fname property
model.Items[model.Items.IndexOf(newItem)].Value += 10; // manual update
} else {
model.Items.Add(newItem);
}
System.Threading.Thread.Sleep(1000); // 1 second pause to "see" each item updated sequentially...
}
callback(null);
}
So in summary, how can I make it so updating Value of my DataItem will instantly reflect in the View, given my current setup of CustomCollection<DateItem>? Something to do with async perhaps? I mean, when Sleep(1000) gets called, the UI does not hang, maybe this has something to do with it?
Any ideas on how to fix this? As you might have guessed, this issue is also present when first retrieving the data (but is barely noticeable as data is retrieved/processed during the navigation to the View).
Note: I'm using the MVVMLight Toolkit.
Thanks.

(is there an) Easy way to check if property in (complex) pocomodel has changed?

I was wondering if there is an 'easy'/good way to check if a property has changed. Like in the hierarchy below when Child.Name has changed (isDirty) I would like to know.
GrantParent
- Parent
-- Child
In my current situation I need to navigate through the model to see if anything has changed.
ps: I'm using IChangeTracking.
Been thinking about caching a hash of the serialized object. (too slow?)
Or creating changedevent which call's the parent until it reaches the grantparent. (chatty?)
public class Parent: BaseEntity
{
private Child _child;
public Child Child
{
get { return _child; }
set { _child = value; OnPropertyChanged("Child"); }
}
}
public class Child : BaseEntity
{
private int _id;
public int Id {
get { return _id; }
set { _id = value; OnPropertyChanged("Id"); }
}
}
[DataContract]
[Serializable]
public abstract class BaseEntity : INotifyPropertyChanged
{
protected BaseEntity()
{
PropertyChanged += PropertyChangedEventHandler;
}
private void PropertyChangedEventHandler(object sender, PropertyChangedEventArgs e)
{
if (e != null && !String.Equals(e.PropertyName, "IsChanged", StringComparison.Ordinal))
{
this.IsChanged = true;
}
}
protected void OnPropertyChanged<T>(Expression<Func<T>> property)
{
MemberExpression me = property.Body as MemberExpression;
if (me == null || me.Expression != property.Parameters[0]
|| me.Member.MemberType != MemberTypes.Property)
{
throw new InvalidOperationException(
"Now tell me about the property");
}
var handler = PropertyChanged;
if (handler != null) handler(this,
new PropertyChangedEventArgs(me.Member.Name));
}
[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool IsChanged
{
get
{
lock (_notifyingObjectIsChangedSyncRoot)
{
return _notifyingObjectIsChanged;
}
}
protected set
{
lock (_notifyingObjectIsChangedSyncRoot)
{
if (!Boolean.Equals(_notifyingObjectIsChanged, value))
{
_notifyingObjectIsChanged = value;
if (IsDirtyChanged != null)
IsDirtyChanged();
this.OnPropertyChanged("IsChanged");
}
}
}
}
private bool _notifyingObjectIsChanged;
private readonly object _notifyingObjectIsChangedSyncRoot = new Object();
public void AcceptChanges()
{
this.IsChanged = false;
}
}
In the end I used a compare on the XML model from the XML serializer I already used. I did't 'need' instant change detection once a second (or so) would be enough. Now I check the XML model with the one I had since the last save.
You'll need to have each of the properties keep track of it themselves, and either store some information indicating what properties have changed, or possibly firing off an event when an item is changed.
essentially each property will have logic similar to this:
public class MyClass : INotifyPropertyChanged
{
private int _value;
public int Value
{
get
{
return _value;
}
set
{
_value = value;
PropertyChanged(this, new PropertyChangedEventArgs("Value"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This will allow you to add an event handler to the PropertyChanged event so that code will be fired when a property is changed.
I recently worked on a project where we had all of the nodes/leaves implement a node.Modified property and used INotifyPropertyChanged to raise state change of node.Modified. Then all of the parents subscribed to their children's property change and if node.Modified was ever set true, then they'd set their own node.Modified to true.
Like you say, it's a little chatty, but hasn't come close to becoming a performance bottleneck for us since we're not seeing thousands of changes every second and our hierarchy is only 3 levels deep.
Here's a quick sample:
class Node : INotifyPropertyChanged
{
public Node()
{
Children = new List<Node>();
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string name)
{
var temp = PropertyChanged;
if (temp != null)
temp(this, new PropertyChangedEventArgs(name));
}
public IList<Node> Children { get; private set; }
public void AddChild(Node node)
{
node.PropertyChanged += ChildPropertyChanged;
Children.Add(node);
}
void ChildPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (args.PropertyName == "Modified")
Modified |= ((Node)sender).Modified;
}
bool _modified = false;
public bool Modified
{
get { return _modified; }
set
{
if (_modified != value)
{
_modified = value;
OnPropertyChanged("Modified");
}
}
}
EDIT: There is another way using a sort of message bus. It may not be perfect, but it is another approach to the problem, so I will share that as well. I quickly hacked up a trivial Msg bus...
static class Bus<T>
{
public static Dictionary<object, Action<object, T>> Subscriptions = new Dictionary<object, Action<object, T>>();
public static void Raise(object sender, T message)
{
foreach (Action<object, T> action in Subscriptions.Values)
{
action(sender, message);
}
}
public static void Subscribe(object subscriber, Action<object, T> action)
{
Subscriptions[subscriber] = action;
}
public static void Unsubscribe(object subscriber)
{
if (Subscriptions.ContainsKey(subscriber))
Subscriptions.Remove(subscriber);
}
}
public class WasModified { }
And the modified Node
class Node
{
public Node()
{
Children = new List<Node>();
}
public IList<Node> Children { get; private set; }
bool _modified = false;
public bool Modified
{
get { return _modified; }
set
{
if (_modified != value)
{
_modified = value;
if (_modified == true)
Bus<WasModified>.Raise(this, new WasModified());
}
}
}
}
Finally, it's use.
static void Main(string[] args)
{
Node parent = new Node();
Bus<WasModified>.Subscribe(parent, (s,a)=> parent.Modified = true);
Node child = new Node();
Node gchild = new Node();
parent.Children.Add(child);
parent.Children.Add(gchild);
gchild.Modified = true;
Console.WriteLine(parent.Modified);
Console.ReadLine();
}
The message bus doesn't need to bubble up to parent objects and you don't need to recurse into them each time you want to see if Modified was changed, so perhaps it's what you're looking for.

Categories