Search code examples
c#windows-phonewindows-phone-8.1

Why is the Frame property null in a pivot item?


I have a pivot page defined like this:

<Grid x:Name="LayoutRoot" Background="Transparent" >
    <Pivot Title="JOETZ">
        <PivotItem Header="kampen" Name="PvOne">
            <views:CampsPage/>
        </PivotItem>

        <PivotItem Header="kalender" Name="PvTwo">
            <views:CalendarPage/>
        </PivotItem>

        <PivotItem Header="profiel" Name="PvThree">
            <views:LoginPage/>
        </PivotItem>
    </Pivot>
</Grid>

Taking the first page (CampsPage) as an example: it contains a ListBox with an overview of camps. when an item is clicked, it should go to a detailview. In order to navigate I figured this should've been enough:

Frame.Navigate(typeof(CampDetail), selectedCamp);

but this throws a NullReferenceException because Frame is apparently null.

On the other hand, doing navigation like this works:

var frame = Window.Current.Content as Frame;
if (frame == null) { return; }
frame.Navigate(typeof(CampDetail), selectedCamp);

And so does this approach:

var frame = ((App) Application.Current).RootFrame;
frame.Navigate(typeof(CampDetail), selectedCamp);

Why isn't the Frame property set in a pivot item? And as a followup: how should I navigate from pivot items?


Solution

  • It seems you've already found most of the info that you need. I can confirm that is the way to navigate (as you suggested yourself):

    var frame = (Frame)Window.Current.Content;
    frame.Navigate(typeof(CampDetail), selectedCamp);
    

    Getting the Frame this way is pretty common and safe. There won't be anything other than a Frame in the Window.Current.Content unless you have put it there. In which case, there simply won't be a Frame, and you won't be able to navigate like this anyway (that is, you'll have to manually change the Window.Current.Content property to show other content, and manually manage the back button).

    Some ideas/suggestions

    I'd suggest you create a static class with a static Navigate method, which does the work (gets the Frame and invokes its Navigate method).

    Perhaps even more convinient would be to (also) make an extension method to the UserControl class that does the navigation. This way you would simply be calling this.Navigate(typeof(CampDetail), selectedCamp) in all UserControls.

    I'd also suggest to be consistent and always use this method and never use the Page.Frame property (unless you really need to, for some reason). This would make finding/resolving problems related to navigation easier, as every navigation would be going through that method.

    You should use UserControl instead of Page

    A Page is a part of the app that is to be displayed on the whole screen. It is not indended to be a part of another page. The way you use it, it works like a UserControl (which is what you should be using in this case). The Page-specific features would only work if you navigate to it, using the Frame object.

    User controls and templated controls on the other hand, are reusable parts of pages. So, if you need to have some UI (with its functionality) in more than one page - you make one of these controls.

    So, I'd suggest you change <views:CampsPage/>, <views:CalendarPage/> and <views:LoginPage/> to be UserControls instead of Pages. If you need pages as well (for navigation purposes), make pages that just display these UserControls.

    Hardware Back button

    You don't seem to need this anymore, but I'll put a few lines anyway.

    By default the Back button just tells the Frame in the Window.Current.Content to GoBack() if possible closes the app. You can manually handle the back button and do some non-default action by using the HardwareButtons.BackPressed event.

    Edit

    I was wrong about the back button's default action and fixed it. I'm pretty sure it used to automatically navigate back through the stack.