Skip to content

Commit

Permalink
Preserve the correct binding context for menu items (#7358)
Browse files Browse the repository at this point in the history
* Rework parenting for MenuBarItems

* - sync menu bar items when collection changes

* - clear parent

* - fix spelling

Co-authored-by: Shane Neuville <shneuvil@microsoft.com>
  • Loading branch information
mattleibow and PureWeen authored Oct 4, 2022
1 parent e75b91d commit 71739c2
Show file tree
Hide file tree
Showing 5 changed files with 166 additions and 52 deletions.
47 changes: 18 additions & 29 deletions src/Controls/src/Core/MenuBar.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,8 @@ public bool IsEnabled
set { SetValue(IsEnabledProperty, value); }
}

ReadOnlyCastingList<Element, IMenuBarItem> _logicalChildren;
readonly ObservableCollection<IMenuBarItem> _menus = new ObservableCollection<IMenuBarItem>();

internal override IReadOnlyList<Element> LogicalChildrenInternal =>
_logicalChildren ??= new ReadOnlyCastingList<Element, IMenuBarItem>(_menus);

public IMenuBarItem this[int index]
{
get { return _menus[index]; }
Expand All @@ -41,19 +37,24 @@ public void Add(IMenuBarItem item)
var index = _menus.Count;
_menus.Add(item);
NotifyHandler(nameof(IMenuBarHandler.Add), index, item);
}

// Take care of the Element internal bookkeeping
if (item is Element element)
internal void SyncMenuBarItemsFromPages(IList<MenuBarItem> menuBarItems)
{
for (int i = 0; i < menuBarItems.Count; i++)
{
OnChildAdded(element);
var menuBarItem = menuBarItems[i];
if (this.Count > i && this[i] == menuBarItem)
continue;

if (this.Contains(menuBarItem))
Remove(menuBarItem);

Insert(i, menuBarItem);
}
}

internal void ReplaceWith(IList<MenuBarItem> menuBarItems)
{
Clear();
foreach (var menuItem in menuBarItems)
Add(menuItem);
while (this.Count > menuBarItems.Count)
RemoveAt(this.Count - 1);
}

public void Clear()
Expand Down Expand Up @@ -86,12 +87,6 @@ public void Insert(int index, IMenuBarItem item)
{
_menus.Insert(index, item);
NotifyHandler(nameof(IMenuBarHandler.Insert), index, item);

// Take care of the Element internal bookkeeping
if (item is Element element)
{
OnChildAdded(element);
}
}

public bool Remove(IMenuBarItem item)
Expand All @@ -100,11 +95,8 @@ public bool Remove(IMenuBarItem item)
var result = _menus.Remove(item);
NotifyHandler(nameof(IMenuBarHandler.Remove), index, item);

// Take care of the Element internal bookkeeping
if (item is Element element)
{
OnChildRemoved(element, index);
}
if (item is Element e)
e.Parent = null;

return result;
}
Expand All @@ -119,11 +111,8 @@ public void RemoveAt(int index)
_menus.RemoveAt(index);
NotifyHandler(nameof(IMenuBarHandler.Remove), index, item);

// Take care of the Element internal bookkeeping
if (item is Element element)
{
OnChildRemoved(element, index);
}
if (item is Element e)
e.Parent = null;
}

IEnumerator IEnumerable.GetEnumerator()
Expand Down
12 changes: 3 additions & 9 deletions src/Controls/src/Core/MenuBarTracker.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,10 @@ public MenuBarTracker(Element parent, string handlerProperty)

void OnMenuBarItemCollectionChanged(object sender, EventArgs e)
{
_menuBar.SyncMenuBarItemsFromPages(ToolbarItems);

if (_handlerProperty != null)
{
// For now we just reset the entire menu if users modify the menubar
// collection
//if (_parent is IMenuBarElement mbe &&
// mbe.MenuBar?.Handler != null)
//{
// mbe.MenuBar.Handler.DisconnectHandler();
//}

_parent?.Handler?.UpdateValue(_handlerProperty);
}
}
Expand All @@ -47,7 +41,7 @@ public MenuBar MenuBar
if (menuBarItems.Count == 0)
return null;

_menuBar.ReplaceWith(ToolbarItems);
_menuBar.SyncMenuBarItemsFromPages(ToolbarItems);

return _menuBar;
}
Expand Down
15 changes: 11 additions & 4 deletions src/Controls/src/Core/Page.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,17 @@ void OnPageBusyChanged()

void OnToolbarItemsCollectionChanged(object sender, NotifyCollectionChangedEventArgs args)
{
if (args.Action != NotifyCollectionChangedAction.Add)
return;
foreach (IElementDefinition item in args.NewItems)
item.Parent = this;
if (args.NewItems != null)
{
foreach (IElementDefinition item in args.NewItems)
item.Parent = this;
}

if (args.OldItems != null)
{
foreach (IElementDefinition item in args.OldItems)
item.Parent = null;
}
}

bool ShouldLayoutChildren()
Expand Down
71 changes: 71 additions & 0 deletions src/Controls/tests/Core.UnitTests/Menu/MenuBarTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,77 @@ MenuBarHandler CreateMenuBarHandler(Action<string, IMenuBarHandler, IMenuBar, Me
return handler;
}

[Fact]
public void UsingWindowDoesNotReAssignParents()
{
MenuFlyoutItem flyout;
MenuBarItem menuItem;

var page = new ContentPage
{
MenuBarItems =
{
(menuItem = new MenuBarItem
{
(flyout = new MenuFlyoutItem { })
})
}
};

Assert.Equal(menuItem, flyout.Parent);
Assert.Equal(page, menuItem.Parent);

var window = new Window(page);

Assert.Equal(menuItem, flyout.Parent);
Assert.Equal(page, menuItem.Parent);
Assert.Equal(window, page.Parent);

var menubar = (window as IMenuBarElement).MenuBar;
Assert.NotNull(menubar);

Assert.Equal(menuItem, flyout.Parent);
Assert.Equal(page, menuItem.Parent);
Assert.Equal(window, page.Parent);
}

[Fact]
public void UsingWindowDoesNotReAssignBindingContext()
{
var bindingContext = new
{
Name = "Matthew"
};

MenuFlyoutItem flyout;
MenuBarItem menuItem;

var page = new ContentPage
{
BindingContext = bindingContext,
MenuBarItems =
{
(menuItem = new MenuBarItem
{
(flyout = new MenuFlyoutItem { })
})
}
};

flyout.SetBinding(MenuFlyoutItem.TextProperty, new Binding(nameof(bindingContext.Name)));

Assert.Equal(bindingContext.Name, flyout.Text);

var window = new Window(page);

Assert.Equal(bindingContext.Name, flyout.Text);

var menubar = (window as IMenuBarElement).MenuBar;
Assert.NotNull(menubar);

Assert.Equal(bindingContext.Name, flyout.Text);
}

class NonThrowingMenuBarHandler : MenuBarHandler
{
public NonThrowingMenuBarHandler(IPropertyMapper mapper, CommandMapper commandMapper)
Expand Down
73 changes: 63 additions & 10 deletions src/Controls/tests/Core.UnitTests/Menu/MenuTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,40 @@ public void UsingIndexUpdatesParent()

menuBar.Add(child0);

Assert.Same(menuBar, child0.Parent);
// Menu Bar Items only get parented to pages currently
if (typeof(TChildType) == typeof(MenuBarItem))
{
var cp = new ContentPage();
cp.MenuBarItems.Add(child0 as MenuBarItem);
Assert.Same(cp, child0.Parent);
}
else
{
Assert.Same(menuBar, child0.Parent);
}

Assert.Null(child1.Parent);

menuBar[0] = child1;
// Menu Bar Items only get parented to pages currently
if (typeof(TChildType) == typeof(MenuBarItem))
{
(child0.Parent as ContentPage)!.MenuBarItems[0] = child1 as MenuBarItem;
}
else
{
menuBar[0] = child1;
}

Assert.Null(child0.Parent);
Assert.Same(menuBar, child1.Parent);

if (typeof(TChildType) == typeof(MenuBarItem))
{
Assert.True(child1.Parent is ContentPage);
}
else
{
Assert.Same(menuBar, child1.Parent);
}
}

[Fact]
Expand All @@ -47,13 +74,39 @@ public void ClearUpdatesParent()
var child0 = new TChildType();
var child1 = new TChildType();

menuBar.Add(child0);
menuBar.Add(child1);

Assert.Same(menuBar, child0.Parent);
Assert.Same(menuBar, child1.Parent);

menuBar.Clear();
// Menu Bar Items only get parented to pages currently
if (typeof(TChildType) == typeof(MenuBarItem))
{
// this sets up the MenuBarTracker
var cp = new ContentPage();
_ = new Window()
{
Page = cp
};

cp.MenuBarItems.Add(child0 as MenuBarItem);
cp.MenuBarItems.Add(child1 as MenuBarItem);

Assert.Same(cp, child0.Parent);
Assert.Same(cp, child1.Parent);
}
else
{
menuBar.Add(child0);
menuBar.Add(child1);

Assert.Same(menuBar, child0.Parent);
Assert.Same(menuBar, child1.Parent);
}

if (typeof(TChildType) == typeof(MenuBarItem))
{
(child0.Parent as ContentPage)!.MenuBarItems.Clear();
}
else
{
menuBar.Clear();
}

Assert.Null(child0.Parent);
Assert.Null(child1.Parent);
Expand Down

0 comments on commit 71739c2

Please sign in to comment.