Search code examples
javascriptsilverlight-4.0silverlight-toolkit

SilverLight 4: Navigate to different frames in a SL control from Javascript


I have a silverlight control that has a Frame on it. I want to change the URI of this frame from outside the SL control. (I have an HTML link that will use Javascript to ulimtately tell the SL control to change.) This is all working but I get random JavaScript errors.

Master Page:

<html>
<body>
    <a href="#" onclick="PdmcNav.NavigateTo('page1');">Page 1 Link</a> 
    <a href="#" onclick="PdmcNav.NavigateTo('page2');">Page 2 Link</a> 

    <div id="main" >
        <asp:ContentPlaceHolder ID="MainContent" runat="server">
        </asp:ContentPlaceHolder>
    </div>
</body>
</html>

Included Javascript:

// Defining the namespace object for the Pdmc navigation
var PdmcNav = {};
PdmcNav.PdmcSLControl = null;

PdmcNav.NavigateTo = function (pagename) {

    // check to see if PDMC Silverlight control is on page. if not (is null), then need to load main PDMC page
    if (PdmcNav.PdmcSLControl == null) {
        // handle this later
    } else {
        // Pdmc SL control on page.. 
        // Talk to silverlight control and request it to navigate to pagename
        PdmcNav.PdmcSLControl.Content.PdmcSL.NavigateToPage(pagename);
    }
}

Main Xaml page loaded in master page (MainNavigationView.xaml)

<UserControl x:Class="PDMC.Views.MainNavigationView"
             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:toolkit="clr-namespace:Microsoft.Windows;assembly=System.Windows.Controls.Toolkit"
             xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
             xmlns:navigationCore="clr-namespace:System.Windows.Navigation;assembly=System.Windows.Controls.Navigation"
             mc:Ignorable="d">
    <StackPanel>
        <!-- this is a test of navigation in the control.... works flawlessly -->
        <StackPanel Orientation="Horizontal">
            <HyperlinkButton Content="profile" Margin="4" TargetName="contentFrame" NavigateUri="/Views/SupplierProfile.xaml"/>
            <HyperlinkButton Content="scores" Margin="4" TargetName="contentFrame" NavigateUri="/Views/SupplierScores.xaml"/>
        </StackPanel>
        <navigation:Frame x:Name="contentFrame"
                          Source="/Views/Profile.xaml"
                          VerticalAlignment="Stretch"
                          HorizontalAlignment="Stretch" />
    </StackPanel>
</UserControl>

MainNavigationView.xaml.cs

using System.Windows;
using System.Windows.Controls;
using System.Windows.Browser;
using System;

namespace PDMC.Views {
    public partial class MainNavigationView : UserControl {
        /// <summary>
        /// Initializes a new instance of the MainNavigationView class.
        /// </summary>
        public MainNavigationView() {
            InitializeComponent();

            HtmlPage.RegisterScriptableObject("PdmcSL", this);
        }

        [ScriptableMember]
        public void NavigateToPage(string pageName) {

            if (pageName == "Profile") {
                Uri x = new Uri(@"/Views/Profile.xaml", System.UriKind.RelativeOrAbsolute);
                contentFrame.Source = x;//.Navigate(x);
            } else if (pageName == "Scores") {
                Uri x = new Uri(@"/Views/Scores.xaml", System.UriKind.RelativeOrAbsolute);
                contentFrame.Source=x;//.Navigate(x);
            }
        }
    }
}

I can click on the links in the master page a few times but after several clicks of going back and forth, i get the following error: (its random when i get this)

Message: Unhandled Error in Silverlight Application Content for the URI cannot be loaded. The URI may be invalid.
Parameter name: uri   at System.Windows.Navigation.NavigationService.NavigateCore(Uri uri, NavigationMode mode, Boolean suppressJournalAdd, Boolean isRedirect)
   at System.Windows.Navigation.NavigationService.Journal_Navigated(Object sender, JournalEventArgs args)
   at System.Windows.Navigation.Journal.OnNavigated(String name, Uri uri, NavigationMode mode)
   at System.Windows.Navigation.Journal.UpdateObservables(JournalEntry currentEntry, NavigationMode mode)
   at System.Windows.Navigation.Journal.AddHistoryPoint(JournalEntry journalEntry)
   at System.Windows.Navigation.Journal.AddHistoryPointIfDifferent(String newState)
   at System.Windows.Navigation.Journal.Browser_Navigated(Object sender, EventArgs eventArgs)
   at System.Windows.Navigation.Journal.<>c__DisplayClass3.<InitializeNavigationState>b__2(Object sender, NavigationStateChangedEventArgs args)
   at System.Windows.Interop.SilverlightHost.RaiseNavigationStateChanged(String oldState, String newState)
   at System.Windows.Interop.SilverlightHost.OnNavigationStatePollingTick(Object sender, EventArgs e)
   at MS.Internal.CoreInvokeHandler.InvokeEventHandler(Int32 typeIndex, Delegate handlerDelegate, Object sender, Object args)
   at MS.Internal.JoltHelper.FireEvent(IntPtr unmanagedObj, IntPtr unmanagedObjArgs, Int32 argsTypeIndex, Int32 actualArgsTypeIndex, String eventName)

Anybody see what Im doing wrong?


Solution

  • It appears the solution to my problem is to change my approach. During my research, I discovered that for the Frame element in Main.xaml, the default JournalOwnership is set to Automatic. If I set this to OwnsJournal, the problem goes away. Apparently, if the frame is using the journal of the browser, weird stuff happens if you navigate via a [ScriptableMethod].

    My solution was to change my approach to the problem.... which ends up being more simple and elegant. One thing to note is that when the Journal is managed by the browser (JournalOwnership=Automatic), you can navigate to pages within your control simply using the URL.

    Here's the solution I ended up with which allows me to have HTML navigation (outside my silverlight control) that will navigate to different pages in my SL control.

    Master Page (Plain Html with links to navigation)

    <html>
    <body>
        <asp:HyperLink ID="HyperLink4" runat="server" NavigateUrl="~/PDMC.aspx#Profiles">Profiles</asp:HyperLink>
        <asp:HyperLink ID="HyperLink2" runat="server" NavigateUrl="~/PDMC.aspx#Scores">Scores</asp:HyperLink>
    </body>
    </html>
    

    Note, the PDMC.aspx is a simple page which contains my silverlight control object.

    Main.xaml is the RootVisual of my silverlight Control. It simply contains a frame which we will use to swap out views:

    <navigation:Page x:Class="PDMC.Views.Main" 
        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"
        mc:Ignorable="d"
        xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
        d:DesignWidth="640" d:DesignHeight="480"
        Title="Main Page">    
            <ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Auto">
                <StackPanel x:Name="LayoutRoot">
                    <navigation:Frame x:Name="MainFrame" 
                                      Source="/Views/Profile.xaml"
                                      JournalOwnership="Automatic"
                                      UriMapper="{StaticResource PDMC_UriMapper}" />
                </StackPanel>
            </ScrollViewer>
    </navigation:Page>
    

    Finally, to make the external links simpler and prettier, I added a UriMapper to my App.xaml:

    <Application.Resources>
        <navigationCore:UriMapper x:Key="PDMC_UriMapper">
            <navigationCore:UriMapping Uri="Profiles" MappedUri="/Views/Profile.xaml" />
            <navigationCore:UriMapping Uri="Scores" MappedUri="/Views/Scores.xaml" />            
        </navigationCore:UriMapper>
    </Application.Resources>
    

    That's it.. a much simpler solution.. Hope this helps someone else down the road (and yes Im new to silverlight at time of this discovery :) )