question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Conductor<T>.Collection.OneActive/AllActive - IChild.Parent not reset on clear

See original GitHub issue
void Main()
{
	var conductor = new Conductor<TestItem>.Collection.AllActive();
	
	var t1 = new TestItem();
	var t2 = new TestItem();
	var t3 = new TestItem();
	conductor.Items.Add(t1);
	conductor.Items.Add(t2);
	conductor.Items.Add(t3);
	if (t1.Parent == null || t2.Parent == null || t3.Parent == null)
		throw new Exception("t1.Parent + t2.Parent + t3.Parent should have been set by conductor");
	conductor.Items[0] = null;
	if (t1.Parent != null)
		throw new Exception("t1.Parent should have been set to <null> by conductor");
	conductor.Items.Remove(t2);
	if (t2.Parent != null) 
		throw new Exception("t2.Parent should have been set to <null> by conductor");
	conductor.Items.Clear();
	if (t3.Parent != null)
		throw new Exception("t3.Parent should have been set to <null> by conductor");
}

The code above fails on ‘t3.Parent != null’, it seems like the conductor implementation only resets the child’s parent property on a normal or index-based removal, but not on a clear operation.

The root cause seems to be located within the BindableCollection<T> implementation: A simple (not really pretty) workaround:

public class Workaround<T>
	where T : class
{
	private void ConductorItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
	{
		switch (e.Action)
		{
			case NotifyCollectionChangedAction.Add:
				this._mirroredItems.AddRange(e.NewItems.OfType<T>());
				break;
			case NotifyCollectionChangedAction.Remove:
				e.OldItems.OfType<T>().Apply(i => this._mirroredItems.Remove(item: i));
				break;
			case NotifyCollectionChangedAction.Replace:
				this._mirroredItems.AddRange(e.NewItems.OfType<T>());
				e.OldItems.OfType<T>().Apply(i => this._mirroredItems.Remove(item: i));
				break;
			case NotifyCollectionChangedAction.Reset:
				if (this._conductorItems.Count == 0) // clear called
				{
					this._mirroredItems.OfType<IChild>().Apply(i => i.Parent = null);
					this._mirroredItems.Clear();
					return;
				}

				// re-synchronize mirrored items
				this._mirroredItems.Where(i => !this._conductorItems.Contains(item: i)).OfType<IChild>().Apply(i => i.Parent = null);
				this._mirroredItems.Clear();
				this._mirroredItems.AddRange(collection: this._conductorItems);
				break;
		}
	}
	public Workaround(IObservableCollection<T> conductorItems)
	{
		if (conductorItems == null) throw new ArgumentNullException(nameof(conductorItems));
		this._conductorItems = conductorItems;
		this._mirroredItems = new List<T>(collection: conductorItems);
		conductorItems.CollectionChanged += this.ConductorItems_CollectionChanged;
	}
}

=>

new Workaround<TestItem>(conductor.Items);

lets the initial example pass without errors.

Issue Analytics

  • State:open
  • Created 6 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
nigel-sampsoncommented, Sep 12, 2017

So this looks like it comes out of an issue around ObservableCollection<T> not populating the NotifyCollectionChangedEventArgs.OldItems when the collection is cleared. Because of this we don’t hace access to the items in the collection that were removed.

The solution above maintains a second separate list in order to determine which items were removed. I’m not sure yet if this is the right approach.

At the moment I’d recommend using something like Items.Apply(i => Items.Remove(i)); to get what you need.

0reactions
nigel-sampsoncommented, Sep 13, 2017

In the specific case of conductors, i don’t think raising multiple remove events instead of a single clear would cause problems, at least i’m not aware of a conductor conducting thousands of items.

I’ve had issues with this as some more advanced controls trigger different animations when items are removed and a mass remove like that would be performance issue.

Leaning further towards that custom event at the moment.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Caliburn.Micro IChild.Parent is null unless activated by ...
When the Parent VM is displayed using Conductor.ActivateItem() , Caliburn.Micro does the usual labours of searching through the view model ...
Read more >
Parent slicer not resetting child slicer
Solved: I have two drop down slicers that I would like to act in a parent child interaction. I have a single table...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found