导航返回后 Xamarin.Forms 中未显示菜单项
Menu Item not showing in Xamarin.Forms after navigation back
我正在开发 Xamarin.Forms Android-App,其中在 ContentPage 上使用 CustomRenderer在页面的 Header 中显示 SearchView。 "SearchPage" class 的 CustomRenderer 如下所示:
/// <summary>
/// The search page renderer.
/// </summary>
public class SearchPageRenderer : PageRenderer
{
/// <summary>
/// Gets or sets the search view.
/// </summary>
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Toolbar toolbar;
/// <summary>
/// Reaction on the disposing of the page.
/// </summary>
/// <param name="disposing">A value indicating whether disposing.</param>
protected override void Dispose(bool disposing)
{
if (this.searchView != null)
{
this.searchView.QueryTextChange -= this.OnQueryTextChangeSearchView;
}
this.toolbar?.Menu?.RemoveItem(Resource.Menu.mainmenu);
base.Dispose(disposing);
}
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
/// <summary>
/// Adds a search item to the toolbar.
/// </summary>
private void AddSearchToToolBar()
{
this.toolbar = (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById<Toolbar>(Resource.Id.toolbar);
if (this.toolbar != null)
{
this.toolbar.Title = this.Element.Title;
this.toolbar.InflateMenu(Resource.Menu.mainmenu);
this.searchView = this.toolbar.Menu?.FindItem(Resource.Id.action_search)?.ActionView?.JavaCast<SearchView>();
if (this.searchView != null)
{
this.searchView.QueryTextChange += this.OnQueryTextChangeSearchView;
this.searchView.ImeOptions = (int)ImeAction.Search;
this.searchView.MaxWidth = int.MaxValue;
this.searchView.SetBackgroundResource(Resource.Drawable.textfield_search_holo_light);
}
}
}
/// <summary>
/// Reaction on the text change event of the searchbar.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event argument.</param>
private void OnQueryTextChangeSearchView(object sender, SearchView.QueryTextChangeEventArgs e)
{
var searchPage = this.Element as SearchPage;
searchPage?.SearchCommand?.Execute(e?.NewText);
}
}
多亏了 Stack-Overflow 线程,到目前为止,我让它工作得很好。
现在,如果用户点击 "SearchPage" 中的某个项目,则会使用以下方法将新的 ContentPage(称为 "DetailPage")推送到 NavigationStack:
private async Task PushPageAsync(object model, ContentPage page, INavigation navigation)
{
page.BindingContext = model;
await navigation.PushAsync(page).ConfigureAwait(false);
}
这没有问题。但是,如果用户使用 Back-Button 从 "DetailPage" 导航回 "SearchPage", 自定义的 Search-Header 根本不会显示 .
我尝试了什么:
使用页面的 "OnAppearing" 事件代替 "OnElementChanged"。乍一看,这并没有解决问题。但是,如果我将 Task.Delay(500) 添加到 OnAppearing-Method,然后再次添加 SearchView ,它就会显示出来。但是这个修复看起来很丑陋,如果我休眠应用程序并在使用 SearchPage 时恢复,Search-Widget 显示 两次 。
所以我的问题是:
是 Xamarin 有问题还是我做错了什么?
Is there a bug in Xamarin or am I doing something wrong?
我不能说这是一个错误,我更愿意认为这是设计使然。真正的问题是您正在尝试修改自定义渲染器中的 Toolbar
,并且根据您的描述,您正在使用 NavigationPage
,它的 NavigationPageRenderer 将更新 Toolbar
每次改变当前页面时。
所以你使用页面的"OnAppearing"事件而不是"OnElementChanged"是正确的,这会导致另一个问题,Toolbar
的更新在RemovePage
方法中的源代码中可以看到旧页面被删除,而 OnAppearing
方法将在显示新页面时立即执行:
Device.StartTimer(TimeSpan.FromMilliseconds(10), () =>
{
UpdateToolbar();
return false;
});
所以我认为你也没有做错什么,你使用的方法await Task.Delay(500);
可以很快解决这个问题。
根据您在此处的描述:
But this fix seems quite ugly, and if i sleep the app and resume while using the SearchPage, the Search-Widget is showing twice.
我建议将您的 SearchPage
更改为 View
,然后动态 add/remove 来自页面的此视图:
public class SearchPage : View
{
...
}
及其渲染器(其他代码相同,只是继承自ViewRenderer
,OnElementChanged
的参数不同):
public class SearchPageRenderer : ViewRenderer
{
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Android.Support.V7.Widget.Toolbar toolbar;
...
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
...
}
然后您可以在要显示它的页面中将其用作 control/view,而不是直接用作 Page
。例如,我想在 MainPage
中显示此搜索视图,然后导航到 App.xaml.cs 中的 MainPage
:
MainPage = new NavigationPage(new MainPage());
MainPage
的布局:
<StackLayout>
...
</StackLayout>
最后覆盖MainPage
的OnAppearing
和OnDisappearing
方法:
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
var content = this.Content as StackLayout;
content.Children.Add(new SearchPage());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var content = this.Content as StackLayout;
foreach (var child in content.Children)
{
var searchpage = child as SearchPage;
if (searchpage != null)
{
content.Children.Remove(searchpage);
return;
}
}
}
顺便说一句,如果你想从MainPage
这里导航到其他页面,你可以这样编码:
this.Navigation.PushAsync(new Page1());
无需为其创建 PushPageAsync
任务。
我正在开发 Xamarin.Forms Android-App,其中在 ContentPage 上使用 CustomRenderer在页面的 Header 中显示 SearchView。 "SearchPage" class 的 CustomRenderer 如下所示:
/// <summary>
/// The search page renderer.
/// </summary>
public class SearchPageRenderer : PageRenderer
{
/// <summary>
/// Gets or sets the search view.
/// </summary>
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Toolbar toolbar;
/// <summary>
/// Reaction on the disposing of the page.
/// </summary>
/// <param name="disposing">A value indicating whether disposing.</param>
protected override void Dispose(bool disposing)
{
if (this.searchView != null)
{
this.searchView.QueryTextChange -= this.OnQueryTextChangeSearchView;
}
this.toolbar?.Menu?.RemoveItem(Resource.Menu.mainmenu);
base.Dispose(disposing);
}
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Page> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
/// <summary>
/// Adds a search item to the toolbar.
/// </summary>
private void AddSearchToToolBar()
{
this.toolbar = (CrossCurrentActivity.Current?.Activity as MainActivity)?.FindViewById<Toolbar>(Resource.Id.toolbar);
if (this.toolbar != null)
{
this.toolbar.Title = this.Element.Title;
this.toolbar.InflateMenu(Resource.Menu.mainmenu);
this.searchView = this.toolbar.Menu?.FindItem(Resource.Id.action_search)?.ActionView?.JavaCast<SearchView>();
if (this.searchView != null)
{
this.searchView.QueryTextChange += this.OnQueryTextChangeSearchView;
this.searchView.ImeOptions = (int)ImeAction.Search;
this.searchView.MaxWidth = int.MaxValue;
this.searchView.SetBackgroundResource(Resource.Drawable.textfield_search_holo_light);
}
}
}
/// <summary>
/// Reaction on the text change event of the searchbar.
/// </summary>
/// <param name="sender">The event sender.</param>
/// <param name="e">The event argument.</param>
private void OnQueryTextChangeSearchView(object sender, SearchView.QueryTextChangeEventArgs e)
{
var searchPage = this.Element as SearchPage;
searchPage?.SearchCommand?.Execute(e?.NewText);
}
}
多亏了
private async Task PushPageAsync(object model, ContentPage page, INavigation navigation)
{
page.BindingContext = model;
await navigation.PushAsync(page).ConfigureAwait(false);
}
这没有问题。但是,如果用户使用 Back-Button 从 "DetailPage" 导航回 "SearchPage", 自定义的 Search-Header 根本不会显示 .
我尝试了什么:
使用页面的 "OnAppearing" 事件代替 "OnElementChanged"。乍一看,这并没有解决问题。但是,如果我将 Task.Delay(500) 添加到 OnAppearing-Method,然后再次添加 SearchView ,它就会显示出来。但是这个修复看起来很丑陋,如果我休眠应用程序并在使用 SearchPage 时恢复,Search-Widget 显示 两次 。
所以我的问题是:
是 Xamarin 有问题还是我做错了什么?
Is there a bug in Xamarin or am I doing something wrong?
我不能说这是一个错误,我更愿意认为这是设计使然。真正的问题是您正在尝试修改自定义渲染器中的 Toolbar
,并且根据您的描述,您正在使用 NavigationPage
,它的 NavigationPageRenderer 将更新 Toolbar
每次改变当前页面时。
所以你使用页面的"OnAppearing"事件而不是"OnElementChanged"是正确的,这会导致另一个问题,Toolbar
的更新在RemovePage
方法中的源代码中可以看到旧页面被删除,而 OnAppearing
方法将在显示新页面时立即执行:
Device.StartTimer(TimeSpan.FromMilliseconds(10), () =>
{
UpdateToolbar();
return false;
});
所以我认为你也没有做错什么,你使用的方法await Task.Delay(500);
可以很快解决这个问题。
根据您在此处的描述:
But this fix seems quite ugly, and if i sleep the app and resume while using the SearchPage, the Search-Widget is showing twice.
我建议将您的 SearchPage
更改为 View
,然后动态 add/remove 来自页面的此视图:
public class SearchPage : View
{
...
}
及其渲染器(其他代码相同,只是继承自ViewRenderer
,OnElementChanged
的参数不同):
public class SearchPageRenderer : ViewRenderer
{
private SearchView searchView;
/// <summary>
/// Gets or sets the toolbar.
/// </summary>
private Android.Support.V7.Widget.Toolbar toolbar;
...
/// <summary>
/// Reaction on the element changed event.
/// </summary>
/// <param name="e">The event argument.</param>
protected override void OnElementChanged(ElementChangedEventArgs<Xamarin.Forms.View> e)
{
base.OnElementChanged(e);
if (e?.NewElement == null || e.OldElement != null)
{
return;
}
this.AddSearchToToolBar();
}
...
}
然后您可以在要显示它的页面中将其用作 control/view,而不是直接用作 Page
。例如,我想在 MainPage
中显示此搜索视图,然后导航到 App.xaml.cs 中的 MainPage
:
MainPage = new NavigationPage(new MainPage());
MainPage
的布局:
<StackLayout>
...
</StackLayout>
最后覆盖MainPage
的OnAppearing
和OnDisappearing
方法:
protected override async void OnAppearing()
{
base.OnAppearing();
await Task.Delay(500);
var content = this.Content as StackLayout;
content.Children.Add(new SearchPage());
}
protected override void OnDisappearing()
{
base.OnDisappearing();
var content = this.Content as StackLayout;
foreach (var child in content.Children)
{
var searchpage = child as SearchPage;
if (searchpage != null)
{
content.Children.Remove(searchpage);
return;
}
}
}
顺便说一句,如果你想从MainPage
这里导航到其他页面,你可以这样编码:
this.Navigation.PushAsync(new Page1());
无需为其创建 PushPageAsync
任务。