Welcome to irritatedVowel.com Sign in | Help

POKE 53280,0: Pete Brown's Blog

Silverlight, WPF, Woodworking, .NET Programming, CNC, Nature, and other topics.
Pete is an MVP for Silverlight Click here to view the MVP profile.

A Microsoft Silverlight MVP and INETA Speaker, Pete Brown writes on a number of topics including Silverlight, WPF, .NET, woodworking and working as a consultant in the DC area. read more

Subscribe

Subscribe to my feed
Add to Technorati Favorites

Community Events



Applied Information Sciences - My Employer

who's online

AddThis Social Bookmark Button

UserControls as Screens in Silverlight 1.1 - Part 1 of 2

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 ]

 

  Add to Technorati Favorites
Posted: Friday, October 12, 2007 3:11 PM by Pete.Brown

Comments

Matt Casto said:

Nice, I'm really looking forward to part 2! Will any source code for the base classes be available for download?
# October 13, 2007 10:07 AM

Pete.Brown said:

Yes, that's why I had to break this into two parts. I plan to put together a sample project coded from scratch (I can't directly use what we did on that calculator project).

# October 13, 2007 7:29 PM

Christopher Steen said:

Link Listing - October 14, 2007
# October 14, 2007 11:27 PM

Christopher Steen said:

FizzBuzz c# 3.0 [Via: D. Mark Lindell ] MbUnit: Testing Internal classes [Via: vkreynin ] Explaining...
# October 14, 2007 11:28 PM

POKE 53280,0: Pete Brown's Blog said:

In Part 1 , I outlined how we handled screen management in the Silverlight 1.1 Carbon Calculator. In
# October 16, 2007 9:23 PM

POKE 53280,0: Pete Brown's Blog said:

In Part 1 , I outlined how we handled screen management in the Silverlight 1.1 Carbon Calculator. In
# October 16, 2007 9:27 PM

Brian said:

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
# November 7, 2007 5:03 PM

Pete.Brown said:

Do you have the Silverlight Tools for VS 2008 Beta 2 installed? If not, you won't be able to open the project in Visual Studio.

# November 7, 2007 5:30 PM

Brian said:

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
# November 8, 2007 5:01 PM

Brian said:

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 :)
# November 8, 2007 5:04 PM

Brian said:

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
# November 8, 2007 5:15 PM

Brian said:

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
# November 12, 2007 3:12 PM

Pete.Brown said:

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

# November 12, 2007 3:16 PM

Brian said:

THANK YOU- That is a great help - now I understand. :)
# November 14, 2007 1:34 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 

Enter the text you see in the image:

Comment Notification

If you would like to receive an email when updates are made to this post, please register here

Subscribe to this post's comments using RSS