导航返回后 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
{
  ...
}

及其渲染器(其他代码相同,只是继承自ViewRendererOnElementChanged的参数不同):

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>

最后覆盖MainPageOnAppearingOnDisappearing方法:

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 任务。