Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

UserControls as Screens in Silverlight 1.1 - Part 1 of 2

Pete Brown - 12 October 2007

The main pattern I followed in the Silverlight 1.1 Carbon Calculator UI development was the pattern of using UserControls as screens. I spoke about this pattern in my Remix07 Presentation in Boston this week during my Real World Silverlight session.

I presented about this pattern back in the late 90s when I talked about implementing it in VB6 for Outlook-like and Wizard-like application user interfaces. I was not the only one by far to independently come up with this, as it was a very intuitive approach to managing screen design in that technology. After leaving it alone for years, I found myself returning to that pattern in Silverlight 1.1.

This pattern requires that you either dynamically load screens, or more commonly, hide and show pages within a common container on the main screen. Each screen in the application is encapsulated in a single usercontrol. Screens themselves may have other nested screens (the carbon calculator does that on the survey pages), or may simply host input/display controls.

Advantages when used with Silverlight vs multiple Page xaml files or dynamic xaml loading:

  • No need to context switch between different main pages. Instead, you keep one main controller page and show/hide bits as needed
  • Each screen can be designed separately in Blend as each has a separate xaml file
  • Each screen can have standard windows-like methods (Show, Hide) and can manage its own state and incoming/outgoing animations
  • No need to call CreateFromXaml and have a master page that contains all possible logic

Class Hierarchy

System.Windows.Control
   ControlBaseEx
      ControlPageBase
         Derived Screens

ControlBaseEx

ControlBaseEx (named as much because there was a ControlBase that came with the SDK, and I originally used that in the project as well) is the base class for all the controls in the project. That includes controls like the drop down list box as well as the individual screens. It had a lot of logic in it to handle a number of application-specific situations, but I have listed out below only the parts important to this pattern.

    public abstract class ControlBaseEx : Control
    {
        private DependencyObject _rootElement;

        public ControlBaseEx()
        {
            this.Loaded += new EventHandler(OnLoaded);
        }

        // Simplifies calls to FindName by selecting the correct root
        // element from which to base the search and also by returning
        // back a strongly-typed reference to the element
        // a common stumbling block in SL code is to call Control.FindName
        // which doesn't return what you want (at leat in 1.1a). Instead, 
        // you need to call FindName from the root element.
        protected T FindByName<T>(string name) where T : DependencyObject
        {
            return _rootElement.FindName(name) as T;
        }

        // Set in the derived class after it loads the xaml file
        // This is the root element from which FindByName works
        protected DependencyObject RootElement
        {
            get { return _rootElement; }
            set { _rootElement = value; }
        }

        ...

ControlPageBase

ControlPageBase is the base class for all the usercontrol screens in the application. Deriving from ControlBaseEx, it also adds in Show and Hide functionality as described above.

    public abstract class ControlPageBase: ControlBaseEx
    {
        protected const string _windowShowAnimationName = "AnimateShow";
        protected const string _windowHideAnimationName = "AnimateHide";

        public event EventHandler MoveNext;
        public event EventHandler MoveBack;

        ...

        protected void LoadXaml(string xamlResourceName)
        {
            try
            {
                System.IO.Stream s = this.GetType().Assembly.GetManifestResourceStream(xamlResourceName);
                RootElement = this.InitializeFromXaml(new System.IO.StreamReader(s).ReadToEnd());
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
                throw;
            }
        }


        protected void RaiseMoveNext()
        {
            if (MoveNext != null)
                MoveNext(this, new EventArgs());
        }

        protected void RaiseMoveBack()
        {
            if (MoveBack != null)
                MoveBack(this, new EventArgs());
        }

        public virtual void Show()
        {
            ...

            this.Visibility = Visibility.Visible;

            if (FindByName<Storyboard>(_windowShowAnimationName) == null)
            {
                this.Opacity = 1;
            }
            else
            {
                FindByName<Storyboard>(_windowShowAnimationName).Begin();
            }

            LogPageToHitbox();

        }

        //this will invoke a javascript event that logs to hitbox
        private void LogPageToHitbox()
        {
            //Get the name of this class it will be logged as the link
            try
            {
                string pageName = this.GetType().Name;
                HitBox.Current.LogPageToHitBox(pageName);
            }
            catch
            {
                //eat it
            }
        }

        public virtual void Hide()
        {

            if (FindByName<Storyboard>(_windowHideAnimationName) == null || !_animationsEnabled)
            {
                this.Visibility = Visibility.Collapsed;
            }
            else
            {
                FindByName<Storyboard>(_windowHideAnimationName).Begin();
            }
        }

        ...

Example Derived Screen

Below is an example of a derived screen. To create this screen, just add a usercontrol to your project (xaml and .xaml.cs) and change it to derive from ControlPageBase instead of from Control

    public class GiftOffsetPage : ControlPageBase, ...
    {
        private Canvas _offsetsContainer;
        private TextBlock _offsetAmountDisplay;

        public GiftOffsetPage()
        {
            try
            {
                LoadXaml("CarbonCalculator.UI.GiftOffsetPages.GiftOffsetPage.xaml");

                _offsetsContainer = FindByName("OffsetsContainer");
                _offsetAmountDisplay = FindByName("OffsetAmount");
                
                ...
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
                throw;
            }
        }

        ...

Usage and placement

To use all of this, simply create the base classes, derive your screens from the base class, and then position your screens appropriately. You will then show and hide

In Part 2, I'll post an example application with AnimateShow and AnimateHide and pull it all together along with some class diagrams and a discussion of simulating modal windows.

For more information on the HitBox tracking, see my post on that topic.

[ Continued in Part 2 ]

 

     
posted by Pete Brown on Friday, October 12, 2007
filed under:      

10 comments for “UserControls as Screens in Silverlight 1.1 - Part 1 of 2”

  1. Briansays:
    Anyone figure out how to get the project to load???

    I am using blend 2, and vs 2008- I get the following error when I try to load the project file:

    "the project file petebrown.usercontrolscreens.csproj cannot be opened. this project type is not supported by this installation"

    Apparently the project works for other people? Am I missing some unknown service pack or something??????????????????

    thanks,

    Brian
  2. Briansays:
    THANK YOU VERY MUCH!
    That was the missing piece.
    For anyone else who has this problem- you can get the missing piece from :
    http://www.microsoft.com/downloads/details.aspx?familyid=b52aeb39-1f10-49a6-85fc-a0a19cac99af&displaylang=en
  3. Briansays:
    I am still learning this stuff. I am JUST getting the hang of WPF and have not spent much time on Sliverlight.

    I would like to use this technique with a WPF EXE- Is this possible?. Does the technique require sliverlight or will it work with the latest WPF runtime?

    If I can get it going as a WPF exe, I would be happy to share the source if you would like it.

    Thanks again for previous help on install.

    Brian
    :)
  4. Briansays:
    Another newbie question :)

    I have spent the last few weeks learning WPF- and havent spent anytime on Sliverlight. (would like to use the example code in a WPF EXE)

    Why do you need to call FindName() on DependancyObject?

    i.e.
    if you have a control in your xaml called mycontrol, then in your code behind file you can just call mycontrol. (at least in WPF this works)

    Is the need to call findname() because the control your looking for was in a usercontrol dynamically loaded into the hierarchy?

    In which case- it would seem like the technique would have problems if you loaded two instances of the same "class", because it would only find the first one...

    I have looked up the API docs for FindName():
    http://msdn2.microsoft.com/en-us/library/bb680297.aspx

    This appears to be a Sliverlight only API. - Sorry- this is really confusing me. Why would an api like this NOT be in wpf????

    Any help appreciated

    Thanks,

    Brian
  5. Briansays:
    Ok- so I spent the weekend on this getting this code ported for WPF. Something I still don't understand- why do you NEED FindByName<> at ALL? In WPF , when you have code behind- you just access the child object by NAME?. Is it that silverlight doesnt support this???

    Also- for anyone else doing this- the button code- doesnt resize itself- it has hard coded height and width
  6. Pete Brownsays:
    Yep, those are all Silverlight-specific issues. Silverlight doesn't currently have any containers other than the Canvas, so you have to write resizing logic yourself.

    Second, FindByName is a Silverlight issue in the current Alpha of Silverlight 1.1. Only the root Page gets the names compiled in. The usercontrols load their xaml at runtime, and therefore resolve the names at runtime. So yes, you have to resolve them by string name. Annoying, but the only option at the moment.

    I fully expect both of those to change in a future Alpha/Beta of Silverlight.

    Pete

Comment on this Post

Remember me

4 trackbacks for “UserControls as Screens in Silverlight 1.1 - Part 1 of 2”

  1. Christopher Steensays:
    FizzBuzz c# 3.0 [Via: D. Mark Lindell ] MbUnit: Testing Internal classes [Via: vkreynin ] Explaining...
  2. POKE 53280,0: Pete Brown's Blogsays:
    In Part 1 , I outlined how we handled screen management in the Silverlight 1.1 Carbon Calculator. In
  3. POKE 53280,0: Pete Brown's Blogsays:
    In Part 1 , I outlined how we handled screen management in the Silverlight 1.1 Carbon Calculator. In