Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reparent TableRow and TableCell when added from one table to another #2600

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 4 additions & 3 deletions src/Eto/Forms/Container.cs
100644 → 100755
Original file line number Diff line number Diff line change
Expand Up @@ -295,9 +295,10 @@ protected void RemoveParent(Control child)
child.TriggerUnLoad(EventArgs.Empty);
}
child.VisualParent = null;
if (ReferenceEquals(child.InternalLogicalParent, this))
child.InternalLogicalParent = null;
}

if (ReferenceEquals(child.InternalLogicalParent, this))
child.InternalLogicalParent = null;
}

/// <summary>
Expand Down Expand Up @@ -397,7 +398,7 @@ protected void SetParent(Control child, Action assign = null, Control previousCh
// Detach so parent can remove from controls collection if necessary.
// prevents UnLoad from being called more than once when containers think a control is still a child
// no-op if there is no parent (handled in detach)
child.Detach();
child.Detach(); //.VisualParent?.Remove(child);

if (ReferenceEquals(child.InternalLogicalParent, null))
SetLogicalParent(child);
Expand Down
79 changes: 55 additions & 24 deletions src/Eto/Forms/Layout/TableCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ namespace Eto.Forms;
[sc.TypeConverter(typeof(TableCellConverter))]
public class TableCell
{
Control control;
TableLayout layout;
Control _control;
TableCellCollection _cells;

TableLayout ParentTable => _cells?._layout;

/// <summary>
/// Gets or sets a value indicating whether this <see cref="Eto.Forms.TableCell"/> will scale its width
Expand All @@ -31,23 +33,24 @@ public class TableCell
/// <value>The control.</value>
public Control Control
{
get { return control; }
get { return _control; }
set
{
if (control != value)
if (_control != value)
{
value?.Detach();
control?.Detach();
layout?.InternalRemoveLogicalParent(control);
control = value;
layout?.InternalSetLogicalParent(control);
_control?.Detach();
var layout = ParentTable;
layout?.InternalRemoveLogicalParent(_control);
_control = value;
layout?.InternalSetLogicalParent(_control);
}
}
}

internal void SetControl(Control control)
{
this.control = control;
_control = control;
}

/// <summary>
Expand Down Expand Up @@ -119,20 +122,29 @@ public TableCell(Control control, bool scaleWidth = false)
return new TableCell(new ImageView { Image = image });
}

internal void SetLayout(TableLayout layout)
internal void SetLayout(TableCellCollection cells, bool shouldRemove, TableLayout oldLayout = null)
{
if (!ReferenceEquals(this.layout, layout))
if (!ReferenceEquals(_cells, cells))
{
// remove from old cells collection
if (shouldRemove)
_cells?.RemoveItemWithoutLayoutUpdate(this);

ParentTable?.InternalRemoveLogicalParent(_control);
_cells = cells;
ParentTable?.InternalSetLogicalParent(_control);
}
else if (!ReferenceEquals(oldLayout, ParentTable))
{
this.layout?.InternalRemoveLogicalParent(control);
this.layout = layout;
layout?.InternalSetLogicalParent(control);
oldLayout?.InternalRemoveLogicalParent(_control);
ParentTable?.InternalSetLogicalParent(_control);
}
}
}

class TableCellCollection : Collection<TableCell>, IList
{
TableLayout layout;
internal TableLayout _layout;

public TableCellCollection()
{
Expand All @@ -142,26 +154,45 @@ public TableCellCollection(IEnumerable<TableCell> list)
: base(list.Select(r => r ?? new TableCell { ScaleWidth = true }).ToList())
{
}

internal void SetLayout(TableLayout layout)
internal void SetLayout(TableRow row, TableLayout layout, bool shouldRemove)
{
this.layout = layout;
if (ReferenceEquals(layout, _layout))
return;

var oldLayout = _layout;
if (shouldRemove && _layout?.Rows is TableRowCollection oldLayoutRows)
{
// switching layouts, remove from old layout without triggering a new SetLayout
// This really shouldn't be done and should throw an exception
oldLayoutRows.RemoveItemWithoutLayoutUpdate(row);
}
_layout = layout;

foreach (var cell in this)
cell.SetLayout(layout);
cell.SetLayout(this, true, oldLayout);
}

internal void RemoveItemWithoutLayoutUpdate(TableCell cell)
{
var index = IndexOf(cell);
if (index >= 0)
base.RemoveItem(index);
}


protected override void RemoveItem(int index)
{
var item = this[index];
item?.SetLayout(null);
item?.SetLayout(null, false);
base.RemoveItem(index);
}

protected override void ClearItems()
{
foreach (var item in this)
{
item?.SetLayout(null);
item?.SetLayout(null, false);
}
base.ClearItems();
}
Expand All @@ -170,17 +201,17 @@ protected override void InsertItem(int index, TableCell item)
{
if (item == null)
item = new TableCell { ScaleWidth = true };
item.SetLayout(layout);
item.SetLayout(this, true);
base.InsertItem(index, item);
}

protected override void SetItem(int index, TableCell item)
{
var old = this[index];
old?.SetLayout(null);
old?.SetLayout(null, false);
if (item == null)
item = new TableCell { ScaleWidth = true };
item.SetLayout(layout);
item.SetLayout(this, true);
base.SetItem(index, item);
}

Expand Down
29 changes: 18 additions & 11 deletions src/Eto/Forms/Layout/TableRow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,57 +155,64 @@ public TableRow(IEnumerable<TableCell> cells)
return new TableCell(new TableLayout(row));
}

internal void SetLayout(TableLayout layout)
internal void SetLayout(TableLayout layout, bool shouldRemove)
{
((TableCellCollection)Cells).SetLayout(layout);
((TableCellCollection)Cells).SetLayout(this, layout, shouldRemove);
}
}

class TableRowCollection : Collection<TableRow>, IList
{
TableLayout layout;
internal TableLayout _layout;
public TableRowCollection(TableLayout layout)
{
this.layout = layout;
_layout = layout;
}

public TableRowCollection(TableLayout layout, IEnumerable<TableRow> list)
: base(list.Select(r => r ?? new TableRow { ScaleHeight = true }).ToList())
{
this.layout = layout;
_layout = layout;
foreach (var item in this)
item?.SetLayout(layout);
item?.SetLayout(layout, true);
}

internal void RemoveItemWithoutLayoutUpdate(TableRow row)
{
var index = IndexOf(row);
if (index >= 0)
base.RemoveItem(index);
}

protected override void RemoveItem(int index)
{
var item = this[index];
item?.SetLayout(null);
item?.SetLayout(null, false);
base.RemoveItem(index);
}

protected override void ClearItems()
{
foreach (var item in this)
item?.SetLayout(null);
item?.SetLayout(null, false);
base.ClearItems();
}

protected override void InsertItem(int index, TableRow item)
{
if (item == null)
item = new TableRow { ScaleHeight = true };
item?.SetLayout(layout);
item?.SetLayout(_layout, true);
base.InsertItem(index, item);
}

protected override void SetItem(int index, TableRow item)
{
var old = this[index];
old?.SetLayout(null);
old?.SetLayout(null, false);
if (item == null)
item = new TableRow { ScaleHeight = true };
item?.SetLayout(layout);
item?.SetLayout(_layout, true);
base.SetItem(index, item);
}

Expand Down
82 changes: 82 additions & 0 deletions test/Eto.Test/UnitTests/Forms/Layout/TableLayoutTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,88 @@ public void LabelInAutoSizedColumnShouldHaveCorrectWidth()
Assert.Greater(label.Height, 0, "Label didn't get correct height!");
});
}

[Test]
public void DestroyingChildShouldRemoveFromParent()
{
TableLayout layout = null;
Label child = null;
Shown(form =>
{
child = new Label { Text = "I should not be shown" };
layout = new TableLayout
{
Rows = {
new TableRow(child)
}
};
child.Dispose();
form.Content = layout;
}, () =>
{
Assert.IsTrue(child.IsDisposed);
Assert.IsNull(child.Parent);
CollectionAssert.DoesNotContain(layout.Children, child);
});
}

[Test, ManualTest]
public void CopyingRowsShouldReparentChildren()
{
ManualForm("Label above should show", form =>
{
var child = new Label { Text = "I should be shown!" };

var layout1 = new TableLayout { ID = "layout1" };
layout1.Rows.Add(child);
Assert.AreEqual(child.Parent, layout1, "#1.1 Child's parent should now be the 2nd table");
Assert.AreEqual(child.LogicalParent, layout1, "#1.2 Child's logical parent should now be the 2nd table");

// copy rows to a new layout
var layout2 = new TableLayout { ID = "layout2" };
foreach (var row in layout1.Rows.ToList())
layout2.Rows.Add(row);

Assert.AreEqual(0, layout1.Rows.Count, "#2.1 All rows should now be in the 2nd table");
Assert.AreEqual(child.Parent, layout2, "#2.2 Child's parent should now be the 2nd table");
Assert.AreEqual(child.LogicalParent, layout2, "#2.3 Child's logical parent should now be the 2nd table");
return layout2;
});
}

[Test, ManualTest]
public void CopyingCellsShouldReparentChildren()
{
ManualForm("Label above should show", form =>
{
var child = new Label { Text = "I should be shown!" };

var layout1 = new TableLayout { ID = "layout1" };
var cell1 = new TableCell(child);
var row1 = new TableRow(child);
layout1.Rows.Add(row1);
Assert.AreEqual(child.Parent, layout1, "#1.1 Child's parent should now be the 2nd table");
Assert.AreEqual(child.LogicalParent, layout1, "#1.2 Child's logical parent should now be the 2nd table");

// copy rows to a new layout
var layout2 = new TableLayout { ID = "layout2" };
var row2 = new TableRow();
foreach (var row in layout1.Rows.ToList())
{
foreach (var cell in row.Cells.ToList())
row2.Cells.Add(cell);
}
layout2.Rows.Add(row2);

Assert.AreEqual(1, layout1.Rows.Count, "#2.1 Should still have a single row");
Assert.AreEqual(0, layout1.Rows[0].Cells.Count, "#2.2 Cells should be removed from old table");
Assert.AreEqual(1, layout2.Rows.Count, "#2.3 2nd table should have a single row");
Assert.AreEqual(1, layout2.Rows[0].Cells.Count, "#2.4 All cells should now be in the 2nd table");
Assert.AreEqual(child.Parent, layout2, "#2.5 Child's parent should now be the 2nd table");
Assert.AreEqual(child.LogicalParent, layout2, "#2.6 Child's logical parent should now be the 2nd table");
return layout2;
});
}
}
}