Loading pages dynamically in Silverlight

I often find myself creating a large number of small test pages in my application, and every time I want to test one of them, I will have to go into App.Xaml.cs and change which page to load by setting the RootVisual.

Therefore I created a small class that allows you to on-the-fly change the currently loaded user control. It uses the #anchor tag in the URL so you can link directly to that page and use the back/forward buttons between your test pages. Furthermore, it will add a small unobtrusive 10x10px dropdown in the upper left corner. Clicking it will show you a list of all user controls available in your assembly, so you quickly can navigate to a specific user control.

To use it, first copy the code below, and add it to your project.

 

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Windows;
using System.Windows.Browser;
using System.Windows.Controls;
 
namespace SharpGIS
{
    public class ControlNavigator
    {
        private string currentHash;
        private System.Windows.Application application;
        private Grid rootGrid = new Grid();
        private string defaultControlName;
 
        /// <summary>
        /// Initializes a new instance of the <see cref="ControlNavigator"/> class.
        /// </summary>
        /// <param name="application">The application to navigate.</param>
        /// <param name="defaultControl">The default control to load when no/invalid anchor is specified.</param>
        public ControlNavigator(System.Windows.Application application, UIElement defaultControl)
        {
            this.application = application;
            defaultControlName = defaultControl.GetType().FullName;
            CreateControl();
            UIElement element = null;
            if (HtmlPage.IsEnabled)
            {
                System.Windows.Threading.DispatcherTimer timer = new System.Windows.Threading.DispatcherTimer();
                timer.Interval = TimeSpan.FromMilliseconds(500);
                timer.Tick += timer_Tick;
                currentHash = GetAnchor();
                element = LoadElement(currentHash);
                timer.Start();
            }
            ShowElement(element ?? defaultControl);
        }
 
        private void CreateControl()
        {
            Grid grid = new Grid();
            grid.Children.Add(rootGrid);
            ComboBox box = new ComboBox()
            {
                Width = 10,
                Height = 10,
                Opacity = 0.5,
                ItemsSource = GetUserControls(),
                HorizontalAlignment = HorizontalAlignment.Left,
                VerticalAlignment = VerticalAlignment.Top,
                Cursor = System.Windows.Input.Cursors.Hand
            };
            box.SelectionChanged += new SelectionChangedEventHandler(box_SelectionChanged);
            grid.Children.Add(box);
            this.application.RootVisual = grid;
        }
 
        private void box_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            string value = (sender as ComboBox).SelectedItem as string;
            if (HtmlPage.IsEnabled)
                HtmlPage.Window.Navigate(new Uri("#" + value, UriKind.Relative));
            else
                if (rootGrid.Children.Count == 0 || rootGrid.Children[0].GetType().FullName != value)
                    ShowElement(LoadElement(value));
        }
 
        private void timer_Tick(object sender, EventArgs e)
        {
            string hash = GetAnchor();
            if (hash != currentHash && 
                (rootGrid.Children.Count==0 || rootGrid.Children[0].GetType().FullName!=hash))
            {
                UIElement element = LoadElement(hash);
                if (element != null)
                {
                    ShowElement(element);
                }
            }
            currentHash = hash;
        }
 
        private void ShowElement(UIElement element)
        {
            if (element == null) return;
            rootGrid.Children.Clear();
            rootGrid.Children.Add(element);
        }
 
        private UIElement LoadElement(string name)
        {
            if (string.IsNullOrEmpty(name)) name = defaultControlName;
            try
            {
                return Assembly.GetExecutingAssembly().CreateInstance(name) as UIElement;
            }
            catch (System.Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(
                    string.Format("Couldn't load control '{0}':\n{1}\n{2}",
                    name, ex.Message, ex.StackTrace));
                MessageBox.Show(string.Format("Failed to load control {0}. See output for stack trace", name));
            }
            return null;
        }
 
        private static string GetAnchor()
        {
            if (HtmlPage.IsEnabled)
                return HtmlPage.Window.CurrentBookmark;
            else
                return null;
        }
 
        /// <summary>
        /// Creates a list of UserControls in this assembly that has an empty constructor
        /// </summary>
        /// <returns></returns>
        private List<string> GetUserControls()
        {
            List<string> types = new List<string>();
            Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly();
            foreach (Type type in assembly.GetTypes())
            {
                if (type.IsSubclassOf(typeof(UserControl)) && type.GetConstructor(new Type[] { }) != null)
                    types.Add(type.FullName);
            }
            return types;
        }
    }
}

Go to Application_Startup() in App.Xaml.cs and change the code to:

new SharpGIS.ControlNavigator(this, new MyPage());
where MyPage() is the page control that you want to load by default.

Download code sample here

View online demo

Add comment