I have a window with a Main frame which contains a Page. And I have another Page that contains the first Page. And I want to move between this two Pages. Here is the first page:
And the second one:
Everything is ok for the first time when I run the app. But when I come back to the first Page and then to the second, the first Page in the second Page's frame disappears:
Here is the MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public static TestPage testPage = new TestPage();
public static CombinedPage combinedPage = new CombinedPage();
public MainWindow()
{
InitializeComponent();
}
private void Butt1_Click(object sender, RoutedEventArgs e)
{
combinedPage.TimerFrame.Content = null;
Main.Content = testPage;
}
private void Butt2_Click(object sender, RoutedEventArgs e)
{
Main.Content = null;
combinedPage.Call();
Main.Content = combinedPage;
}
}
MainWindow.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="80 px"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="70 px" ></ColumnDefinition>
<ColumnDefinition Width="*" ></ColumnDefinition>
</Grid.ColumnDefinitions>
<StackPanel Grid.Row="0" Grid.Column="0" Orientation="Vertical" VerticalAlignment="Top" HorizontalAlignment="Left" Grid.RowSpan="2">
<Button x:Name="Butt1" Height="50" Width="50" Click="Butt1_Click" />
<Button x:Name="Butt2" Height="50" Width="50" Click="Butt2_Click" />
</StackPanel>
<Frame NavigationUIVisibility="Hidden" x:Name="Main" Grid.Row="1" Grid.Column="1"/>
</Grid>
CombinedPage.xaml.cs:
public partial class CombinedPage : Page
{
public CombinedPage()
{
InitializeComponent();
}
public void Call()
{
TimerFrame.Content = MainWindow.testPage;
}
}
CombinedPage.xaml:
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition Height="Auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<GridSplitter Grid.Column="1" Grid.RowSpan="3" Width="5" Background="Black"
VerticalAlignment="Stretch" HorizontalAlignment="Center" />
<GridSplitter Grid.Row="1" Grid.ColumnSpan="3" Height="5" Background="Black"
VerticalAlignment="Center" HorizontalAlignment="Stretch" />
<Frame NavigationUIVisibility="Hidden" x:Name="TimerFrame" Grid.Row="0" Grid.Column="0"/>
</Grid>
So the final question is - Why does the first Page disappear when I move between pages?
I'm actuall using the Frame
quiet rarily. It's a very heavy control that is only recommended when you want to display web content.
I highly recommend to use a ContentControl
and a DataTemplate
for each page/view.
See this example: C# WPF Navigation Between Pages (Views).
Using the suggested solution makes navigation more convenient. It eliminates isues like yours. Additionally, you have more options to customize the page navigation like adding animations.
The issue is that the Frame
implements a special navigation flow. Setting the Frame.Source
or Frame.Content
property does not immediately completes the navigation i.e. the destination Page
value is not immediately applied:
(Source: Microsoft Docs: Navigation Overview)
As you can learn from the diagramm, setting the Frame.Source
or Frame.Content
results in a series of events that ends with the LoadCompleted
event.
You are currently navigating to pages too early. The testPage
is still loaded in the recent Frame
and is therefore not able to be displayed in the next Frame
.
The special problem with your scenario is that you want to load the same Page
instance in different Frame
instances. Since the Page
instance can only be rendered once, you must unload the Page
instance from the recent Frame
in order to show it in the next Frame
.
To fix your problem you have two options:
Handle the LoadCompleted
event after the null
assignment/Frame
reset and set the new Page
from the handler.
Use the Dispatcher
with a priority of at least DispatcherPriority.Background
to defer the assignment after the null
assignment/Frame
reset. For example:
Dispatcher.InvokeAsync(() => Main.Navigate(testPage), DispatcherPriority.Background);
I recommend handling the LoadCompleted
event as the behavior is definitely more deterministic:
private void Butt1_Click(object sender, RoutedEventArgs e)
{
combinedPage.TimerFrame.LoadCompleted += SetMainFrame_OnLoadCompleted;
combinedPage.TimerFrame.Navigate(null);
}
private void SetMainFrame_OnLoadCompleted(object sender, NavigationEventArgs e)
{
combinedPage.TimerFrame.LoadCompleted -= SetMainFrame_OnLoadCompleted;
Main.Navigate(testPage);
}
private void Butt2_Click(object sender, RoutedEventArgs e)
{
Main.LoadCompleted += SetFrameContents_OnLoadCompleted;
Main.Navigate(null);
}
private void SetFrameContents_OnLoadCompleted(object sender, NavigationEventArgs e)
{
Main.LoadCompleted -= SetFrameContents_OnLoadCompleted;
combinedPage.Call();
Main.Navigate(combinedPage);
}