I have a requirement to allow users to drill down into related documents, then return. For example, a purchase order may have several payments. While editing a payment, the user wants to be able to open the purchase order as a new page and then return to the payment. Furthermore the user wants to be able to be looking at a payment, then drill down to the purchase order, then drill further down into any of the purchase order's payments - potentially into the payment they had open in the first place.
This is fine except the second instance of the payment must be opened in read-only mode to avoid concurrency issues.
The frame's NavigationService has BackStack property which contains a JournalEntry object for each page on the back stack. Unfortunately the JournalEntry object has very few useful public properties. What I really want is to get access to the object that was the frame's Content.
Let's get a sample project started. Create a new WPF Application project called InterrogateNavigation.
We will start by adding some pages that navigate to each other creating a stack of journal entries. Modify MainWindow to look like this.
<Window x:Class="InterrogateNavigationService.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:InterrogateNavigationService"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525"
Loaded="MainWindow_Loaded">
<Frame Initialized="Frame_Initialized"
NavigationUIVisibility="Hidden">
</Frame>
</Window>
using System.Windows;
using System.Windows.Controls;
namespace InterrogateNavigationService
{
public partial class MainWindow : Window
{
public Frame theFrame;
public MainWindow()
{
InitializeComponent();
}
public void
MainWindow_Loaded(object Sender, RoutedEventArgs e)
{
theFrame.Navigate(new Page1(theFrame));
}
private void
Frame_Initialized(object sender,
System.EventArgs e)
{
theFrame = (Frame)sender;
}
}
}
<local:BaseUserControl x:Class="InterrogateNavigationService.Page1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:InterrogateNavigationService"
mc:Ignorable="d">
<local:BaseUserControl.Resources>
<RoutedCommand x:Key="Page2"/>
<RoutedCommand x:Key="GoBack"/>
</local:BaseUserControl.Resources>
<local:BaseUserControl.CommandBindings>
<CommandBinding Command="{StaticResource Page2}" Executed="Page2_Executed"/>
<CommandBinding Command="{StaticResource GoBack}" CanExecute="GoBack_CanExecute" Executed="GoBack_Executed"/>
</local:BaseUserControl.CommandBindings>
<StackPanel Orientation="Vertical">
<TextBlock Text="I
am page 1"/>
<Button Content="Take
me to Page 2" Command="{StaticResource Page2}"/>
<Button Content="Go
Back" Command="{StaticResource GoBack}"/>
</StackPanel>
</local:BaseUserControl>
using System.Windows.Controls;
using System.Windows.Input;
namespace InterrogateNavigationService
{
public partial class Page1 : BaseUserControl
{
public Page1(Frame Parent)
{
myParent = Parent;
myParam = "Page1";
InitializeComponent();
}
private void
Page2_Executed(object sender, ExecutedRoutedEventArgs e)
{
myParent.Navigate(new Page2(myParent));
}
}
}
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Navigation;
namespace InterrogateNavigationService
{
public class BaseUserControl : UserControl
{
public Frame myParent;
public string myParam;
protected void GoBack_CanExecute(object sender, CanExecuteRoutedEventArgs e)
{
e.CanExecute =
(myParent.CanGoBack);
}
protected void
GoBack_Executed(object sender, ExecutedRoutedEventArgs e)
{
myParent.GoBack();
}
}
}
Page1 can also navigate to Page2 which looks remarkably similar.
<local:BaseUserControl x:Class="InterrogateNavigationService.Page2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:InterrogateNavigationService"
mc:Ignorable="d">
<local:BaseUserControl.Resources>
<RoutedCommand x:Key="Page1"/>
<RoutedCommand x:Key="GoBack"/>
</local:BaseUserControl.Resources>
<local:BaseUserControl.CommandBindings>
<CommandBinding Command="{StaticResource Page1}" Executed="CommandBinding_Executed"/>
<CommandBinding Command="{StaticResource GoBack}" CanExecute="GoBack_CanExecute" Executed="GoBack_Executed"/>
</local:BaseUserControl.CommandBindings>
<StackPanel>
<TextBlock Text="I
am page 2"/>
<Button Content="Take
me to page 1" Command="{StaticResource Page1}"/>
<Button Content="Go
Back" Command="{StaticResource GoBack}"/>
</StackPanel>
</local:BaseUserControl>
using System.Windows.Controls;
using System.Windows.Input;
namespace InterrogateNavigationService
{
public partial class Page2 : BaseUserControl
{
public Page2(Frame Parent)
{
myParent = Parent;
myParam = "Page2";
InitializeComponent();
}
private void
CommandBinding_Executed(object sender, ExecutedRoutedEventArgs e)
{
myParent.Navigate(new Page1(myParent));
}
}
}
In the BaseUserControl class we will execute some code in the Loaded event handler. The modified constructor and event handler look like this.
public BaseUserControl()
{
Loaded += CheckForDuplicate;
}
void CheckForDuplicate(object sender, RoutedEventArgs e)
{
Application.Current.MainWindow.Title = "";
if (myParent.BackStack != null)
{
foreach (JournalEntry je in
myParent.BackStack)
{
PropertyInfo
piKeepAliveRoot = je.GetType().GetProperty("KeepAliveRoot", BindingFlags.NonPublic | BindingFlags.Instance);
BaseUserControl uc = (BaseUserControl)piKeepAliveRoot.GetValue(je,
null);
if (uc.GetType() == this.GetType())
Application.Current.MainWindow.Title = "Duplicate
of " + uc.myParam;
}
}
}
We are looking at the Frame's BackStack. I pass around a reference to the frame in myParent to simplify the example. The BackStack is an Enumerable of JournalEntries or derived classes. The JournalEntry has a Friend property called KeepAliveRoot. We can access that property using reflection. The property stores the Content of the frame before we navigated away from it. All we have to do is cast it (because we know it inherits BaseUserControl) and then we can examine it.
After navigating from Page1 to Page2 then on to Page1 |
This is yet another argument for using base classes for all your pages.
No comments:
Post a Comment