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

Fix for MenuFlyoutItem stops working after navigating away from and back to page #25170

Merged
merged 23 commits into from
Feb 9, 2025

Conversation

BagavathiPerumal
Copy link
Contributor

@BagavathiPerumal BagavathiPerumal commented Oct 10, 2024

Root cause

The issue occurs because the BindingContext is set to null after navigating to another page. As a result, when returning to the page, the MenuFlyoutItem loses its binding, causing the Command to stop functioning properly.

Description of Issue Fix

The fix involves the parent of the MenuBarItem only set to null when the parent of MenuBarItem is MenuBar instead of page. This prevents the unintended loss of the BindingContext, which can disrupt bindings and commands when navigating back to a page.

Tested the behavior in the following platforms.

  • Windows
  • Mac
  • iOS
  • Android

The MenuBar control is not supported on iOS and Android, so this fix was not tested on these platforms.

Issues Fixed

Fixes #21675

Output

Before Issue Fix After Issue Fix
BeforeFix-MenuBarItem-Windows.mp4
AfterFix-MenuBarItem-Windows.mp4

@dotnet-policy-service dotnet-policy-service bot added the community ✨ Community Contribution label Oct 10, 2024
Copy link
Contributor

Hey there @BagavathiPerumal! Thank you so much for your PR! Someone from the team will get assigned to your PR shortly and we'll get it reviewed.

@BagavathiPerumal BagavathiPerumal marked this pull request as ready for review October 11, 2024 09:30
@BagavathiPerumal BagavathiPerumal requested a review from a team as a code owner October 11, 2024 09:30
@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@BagavathiPerumal
Copy link
Contributor Author

/rebase

@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@BagavathiPerumal
Copy link
Contributor Author

/rebase

Copy link
Member

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't look like this needs a UITest

Can you move this to a unit test?

@BagavathiPerumal
Copy link
Contributor Author

BagavathiPerumal commented Oct 16, 2024

It doesn't look like this needs a UITest

Can you move this to a unit test?

@PureWeen, we couldn't recreate the same UI test scenario in the unit test case because the command of the MenuFlyoutItem is always present after page navigation.

[Fact]
public async void UpdateMenuBarItemBindingContext()
{
	bool fired = false;
	var cmd = new Command(() => fired = true);
	var bindingContext = new
	{
		MenuItemCommand = cmd
	};
 
	var menuFlyoutItem = new MenuFlyoutItem();
	var menuItem = new MenuBarItem { menuFlyoutItem };
	var menubarItems = new List<MenuBarItem> { menuItem };
	var mainPage = new ContentPage
	{
		MenuBarItems = menubarItems,
	};
	mainPage.BindingContext = bindingContext;
	menuFlyoutItem.SetBinding(MenuFlyoutItem.CommandProperty, new Binding("MenuItemCommand"));
 
	var page = new ContentPage
	{
		BindingContext = bindingContext
	};
 
	NavigationPage nav = new NavigationPage(mainPage);
	await mainPage.Navigation.PushAsync(page);
	await page.Navigation.PopAsync();
 
	menuFlyoutItem.Command.Execute(null);
 
	Assert.True(fired);
}  

@PureWeen PureWeen added this to the .NET 9 SR4 milestone Jan 5, 2025
Copy link

Azure Pipelines successfully started running 3 pipeline(s).

Copy link
Member

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we validate this one using a unit test vs a UI Test?

@BagavathiPerumal
Copy link
Contributor Author

Can we validate this one using a unit test vs a UI Test?

@PureWeen, As discussed here in this comment, we were facing difficulties in converting the test from UI to Unit testcase.

@PureWeen
Copy link
Member

Can we validate this one using a unit test vs a UI Test?

@PureWeen, As discussed here in this comment, we were facing difficulties in converting the test from UI to Unit testcase.

If you wire up through a TestWindow does it work?

For example, if you check any of tests under NavigationModelTests

{
if (menuBarItem.BindingContext is null)
{
SetInheritedBindingContext(menuBarItem, BindingContext);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that this is where the actual bug is @BagavathiPerumal

The fix here might be to check if the Parent is MenuBar and if it's not then don't set it to null.

The Parent of MenuBarItem is the Page that it's on so the Page needs to control when the parent is set to null

@BagavathiPerumal
Copy link
Contributor Author

Can we validate this one using a unit test vs a UI Test?

@PureWeen, As discussed here in this comment, we were facing difficulties in converting the test from UI to Unit testcase.

If you wire up through a TestWindow does it work?

For example, if you check any of tests under NavigationModelTests

@PureWeen, As suggested, when wiring up through a TestWindow in the UnitTest case, we couldn't recreate the UI test scenario because the MenuFlyoutItem command remains active after page navigation. Could you please share your suggestions.

[Fact]
public async void UpdateMenuBarItemBindingContext()
{
	bool fired = false;
	var cmd = new Command(() => fired = true);
	var bindingContext = new
	{
		MenuItemCommand = cmd
	};
 
	MenuFlyoutItem flyout;
	MenuBarItem menuItem;
	var mainPage = new ContentPage
	{
		MenuBarItems =
		{
			(menuItem = new MenuBarItem
			{
				(flyout = new MenuFlyoutItem { })
			})
		}
	};
	mainPage.BindingContext = bindingContext;
	flyout.SetBinding(MenuFlyoutItem.CommandProperty, new Binding("MenuItemCommand"));
 
	var page = new ContentPage
	{
		BindingContext = bindingContext
	};
 
	TestWindow testWindow = new TestWindow(mainPage);
	NavigationPage nav = new NavigationPage(mainPage);
	await mainPage.Navigation.PushAsync(page);
	await page.Navigation.PopAsync();
 
	flyout.Command.Execute(null);
 
	Assert.True(fired);
}

@PureWeen
Copy link
Member

Can we validate this one using a unit test vs a UI Test?

@PureWeen, As discussed here in this comment, we were facing difficulties in converting the test from UI to Unit testcase.

If you wire up through a TestWindow does it work?
For example, if you check any of tests under NavigationModelTests

@PureWeen, As suggested, when wiring up through a TestWindow in the UnitTest case, we couldn't recreate the UI test scenario because the MenuFlyoutItem command remains active after page navigation. Could you please share your suggestions.

[Fact]
public async void UpdateMenuBarItemBindingContext()
{
	bool fired = false;
	var cmd = new Command(() => fired = true);
	var bindingContext = new
	{
		MenuItemCommand = cmd
	};
 
	MenuFlyoutItem flyout;
	MenuBarItem menuItem;
	var mainPage = new ContentPage
	{
		MenuBarItems =
		{
			(menuItem = new MenuBarItem
			{
				(flyout = new MenuFlyoutItem { })
			})
		}
	};
	mainPage.BindingContext = bindingContext;
	flyout.SetBinding(MenuFlyoutItem.CommandProperty, new Binding("MenuItemCommand"));
 
	var page = new ContentPage
	{
		BindingContext = bindingContext
	};
 
	TestWindow testWindow = new TestWindow(mainPage);
	NavigationPage nav = new NavigationPage(mainPage);
	await mainPage.Navigation.PushAsync(page);
	await page.Navigation.PopAsync();
 
	flyout.Command.Execute(null);
 
	Assert.True(fired);
}

@BagavathiPerumal in your test you aren't assigning the right thing to the window

You need to change those lines to this

NavigationPage nav = new NavigationPage(mainPage);
			TestWindow testWindow = new TestWindow(nav);
			await mainPage.Navigation.PushAsync(page);
			await page.Navigation.PopAsync();
		

@PureWeen
Copy link
Member

/rebase

@BagavathiPerumal
Copy link
Contributor Author

Can we validate this one using a unit test vs a UI Test?

@PureWeen, As discussed here in this comment, we were facing difficulties in converting the test from UI to Unit testcase.

If you wire up through a TestWindow does it work?
For example, if you check any of tests under NavigationModelTests

@PureWeen, As suggested, when wiring up through a TestWindow in the UnitTest case, we couldn't recreate the UI test scenario because the MenuFlyoutItem command remains active after page navigation. Could you please share your suggestions.

[Fact]
public async void UpdateMenuBarItemBindingContext()
{
	bool fired = false;
	var cmd = new Command(() => fired = true);
	var bindingContext = new
	{
		MenuItemCommand = cmd
	};
 
	MenuFlyoutItem flyout;
	MenuBarItem menuItem;
	var mainPage = new ContentPage
	{
		MenuBarItems =
		{
			(menuItem = new MenuBarItem
			{
				(flyout = new MenuFlyoutItem { })
			})
		}
	};
	mainPage.BindingContext = bindingContext;
	flyout.SetBinding(MenuFlyoutItem.CommandProperty, new Binding("MenuItemCommand"));
 
	var page = new ContentPage
	{
		BindingContext = bindingContext
	};
 
	TestWindow testWindow = new TestWindow(mainPage);
	NavigationPage nav = new NavigationPage(mainPage);
	await mainPage.Navigation.PushAsync(page);
	await page.Navigation.PopAsync();
 
	flyout.Command.Execute(null);
 
	Assert.True(fired);
}

@BagavathiPerumal in your test you aren't assigning the right thing to the window

You need to change those lines to this

NavigationPage nav = new NavigationPage(mainPage);
			TestWindow testWindow = new TestWindow(nav);
			await mainPage.Navigation.PushAsync(page);
			await page.Navigation.PopAsync();
		

@PureWeen, Thank you for the suggestion. I have added the unit test case accordingly. Please review and let me know if you have any further concerns.

Copy link
Member

@PureWeen PureWeen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My comment here still applies to the PR in its current state

#25170 (comment)

@BagavathiPerumal
Copy link
Contributor Author

My comment here still applies to the PR in its current state

#25170 (comment)

Hi @PureWeen, Could you kindly check this response?

#25170 (comment)

We have attempted this approach and encountered a related test failure.

@jfversluis jfversluis requested a review from PureWeen January 31, 2025 14:40
@PureWeen
Copy link
Member

PureWeen commented Feb 3, 2025

/rebase

@PureWeen
Copy link
Member

PureWeen commented Feb 3, 2025

@BagavathiPerumal
Apologies but I'm having trouble following the thread on this one

AFAICT this code is wrong

if (item is Element e)
e.Parent = null;

and should be changed to this

			if (item is Element e && e.Parent == this)
				e.Parent = null;

What I'm thinking here is

  1. remove the code you've added
  2. make the change I link above

And then that should be the proper fix here.

If I'm still missing something here can you summarize it all in a comment so it's a bit easier to follow where I'm not understanding?

@PureWeen PureWeen modified the milestones: .NET 9 SR4, .NET 9 SR5 Feb 4, 2025
@BagavathiPerumal
Copy link
Contributor Author

@BagavathiPerumal Apologies but I'm having trouble following the thread on this one

AFAICT this code is wrong

if (item is Element e)
e.Parent = null;

and should be changed to this

			if (item is Element e && e.Parent == this)
				e.Parent = null;

What I'm thinking here is

  1. remove the code you've added
  2. make the change I link above

And then that should be the proper fix here.

If I'm still missing something here can you summarize it all in a comment so it's a bit easier to follow where I'm not understanding?

@PureWeen, As suggested, setting the parent of a MenuBarItem to null when its parent is MenuBar resolves the actual issue. However, it causes the following test case to fail.

ClearUpdatesParent()

In this test case, the ContentPage's MenuBarItems are cleared. At that time, the parent of the MenuBarItem is ContentPage. Therefore, when MenuBarItems are cleared, the parent of the MenuBarItem must also be cleared and set to null. This seems to be a valid case. However, due to your suggested fix, these test cases have failed.

The actual issue is that the MenuBarItem's BindingContext is reset to null after navigating to another page due to the previous page's MenuBarItem parent being set to null. To resolve this, I added a fix where the BindingContext for the MenuBarItem is reapplied within the OnAppearing() method of the page. This ensures that the MenuBarItem's BindingContext is correctly set when the page is displayed again after navigation, without affecting any previous logic.

@PureWeen
Copy link
Member

PureWeen commented Feb 4, 2025

@BagavathiPerumal Apologies but I'm having trouble following the thread on this one
AFAICT this code is wrong

if (item is Element e)
e.Parent = null;

and should be changed to this

			if (item is Element e && e.Parent == this)
				e.Parent = null;

What I'm thinking here is

  1. remove the code you've added
  2. make the change I link above

And then that should be the proper fix here.
If I'm still missing something here can you summarize it all in a comment so it's a bit easier to follow where I'm not understanding?

@PureWeen, As suggested, setting the parent of a MenuBarItem to null when its parent is MenuBar resolves the actual issue. However, it causes the following test case to fail.

ClearUpdatesParent()

In this test case, the ContentPage's MenuBarItems are cleared. At that time, the parent of the MenuBarItem is ContentPage. Therefore, when MenuBarItems are cleared, the parent of the MenuBarItem must also be cleared and set to null. This seems to be a valid case. However, due to your suggested fix, these test cases have failed.

The actual issue is that the MenuBarItem's BindingContext is reset to null after navigating to another page due to the previous page's MenuBarItem parent being set to null. To resolve this, I added a fix where the BindingContext for the MenuBarItem is reapplied within the OnAppearing() method of the page. This ensures that the MenuBarItem's BindingContext is correctly set when the page is displayed again after navigation, without affecting any previous logic.

Yea, but the problem is that the MenuBarItems parent should never be set to null. It belongs to the the Page so it isn't the responsibility of the MenuBar to clear out the parent.

The tests inside ClearUpdatesParent() attempt to be generalized across all the different types of menu since they apply to a lot of the same scenarios but there are cases where the logic differs.

For example, if you check this test

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

you'll see the logic deviates based on the type of item, so I'm pretty sure this is the same case. The ClearUpdatesParent needs to be fixed so that it's actually checking that the parent isn't removed when clearing the MenuBar

@BagavathiPerumal
Copy link
Contributor Author

@BagavathiPerumal Apologies but I'm having trouble following the thread on this one
AFAICT this code is wrong

if (item is Element e)
e.Parent = null;

and should be changed to this

			if (item is Element e && e.Parent == this)
				e.Parent = null;

What I'm thinking here is

  1. remove the code you've added
  2. make the change I link above

And then that should be the proper fix here.
If I'm still missing something here can you summarize it all in a comment so it's a bit easier to follow where I'm not understanding?

@PureWeen, As suggested, setting the parent of a MenuBarItem to null when its parent is MenuBar resolves the actual issue. However, it causes the following test case to fail.
ClearUpdatesParent()
In this test case, the ContentPage's MenuBarItems are cleared. At that time, the parent of the MenuBarItem is ContentPage. Therefore, when MenuBarItems are cleared, the parent of the MenuBarItem must also be cleared and set to null. This seems to be a valid case. However, due to your suggested fix, these test cases have failed.
The actual issue is that the MenuBarItem's BindingContext is reset to null after navigating to another page due to the previous page's MenuBarItem parent being set to null. To resolve this, I added a fix where the BindingContext for the MenuBarItem is reapplied within the OnAppearing() method of the page. This ensures that the MenuBarItem's BindingContext is correctly set when the page is displayed again after navigation, without affecting any previous logic.

Yea, but the problem is that the MenuBarItems parent should never be set to null. It belongs to the the Page so it isn't the responsibility of the MenuBar to clear out the parent.

The tests inside ClearUpdatesParent() attempt to be generalized across all the different types of menu since they apply to a lot of the same scenarios but there are cases where the logic differs.

For example, if you check this test

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

you'll see the logic deviates based on the type of item, so I'm pretty sure this is the same case. The ClearUpdatesParent needs to be fixed so that it's actually checking that the parent isn't removed when clearing the MenuBar

@PureWeen, As suggested, I have modified the fix and the test case(ClearUpdatesParent()). Could you please share your concerns about this.

@jsuarezruiz
Copy link
Contributor

/azp run

Copy link

Azure Pipelines successfully started running 3 pipeline(s).

@PureWeen PureWeen merged commit 8000048 into dotnet:main Feb 9, 2025
117 of 124 checks passed
@HimanshuBansal112
Copy link

Does it fix #13848, repository for my issue is at https://github.com/HimanshuBansal112/NetMauiIssue for testing?

@MartyIX
Copy link
Contributor

MartyIX commented Feb 11, 2025

Does it fix #13848, repository for my issue is at https://github.com/HimanshuBansal112/NetMauiIssue for testing?

You can try a nightly build to figure out if the issue is fixed or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-controls-contextmenu ContextMenu community ✨ Community Contribution partner/syncfusion Issues / PR's with Syncfusion collaboration
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

MenuFlyoutItem stops working after navigating away from and back to page
9 participants