Monday, November 30, 2009

MEF: Build a Plugin in Under 10 Minutes

In preparing for an upcoming talk I'll be giving on the Managed Extensibility Framework (MEF), I wanted to demonstrate how fast and easy it is to use in a sample, reference application. This application creates a dynamic plugin. I first link a plugin and show it active, then I create a second plugin and show how it is dynamically added to the program during runtime. It all takes under 10 minutes and would be faster if I didn't want to pace the demo to show the steps involved.

Here is a direct link to the video

Here it is embedded:

Jeremy Likness

Tuesday, November 24, 2009

RIA POCO DomainService with Existing Business Applications in Silverlight

Your boss suddenly decides that Silverlight is the "next big thing" and asks you to begin converting your line of business application to use the new features. Being a solid architect, of course, you have already built a nice application that is structured in layers. You have a data access layer that abstracts persistence behind interfaces, a domain layer that describes business models, a business logic layer for heavy lifting, a services layer for interconnectivity, and a presentation layer that is ASP.NET or something similar.

RIA services looks promising, but there is one problem. You see that you can create a LINQ to SQL based service, or an Entity Framework service, but you're not comfortable going to production with either of those because then it would introduce an entirely new way of accessing data. You want to keep your data access layer (maybe it's built using Enterprise Library or NHibernate or something similar).

What do you do?

Fortunately, RIA services is not limited to LINQ or EF. You can build a domain service that handles your POCO (plain-ole' CLR objects) domain models. If you are willing to extend your classes a bit and haven't already begun decorating your POCO objects with data annotations, you can handle that, too! Here's how.

Let's assume I've got a decent entity model based on entities that have an integer for their identifier. I do like INotifyPropertyChanged but had reservations about decorating my model with data annotations or validation attributes. Just didn't seem right. So what I have is a base entity that looks like this:


public abstract class BaseEntity : INotifyPropertyChanged
{
    private bool _isDirty = false;

    private bool _isNew = true;

    private int _id = -1;
  
    public virtual int ID
    {
        get { return _id; }
        set
        {
            if (!value.Equals(_id))
            {
                _id = value;
                OnPropertyChanged("ID", true);

                if (_isNew && value > 0)
                {
                    _isNew = false;
                    OnPropertyChanged("IsNew", true);
                }
            }
        }
    }

    public virtual bool IsNew
    {
        get { return _isNew; }
    }

    public virtual bool IsDirty
    {
        get { return _isDirty; }
    }

    public void Reset()
    {
        if (_isDirty)
        {
            _isDirty = false;
            OnPropertyChanged("IsDirty", false);
        }            
    }

    protected void OnPropertyChanged(string property)
    {
        OnPropertyChanged(property, true); 
    }


    private void OnPropertyChanged(string property, bool setDirtyFlag)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(property));
        }

        if (setDirtyFlag)
        {
            if (!_isDirty)
            {
                _isDirty = true;
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs("IsDirty"));
                }
            }
        }
    }

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion

    protected abstract bool _Equals(object obj);

    public override bool Equals(object obj)
    {
        return _Equals(obj);
    }

    public override int GetHashCode()
    {
        return _id.GetHashCode();
    }
}

I have a user entity defined as well. This contains properties for a username, a first and last name, and an email address. Only the username is required. The entity looks like this:


public class UserEntity : BaseEntity 
{
    private string _userName, _firstName, _lastName, _email;

    public virtual string UserName
    {
        get { return _userName; }
        set
        {
            if (value == null || !value.Equals(_userName))
            {
                _userName = value;
                OnPropertyChanged("UserName"); 
            }
        }
    }

    public virtual string FirstName
    {
        get { return _firstName; }
        set
        {
            if (value == null || !value.Equals(_firstName))
            {
                _firstName = value;
                OnPropertyChanged("FirstName"); 
            }
        }
    }

    public virtual string LastName
    {
        get { return _lastName; }
        set
        {
            if (value == null || !value.Equals(_lastName))
            {
                _lastName = value;
                OnPropertyChanged("LastName");
            }
        }
    }

    public virtual string Email
    {
        get { return _email; }
        set
        {
            if (value == null || !value.Equals(_email))
            {
                _email = value;
                OnPropertyChanged("Email");
            }
        }
    }

    protected override bool _Equals(object obj)
    {
        return obj is UserEntity && ((UserEntity)obj).Equals(ID);
    }
}

Note: it's quite possible that you didn't declare your properties as virtual. While I won't make you rewrite your domain model layer, it would be helpful if you're willing to make that one change ... I'll explain why later. Virtual gives us some flexibility to extend later on.

Now for my data access class, I have an interface that defines my basic data handling needs. How I implement these could be anything from NHibernate to Enterprise Library to my own SQL provider that directly accesses the database and parses readers and a data sets into my POCO objects. The point is, I can do this all through a clean interface, like this:


public interface IDataHandler<TEntity> where TEntity : BaseEntity 
{
    TEntity Load(int id);

    void Delete(int id);

    int Save(TEntity entity);

    IEnumerable<TEntity> List();
}

Great. So now I can implement and extend as needed. For example, I might create a concrete UserDataHandler that is based on the IDataHandler<UserEntity> interface. Sound good?

Now we want to build our domain service in RIA but maintain backwards compatibility with our existing system. After all, a large line of business application wasn't written overnight. It could take months to convert all of those screens to Silverlight and we might not even touch them all. It's important to maintain a consistent data access layer so I can fix things in one place and still reuse the existing connectors in my legacy code.

The first thing I'll do is fire up a new Silverlight Business Project. This puts in some plumbing that I might use, or I might throw out, but more importantly it integrates the structure for my Silverlight project to be able to seamlessly communicate with my RIA services.

It makes sense for those of you new to RIA to take a step back and mention two things.

First, RIA is "magic" and exposes a neat class that I can work with, without having to deal with the nuances of WCF services. In the end, however, RIA generates WCF endpoints. These are 100% bona-fide WCF service points that anything can plug into ... I'm just going to do it with my Silverlight application for now.

Second, RIA is "magic" and performs some code generation. I don't see these in a special designer.cs file, but it's there. Code generation isn't a popular term because of some bad projects in the past, so I think the cool word for it now is "projection." We say RIA projects code to my Silverlight application. This means I'll have access to the host web namespace from my Silverlight application.

Let's dig in. What I want to do is create a Domain Service that allows me to reuse my existing data access layer and POCO classes. What I'll do is add a new item to my web application (the one that's hosting the Silverlight, not the Silverlight application itself) and I'll call it LOBDomainService (line of business domain service). The shell that is provided for me looks like this:


[EnableClientAccess()]
public class LOBDomainService : DomainService
{
}

This is the start of a workable service. Because I called my class LOBDomainService, RIA will project for me a LOBDomainContext in the Silverlight client. We'll get to that in a moment.

First, we need to get our data access layer into the domain service. There are several ways to do this:

Factory


IDataHandler<UserEntity> dataHandler = MyDataFactory.GetDataAccess<UserEntity>();

Constructor Injection using something like StructureMap or Unity


private IDataHandler<UserEntity> _dataHandler;

public LOBDomainService(IDataHandler<UserEntity> dataHandler)
{
    _dataHandler = dataHandler;
}


Managed Extensibility Framework (MEF)


[Import]
IDataHandler<UserEntity> DataHandler { get; set; }

... or maybe you just want to new it up. It's up to you.

At this point, however, we have a problem. While I'm able to import my data handler and my objects, the RIA framework doesn't know much about my entities. This is because it depends on data annotations to get hints. With data annotations, I can specify which field is the key field for the entity. I can provide user friendly column names, hints, and descriptions, flag which fields are required and even provide validation.

Some shops may already use these annotations, as they are encapsulated in System.ComponentModel.DataAnnotations and are database and data-strategy independent. However, if you either haven't used these, or simply don't want to "dirty" your base domain models, there is a way to "cheat" a bit and get this to work.

Remember how I mentioned using virtual on your properties would come into play? Let's create a new class called UserEntityExtension. I'll go ahead and base it on my POCO class, but add some annotations.


public class UserEntityExtension : UserEntity
{
    public UserEntityExtension()
    {
    }

    public UserEntityExtension(UserEntity baseEntity)
    {
        if (baseEntity.ID > 0)
        {
            ID = baseEntity.ID;
        }
        UserName = baseEntity.UserName; 
        FirstName = baseEntity.FirstName;
        LastName = baseEntity.LastName;
        Email = baseEntity.Email;
        Reset();
    }

    [Key]
    [ReadOnly(true)]
    [Display(AutoGenerateField=false)]
    public override int ID
    {
        get
        {
            return base.ID;
        }
        set
        {
            base.ID = value;
        }
    }

    [Display(Name="Username")]
    [Required]
    [RegularExpression("^[A-Za-z0-9]+$", ErrorMessage="Please enter a valid user name using only alphanumeric characters without spaces.")]
    public override string UserName
    {
        get
        {
            return base.UserName;
        }
        set
        {
            base.UserName = value;
        }
    }

    [Display(Name="First Name")]
    public override string FirstName
    {
        get
        {
            return base.FirstName;
        }
        set
        {
            base.FirstName = value;
        }
    }

    [Display(Name="Last Name")]
    public override string LastName
    {
        get
        {
            return base.LastName;
        }
        set
        {
            base.LastName = value;
        }
    }

    [Display(Name="Email Address")]
    [RegularExpression(@"^([\w-\.]+)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.)|(([\w-]+\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(\]?)$", ErrorMessage="Please enter a valid email address.")]
    public override string Email
    {
        get
        {
            return base.Email;
        }
        set
        {
            base.Email = value;
        }
    }

    [Display(AutoGenerateField=false)]
    public override bool IsNew
    {
        get
        {
            return base.IsNew;
        }
    }

    [Display(AutoGenerateField=false)]
    public override bool IsDirty
    {
        get
        {
            return base.IsDirty;
        }
    }
}

As you can see, I'm simply passing through to the base properties, but annotating these with various tags. The constructor takes the base class and populates itself, making it easy to convert from the base type to the extended type. Now I'm ready to complete my domain service model:


[EnableClientAccess()]
public class LOBDomainService : DomainService
{
    public LOBDomainService()
    {
        // MEF set up goes here
    }

    [Import(AllowRecomposition=true)]
    IDataHandler<UserEntity> UserContext { get; set; }

    public IQueryable<UserEntityExtension> GetUsers()
    {
        return UserContext.List().ToList().ConvertAll(u => new UserEntityExtension(u)).AsQueryable(); 
    }

    public void InsertUser(UserEntityExtension entity)
    {
        UserContext.Save(entity);
    }

    public void UpdateUser(UserEntityExtension entity)
    {
        UserContext.Save(entity);
    }

    public void DeleteUser(UserEntityExtension entity)
    {
        UserContext.Delete(entity.ID);
    }
}

The Liskov Substitution Principle (LSP) states that "if S is a subtype of T, then objects of type T in a program may be replaced with objects of type S without altering any of the desirable properties of that program." In this case, we can safely pass the extended class to our data handler that only knows about the base class without worry.

On the Silverlight side, I can now wire up a fast grid and form to see the results of my hard work. I'll sneak into the Views/Home.xaml template and add the following references:


xmlns:ria="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Ria"
  xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Controls.Ria"
  xmlns:local="clr-namespace:LOBApp.Web.Services"
  xmlns:dataForm="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.DataForm.Toolkit"
  xmlns:dataGrid="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

Then, I'll wire in the data source, a grid, and a data form, like this:

 <ria:DomainDataSource AutoLoad="True" Name="userSource" QueryName="GetUsers">
                    <ria:DomainDataSource.DomainContext>
                        <local:LOBDomainContext/>
                    </ria:DomainDataSource.DomainContext>
                </ria:DomainDataSource>
          
                <dataGrid:DataGrid RowDetailsVisibilityMode="VisibleWhenSelected" SelectionMode="Single" AutoGenerateColumns="True" 
                                   ItemsSource="{Binding Path=Data, ElementName=userSource}">
                    <dataGrid:DataGrid.RowDetailsTemplate>
                        <DataTemplate>
                            <dataForm:DataForm x:Name="dataForm" AutoGenerateFields="True" CurrentItem="{Binding Path=DataContext, ElementName=dataForm}"/>
                        </DataTemplate>
                    </dataGrid:DataGrid.RowDetailsTemplate>
                </dataGrid:DataGrid>
 

With just that little bit of code, I can compile and generate and get something like this (notice it takes on the field names as well as validations, etc):

RIA Services using POCO

Obviously there's much more to do (we haven't touched security or made a submit button or added delete functionality, for example) but this should give you an idea of not only how powerful RIA services truly are, but also how there is plenty in place to enable you to leverage your existing architecture with the new model.

Jeremy Likness

Monday, November 23, 2009

Silverlight 3, RIA services, and Resource (RESX) Files

One of the advantages of using RIA services is that you can build your domain entity tier independently from the rest of the application tiers and then wire it in as needed, whether as LINQ or simple POCO classes.

If you decide to annotate your entities with resources, it can get a little dicey. Most examples online focus on resource files within Silverlight or entities defined within Silverlight. However, it is quite common to have existing POCO objects that point to a resource file. For example, I might declare a user entity like this:


public class User
    {
        [Key]
        [ReadOnly(true)]
        public int ID { get; set; }

        [Display(Name="UserName", Description="UserNameDescription", ResourceType=typeof(EntityResource))]
        [Required]
        [RegularExpression("^([A-Za-z0-9])+$", ErrorMessageResourceName="BadUserName", ErrorMessageResourceType=typeof(EntityResource))]
        public string UserName { get; set; }
    }

In this example, my resources are defined in the same project as the user entity, in a EntityResource.resx file.

When I build my DomainService to use this object, the first thing I get is an error related to accessing the resource file. I'm told it needs to be flagged as public. This is simple enough: I go into the designer and switch the access the modifier.

RIA Services with Resource File

Now we can compile again, but again, we get an error. This time it's in one of those auto-generated files that ends with .g and it says that my LOB.Entity.EntityResource type doesn't exist! What happened? I marked my domain service with EnableClientAccess, but the resource type doesn't make it to the Silverlight client.

It turns out the magic of RIA (for Silverlight 3) only goes so far ... we need to go ahead and manually bring the resource file over. Fortunately, we can do it in a way that prevents us from having to duplicate code.

In the silverlight project, figure out where you want the resource files to reside. I chose a folder called Links to keep all of my cross-linked resources in one place. On the folder, I right-click and choose "Add Existing Item." I then navigate to the resource file in my server side project, and add it ... as a link.

RIA Services with Resource File

Be sure to add the Designer.cs file as well, and you should be good.

Jeremy Likness

Saturday, November 21, 2009

Getting Silverlight Applications Ready for the Real World

As I continue to work on a reference project (basically building my portfolio site out in Silverlight) I keep going back to what it takes to put a Silverlight application "out there." When is it really "code complete?" There are tons of blog posts and articles about unit testing, frameworks, line of business, etc, but often I believe people miss some of the smaller points and nuances that go into making the application "application-ready." Here's a few tips and pointers that may help out.

Is SEO Important?

If it's an internal application, or even one hosted on the cloud that performs a business function and doesn't generate revenue via search engine discovery and click throughs, then search engine optimization is, of course, not much of a concern. What about if I'm presenting a portfolio or some other type of application that I want the search engines to uncover? Then we have to worry about it.

The introduction of deep-linking navigation certainly helps, but as someone pointed out in an earlier post of mine, search engines won't necessarily take the time to load XAP files, run them in a virtual environment, and see what they spit out. So we have to host the application creatively. Some shops will build two different versions. I'm working on an engine I think is a compromise, and am currently testing how the search engines like it. If it works, I'll blog about it ... if not? Back to the lab.

Essentially, you can host the website to parse out the current URL and present a set of default title, description, and other meta tags based on the URL. In other words, if I navigate to page.aspx#/Home I'll send out different tags and data than page.aspx#/Bio. The question is whether I need to generate this server side, or if I can parse it via javascript, update the tags, and the search engines will recognize the update. If I put a "site map" or legend on my pages that link to these "relative" nodes, the theory is that the engines will crawl those nodes and index at least the title and description for the content.

Most applications in Silverlight, however, function "inside the box." They don't rely on SEO. The only reason you use the deep linking and HTML DOM integration is to give the user consistent browser feedback and the ability to bookmark and return to pages.

Splash Page

It seems like a trivial step, but it is very important to promote your brand and create a seamless experience for the user. Mike Taulty has a nice video and rant in this post about the default progress animation. Take the time (about 2 minutes) to build a nice, branded experience for the user.

Here's a tip: I've seen lots of exotic ways people use to "test" their splash screen. Here's something I think will help, and will also give you good feedback for your application as a whole.

  1. Download Fiddler, a web-debugging proxy.
  2. Publish your website locally
  3. Run Fiddler. Go into Rules -> Performance and check "Simulate modem speeds" and "Disable caching"
  4. Run your application. Instead of localhost use your machine name. My laptop is called jeremyliknesspc so I navigate to http://jeremyliknesspc/testapp to test

That's it. Fiddler will slow down your connection enough that you can see the screen. What's better, if you are using dynamic module loading, you can also see how the application behaves when those modules take a long time to load. It might prompt you to add some nice progress indicators or other user feedback to manage the case when their connection is slow. We always test our applications locally (either on our machine or the local intranet) so it's very important to throttle the speed down and check out how it behaves in the worse case scenarios.

As an added bonus, you can start to see the internals of how the engine works: when it requests the XAP files, the additional modules, resources, etc. This will help you tune your application.

The Dead Plugin

It's going to happen. Believe it or not, as enthusiastic as we are about the way Silverlight has been growing, there will be end users who do not have the plugin. This is another branding choice in line with the splash screen. You can either give them a nice, boring, default logo provided by Microsoft, or you can spruce it up a bit. Connect with the consumer, explain why you are using Silverlight. Delight them with the benefits they'll receive after installing it and assure them it won't take long.

You can manipulate what's inside the object tag to your heart's desire. For the portfolio that I'm building, I welcomed the user, thanked them for visiting, then mentioned that as a Silverlight developer, I chose to build my portfolio in Silverlight. I indicated it is a safe plugin and that they simply need to click on the link to install it. I included the standard Microsoft icon and then put a little set of breadcrumbs linking to my relative pages beneath it.

Testing this is simple and easy, too. In Internet Explorer, you simply go into Tools -> Manage Add-ons. Navigate to Silverlight and set it to disabled. Refresh the page and see what the users will see when they don't have the plugin installed. (Re-enable it later, of course, or your Silverlight development career may end abruptly).

That's it: nothing earth-shattering or ground-breaking, but a little common sense to sprinkle along the way as we all forge ahead with our applications and anticipate how Silverlight 4 will change the landscape.

Jeremy Likness

Friday, November 20, 2009

Rich Data Forms in Silverlight 4 Beta

Silverlight 4 provides some very powerful data form capabilities out of the box. You will no longer have to throw an exception just to send a validation error or even worry about hard-coding labels. With support for data annotations, IDataErrorInfo and more, you now have plenty "out of the box" to work with.

Let's explore the surface of some of these powerful new features!

IDataErrorInfo

With support for IDataErrorInfo as well as INotifyDataErrorInfo, you no longer have to raise exceptions just to validate your classes. Fire up VS 2010 and create a new Silverlight 4 application. First, we'll create a "helper" class to derive our entities from in order to perform validation and notify property changes. The class looks like this:


public abstract class BaseEntity : INotifyPropertyChanged, IDataErrorInfo
{
    private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();

    protected void RemoveErrors(string prop)
    {
        if (_errors.ContainsKey(prop))
        {
            _errors.Remove(prop);
        }
    }

    protected void AddError(string prop, string error)
    {
        if (_errors.ContainsKey(prop))
        {
            _errors[prop].Add(error);
        }
        else
        {
            _errors.Add(prop, new List<string> { error });
        }
    }

    protected void OnPropertyChanged(string prop)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(prop));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public virtual string Error
    {
        get { return string.Empty; }
    }

    public virtual string this[string columnName]
    {
        get 
        {
            System.Text.StringBuilder retVal = new System.Text.StringBuilder();

            if (_errors.ContainsKey(columnName))
            {
                bool first = true;
                foreach (string error in _errors[columnName])
                {
                    retVal.Append(error);
                    if (first)
                    {
                        first = false;
                    }
                    else
                    {
                        retVal.Append(Environment.NewLine);
                    }
                }
            }

            return retVal.ToString();
        }
    }
}

There are a few things going on here. First, we implement INotifyPropertyChanged in to facilitate binding. The protected OnPropertyChanged method allows derived classes to hook into this event.

Next, we implement IDataErrorInfo. This forces us to provide a single error property as well as an extension method that references errors for a given property. John Papa has an excellent article about building a handler for this at Enabling Validation in Silverlight 4 with IDataErrorInfo. In my case, I keep a dictionary referenced by the property name that holds a list in case there are multiple errors I want to show. When the errors are requested, I fold them into a single string using Environment.NewLine (OK, so the beta just came out the other day ... can't say I've fully tested that part).

Data Annotations

Now you can build a PersonInfo class based on this base class. To keep things simple for a contrived example, I chose just a first name, last name, and age. Here we'll need to add a reference to System.ComponentModel.DataAnnotations. You must install the latest Silverlight Toolkit for this. If you need to browse to it manually, it's located under the toolkit installation directory (usually c:\Program Files\Microsoft SDKs\Silverlight) under v4.0\Libraries\Client. Now we can derive the class and annotate it, like this:


public class PersonInfo : BaseEntity 
{
    private string _firstName, _lastName;

    private int _age;

    [Display(Name="First Name")]
    [Required]
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            if (string.IsNullOrEmpty(value.Trim()))
            {
                AddError("FirstName", "First name is required.");
            }
            else
            {
                RemoveErrors("FirstName");
                if (!value.Equals(_firstName))
                {
                    _firstName = value;
                    OnPropertyChanged("FirstName");
                }
            }
        }
    }

    [Display(Name="Last Name")]
    [Required]
    public string LastName
    {
        get { return _lastName; }
        set
        {
            if (string.IsNullOrEmpty(value.Trim()))
            {
                AddError("LastName", "Last name is required.");
            }
            else
            {
                RemoveErrors("LastName");
                if (!value.Equals(_lastName))
                {
                    _lastName = value;
                    OnPropertyChanged("LastName");
                }
            }
        }
    }

    [Display(Name="Age (Years)")]
    [Required]
    public int Age
    {
        get { return _age; }
        set
        {
            if (value < 18 || value > 130)
            {
                AddError("Age", "Age must be a valid integer between 18 and 130.");
            }
            else
            {
                RemoveErrors("Age");
                if (!value.Equals(_age))
                {
                    _age = value;
                    OnPropertyChanged("Age");
                }
            }
        }
    }
}

You'll probably pull out validations into a handler / rule set but basically what I'm doing is validating the value, setting an error or clearing all errors if it passes, then calling the property changed event if the value truly changes. Note the use of annotations to specify required properties as well as "friendly names" for the properties.

Now that our class is prepped, we can get to work on the XAML.

Implicit Styles

With support for implicit styles, we can set a style based on a target type and style it that way - allowing for themes to be set externally. We'll take advantage of that to set the width for our TextBox controls. I also add a reference to the PersonInfo class to use in data binding, so I won't need to touch the code-behind for MainPage.xaml:


<UserControl.Resources>
   <local:PersonInfo x:Key="Person"/>
   <Style TargetType="TextBox">
      <Setter Property="Width" Value="200"/>
   </Style>
</UserControl.Resources>

Data Input Helpers

Next, add a reference to System.Windows.Controls.Data.Input (found in the toolkit as well, same folder as the data annotations). We'll reference it at the top of our XAML (along with the local reference we need for the PersonInfo class:


xmlns:local="clr-namespace:DataForm"
    xmlns:dataInput="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data.Input"

Great, now let's get to work! I'm going to show you the full snippet of XAML and then explain the pieces:


    <Grid x:Name="LayoutRoot" Background="White" DataContext="{Binding Source={StaticResource Person}}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>           
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="Auto"/>
        </Grid.ColumnDefinitions>
        <dataInput:Label Target="{Binding ElementName=tbFirstName}"
                         Grid.Row="0" Grid.Column="0"/>
        <StackPanel Orientation="Horizontal" 
                    Grid.Row="0" Grid.Column="1">
            <TextBox x:Name="tbFirstName" 
                 Text="{Binding Path=FirstName, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, FallbackValue=''}"/>
            <dataInput:DescriptionViewer Description="Please enter your first name."
                                         Target="{Binding ElementName=tbFirstName}"/>
        </StackPanel>
        <dataInput:Label Target="{Binding ElementName=tbLastName}"
                         Grid.Row="1" Grid.Column="0"/>
        <StackPanel Orientation="Horizontal" 
                    Grid.Row="1" Grid.Column="1">
            <TextBox x:Name="tbLastName" 
                 Text="{Binding Path=LastName, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, FallbackValue=''}" />
            <dataInput:DescriptionViewer Description="Please enter your last name."
                                         Target="{Binding ElementName=tbLastName}"/>
        </StackPanel>
        <dataInput:Label Target="{Binding ElementName=tbAge}"
                         Grid.Row="2" Grid.Column="0"/>
        <StackPanel Orientation="Horizontal" 
                    Grid.Row="2" Grid.Column="1">
            <TextBox x:Name="tbAge" 
                     Text="{Binding Path=Age, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True, FallbackValue='0'}"/>
            <dataInput:DescriptionViewer Description="Please enter a valid age between 18 and 130."
                                         Target="{Binding ElementName=tbAge}"/>
        </StackPanel>
        <dataInput:ValidationSummary Grid.Row="3" Grid.Column="0"
                                     Grid.ColumnSpan="2"/>        
    </Grid>

Some of these helper classes are made available via the toolkit for Silverlight 3 as well.

Labels

The label class lets us bind to an element like a textbox. It will pull the name of the databound property from the annotations and display it. If a validation error is thrown, it will turn red to further reinforce the error. If the field is tagged as required, the label will automatically bold itself to indicate a required field.

Learn more about the Label class

New Bindings

If you take a look at the textbox contorls, you'll see we've added new binding parameters. Specifically, ValidatesOnDataErrors (as opposed to exceptions) and FallbackValue (a value to use when databinding fails).

Learn more about bindings

Description Viewer

The description viewer control provides an easy way to show tips and hints and is similar to the ToolTip service. It will show an informational icon that, when the cursor hovers over, will provide a hint or description.

Learn more about the description viewer

Validation Summary

Finally, we add a validation summary control. The default behavior is to list all errors. When the user clicks on an error, it gives focus to the UI element that caused the error.

Learn more about the validation summary class

Putting it all Together

When you tie this all together and run it (without any additional code behind), this is an example of what you'll see:

Silverlight 4 Beta Data Forms

  • Note the labels automatically pulled from the DisplayAttribute on the entity class
  • The labels are bold due to the RequiredAttribute annotation
  • The labels on properties with errors are colored red
  • I clicked on the information glyph next to the first name text box and was the hint to enter my first name
  • I clicked on the red triangle in the upper right corner of the text box for age and was shown the error that my age is not in the valid range
  • The ValidationSummary control has summarized my errors (if I click on one, I'll be taken to the control with the issue)

As you can see, that's quite a bit of functionality right out of the box, and it allows a nice, clean separation of things like attributes and descriptions from the user interface that presents them.

Jeremy Likness

Thursday, November 19, 2009

Revisiting Lorem Ipsum in Silverlight 4 with RichTextArea and Printing

Earlier I explored some options to allow inline hyperlinks in Silverlight 3 using some extensions and the WrapPanel. In Silverlight 4, which was released as beta earlier this week, the problem is solved in the framework. Silverlight 4 gives us the RichTextArea control, which allows us to organize runs, spans, paragraphs, and even embed other UI elements to create a very richly illustrated document.

To illustrate this, I fired up a new Silverlight Application in Visual Studio 2010 and chose a Silverlight 4 application. Grabbing some more "lorem ipsum" text, I threw it into a RichTextArea control and then began embellishing with hyper links, formatting, and tossed in a red rectangle for good measure. The markup looks like this:


<RichTextArea TextWrapping="Wrap">
            <RichTextArea.Blocks>
                <Paragraph>Lorem ipsum dolor sit amet consectetur adipiscing elit.
                    <Bold>Suspendisse eu erat quis nibh laoreet hendrerit.</Bold>
                    <Italic>Tortor quam, auctor vel elementum et, rhoncus id augue.</Italic>
                    <Span Foreground="BlueViolet">Vestibulum nulla augue, dictum ut placerat et,</Span>
                    <Span>hendrerit a lorem. Vestibulum ante ipsum primis in</Span>
                    <Hyperlink NavigateUri="http://www.wintellect.com/"> faucibus orci luctus </Hyperlink>
                    <Run>et ultrices posuere cubilia Curae; Donec sollicitudin feugiat augue, eu 
                        vestibulum eros dapibus vel.
                    </Run>
                    <InlineUIContainer>
                        <Rectangle Width="20" Height="20" Stroke="Black" Fill="Red"/>
                    </InlineUIContainer>
                    Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.                  
                </Paragraph>
            </RichTextArea.Blocks>
            </RichTextArea>

As you can see, I am able to mix raw text, spans and runs that format colors or fonts, hyperlinks, and even use the InlineUIContainer to toss a rectangle into the mix. After building and publishing, this is what appears in the browser:

Rich text area in Silverlight 4 Beta

As you can see, a very serviceable rendering!

Next, we'll have a quick bit of fun. I add some row definitions and a header:


 <Grid.RowDefinitions>
             <RowDefinition Height="Auto"/>
             <RowDefinition/>
             <RowDefinition Height="Auto"/>
         </Grid.RowDefinitions>
         <TextBlock FontWeight="Bold" Grid.Row="0">Header

I wire the text area into row 1, then add this button to the last row:


  <Button Click="Button_Click" Content="Print" Grid.Row="2"/>

Do you get where I'm going? We don't want to print the header or the button, just the lorem ipsum section, so this is what we wire into the code behind:


public partial class MainPage : UserControl
{
    PrintDocument _pd; 

    public MainPage()
    {
        InitializeComponent();
        _pd = new PrintDocument();
        _pd.PrintPage += new EventHandler(pd_PrintPage);
    }

    void pd_PrintPage(object sender, PrintPageEventArgs e)
    {
        e.PageVisual = MainText;
    }

    private void Button_Click(object sender, RoutedEventArgs e)
    {
        _pd.DocumentName = "Lorem Ipsum";
        _pd.Print();
    }
}

Note that we have three steps:

  1. Create a printing surface
  2. Trigger the print by calling the print method (and optionally setting some parameters)
  3. Wiring into the print event and setting the visual to print only what we want (keep in mind we could have created an entirely new set of UI elements here).

The new page looks like this:

Silverlight 4 Beta Print Button

When I click "print" I'm given the familiar print dialog. I selected the XPS writer and saved it as an XPS document. Here is the final result:

Silverlight 4 Beta Page Printed from Browser

I'll continue to follow how Silverlight 4 makes our life easier by integrating into the framework what we used to have to build ourselves!

Jeremy Likness

Tuesday, November 17, 2009

Dynamic Module Loading with Silverlight Navigation using Prism

I started a project to update my personal website using Silverlight. The goal is to use Prism/Composite Application Guidance to build out a nice site that demonstrates some Silverlight capabilities and make the source code available as a reference. One of the first pieces I chose to tackle was ensuring I could facilitate deep linking using Silverlight navigation and still take advantage of dynamic module loading using the Prism framework.

The initial research I did was not promising. Most sites claimed that Prism broke the Silverlight navigation, while others had partial solutions that didn't really cut it for me. Mapping a separate view in the main module to have a named region for every dynamically loaded module seems to be overkill. So, I set out to see for myself if it could be done. The answer, of course, is yes!

While the project is a simple pair of pages, I've built in a few more advanced pieces of functionality to extend its richness as a reference project. In addition to dynamic loading the module for the "biography" page, I've trimmed the size of the XAP files, sprinkled in a little search engine optimization, and used the visual state manager to handle some animations to boot.

You can download the full source code by clicking here. For a working demo, click here. You can use a sniffer or proxy like Fiddler 2 to confirm for yourself that the Biography module doesn't load until you click that tab, BUT you can also navigate directly:

I do apologize in advance for the vanity built into the project ... it is a project with the goal of replacing my biography website so it made since to name it after, well, me.

The first step in creating a deep linking navigation website is to go ahead and create a Silverlight Navigation Application. I called mine JeremyLikness.Main and it gave me JeremyLikness.Web to host it. Go ahead and trash everything in the views folder but the error window (I like that functionality, some might find it annoying), leave the App pieces alone but rename the main page to Shell.xaml.

Here's when things start to get interesting. First, I've seen a lot of implementations that want the pure "my module gets added magically" functionality, so they do things like inject the link to the module when the module itself gets loaded, etc. This is all great but then they also stuff a view in the main application that maps one-to-one with the module, give a new named region per module, and leave me scratching my head wondering, "What did we gain?" To me, the main portion of my app is a single region that can have multiple modules inject their views into it, so that's how I approached this project.

I'm going to be a bit more pragmatic and go ahead and map my links in the main shell because I'm assuming right now the shell is like the overall "controller" and is aware of the other modules. It should, however, be very easy and minimal overhead for me to add a new module later on. I'm not going to worry about the modules injecting the links because then I have to load the module before I can show the link, and that defeats the purpose of dynamically loading the modules. Ideally, your browser doesn't fetch that extra 100K of XAP download if you don't care about reading my biography!

I end up keeping the generated code for the links (I'll go back and rearrange and style it later ... getting it functioning for me comes before making it pretty). I'm starting with two links, so that section looks like this:


 <Border x:Name="LinksBorder" Style="{StaticResource LinksBorderStyle}">
                <StackPanel x:Name="LinksStackPanel" Style="{StaticResource LinksStackPanelStyle}">

                    <HyperlinkButton x:Name="homeLink" Style="{StaticResource LinkStyle}" 
                                     NavigateUri="/Home" TargetName="ContentFrame" Content="Home"/>
          
                    <Rectangle x:Name="Divider1" Style="{StaticResource DividerStyle}"/>
     
                    <HyperlinkButton x:Name="Link2" Style="{StaticResource LinkStyle}" 
                                     NavigateUri="/Bio" TargetName="ContentFrame" Content="Bio"/>
                </StackPanel>
            </Border>

Not bad. Now we're going to start butchering the generated template, so bear with me.

Dynamic Modules

Setting up modules to be dynamic is fairly straightforward. While there are many ways to approach this, the method I used has two key features:

  1. I'm using a XAML configuration for the module, not programmatic, and
  2. to add the module in a way that Prism can dynamically load it, I add it as a Silverlight Application, not a Class Library

That's it! We're going to work a bit backwards and live without the project compiling for a bit until we get all of the pieces put into place. I know I'm going to have a Home and a Bio module. For the sake of simplicity and this example, I'm doing one view per module, but you could obviously do more. You'll see why this makes it easy for me in a minute. First, let's set up the XAML that will describe the modules.

Right click on the main project (the one with your shell) and add a new Resources File. Name it ModuleCatalog.xaml. Blow away everything in there and instead put in the Prism notation for a module, like this:


<m:ModuleCatalog xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:sys="clr-namespace:System;assembly=mscorlib"
                 xmlns:m="clr-namespace:Microsoft.Practices.Composite.Modularity;assembly=Microsoft.Practices.Composite">
    <m:ModuleInfoGroup Ref="JeremyLikness.ModuleHome.xap" InitializationMode="WhenAvailable">
        <m:ModuleInfo ModuleName="JeremyLikness.ModuleHome.InitModule"
                      ModuleType="JeremyLikness.ModuleHome.InitModule, JeremyLikness.ModuleHome, Version=1.0.0.0"/>
    </m:ModuleInfoGroup>
    <m:ModuleInfoGroup Ref="JeremyLikness.ModuleBio.xap" InitializationMode="OnDemand">
        <m:ModuleInfo ModuleName="JeremyLikness.ModuleBio.InitModule"
                      ModuleType="JeremyLikness.ModuleBio.InitModule, JeremyLikness.ModuleBio, Version=1.0.0.0"/>
    </m:ModuleInfoGroup>
</m:ModuleCatalog>
                 

I've purposefully set the home page to load when available and the bio to load on demand to show the difference between the methods ... I could just as easily load both modules on demand. This simply points to the class that defines the module, and the assembly to find it in. Prism will assume it's available for download in the same directly as the hosted XAP and kindly pull down the XAP when it's needed for us.

We're now referring to some modules we don't have yet. At this point, you could go ahead and create the shells for the applications. In order for the project to be set up correctly for dynamic loading, instead of adding a Silverlight Class Library, you're going to add a new Silverlight Application. Pick the same web page to host it but don't bother with the generation of a test page as it will load into the same test page we used for the main. I called my projects JeremyLikness.ModuleBio and JeremyLikness.ModuleHome.

Once the projects are created, blow away the all of the generated XAML files. You won't need the App object because this will be hosted in our main application. I simply added the references needed for Prism, then created a Views folder and added a Home.xaml in my home project and a Bio.xaml in my bio project. These are UserControl types, not pages. In the root, I created InitModule classes for both projects (note that I referenced this in the ModuleCatalog.xaml). The code looks almost identical - here is the bio:


namespace JeremyLikness.ModuleBio
{
    public class InitModule : IModule
    {
        private IRegionManager _regionManager;

        public InitModule(IRegionManager regionManager)
        {
            _regionManager = regionManager;
        }

        #region IModule Members

        public void Initialize()
        {
            _regionManager.RegisterViewWithRegion("ModuleRegion", typeof(Bio));
        }

        #endregion
    }
}

Simple: reference the region manager in the constructor. The Prism will use Unity to resolve the reference and inject it. On the Initialize method, we register our view with the region. The home project will register to the same region, but with typeof(Home) home instead.

Now we've got the catalog and the modules, what's next?

Bootstrapping

We need to tell the Prism framework how to "get started." This means a bootstrapper. Back to JeremyLikness.Main, I add a class called Bootstrapper and base it on UnityBootstrapper. I need to create the shell and set it as the root visual:


protected override DependencyObject CreateShell()
{
    Shell shell = Container.Resolve<Shell>();
    Application.Current.RootVisual = shell;
    return shell;
}

I also need to supply a module catalog. For this, I'll point to the XAML file we created earlier:


protected override Microsoft.Practices.Composite.Modularity.IModuleCatalog GetModuleCatalog()
{
    return Microsoft.Practices.Composite.Modularity.ModuleCatalog.CreateFromXaml(
        new Uri("JeremyLikness.Main;component/ModuleCatalog.xaml", UriKind.Relative));
}

You can see I'm telling it to look to the XAML to create the catalog, then specifying the path to it. The last thing I need to do is go into the App.cs code behind and change the application start up to just run the boot strapper:


private void Application_Startup(object sender, StartupEventArgs e)
{
   new Bootstrapper().Run();
}

Adapting to the Region

Prism works with defined regions that can host views. There are a number of different ways those regions can be implemented. I would ask that you refer to the Prism documentation for this, but basically something like a ContentControl can have a single view active at any given time, while something like a ItemsContentControl can hold multiple views. One source of confusion is what "active" really means. Active views doesn't necessarily mean "visible" or "hidden." I had two challenges for this application: the first was how to host the view, and the second was how to manage the visibility of the views. Ideally, something would hold all of the views as they are selected, then simply make them visible or invisible as they are selected/deselected.

We'll tackle visibility in a minute. ContentControl wasn't an option because it only holds one view at a time. The problem with ItemsControl was related to layout. As each view is injected, it takes up space in the container (this is the same whether it's a items control or a stack panel, etc). Even when I'd collapse the visibility of a control, the original space would still cause the other controls (views) to be shifted, which was not the effect I wanted. I needed something like a Grid where I could stack the views one on top of the other.

Fortunately, making your own type of region is easy. After reading John Papa's excellent post, I made a PrismExtensions folder and built my GridRegionAdapter to allow Prism to use a grid as a container. You can read his article to understand the how/why and see how the adapter is constructer. I had to revisit the boot strapper and tell Prism how to map the adapter to the grid:


protected override RegionAdapterMappings ConfigureRegionAdapterMappings() 
{ 
    RegionAdapterMappings mappings = base.ConfigureRegionAdapterMappings(); 
    mappings.RegisterMapping(typeof(Grid), Container.Resolve()); 
    return mappings; 
}

Now I was ready to give the solution a go!

The Problem with Navigation

The first iteration I tried was to declare a single page with the region adapter to process the requests. In my Views folder in the main project, I created a Silverlight Page (Page, not UserControl) and called it Module.xaml. The XAML for the module looked like this:


<navigation:Page x:Class="JeremyLikness.Main.Views.Module" 
           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:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
           xmlns:navigation="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
           xmlns:local="clr-namespace:JeremyLikness.Main.Views"
                 d:DesignWidth="640" d:DesignHeight="480"
           Title="Module Page">
    <Grid x:Name="MainGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" cal:RegionManager.RegionName="ModuleRegion">
    </Grid>
</navigation:Page>
      

Going back to the main shell, I mapped all requests to the module, like this:


<Border x:Name="ContentBorder" Style="{StaticResource ContentBorderStyle}">

    <navigation:Frame x:Name="ContentFrame" Style="{StaticResource ContentFrameStyle}" 
                      Source="/Home" Navigated="ContentFrame_Navigated" NavigationFailed="ContentFrame_NavigationFailed">
        <navigation:Frame.UriMapper>
          <uriMapper:UriMapper>
            <uriMapper:UriMapping Uri="" MappedUri="/Views/Module.xaml"/>
            <uriMapper:UriMapping Uri="/{pageName}" MappedUri="/Views/Module.xaml"/>
          </uriMapper:UriMapper>
        </navigation:Frame.UriMapper>
    </navigation:Frame>
</Border>

As you can see, pretty much every request is handled by the Module page. In the code behind, I had to work a little bit of magic to ensure the appropriate module would be loaded. First, I added a reference to the module manager:


public partial class Shell : UserControl
{
    private IModuleManager _moduleManager;

    public Shell(IModuleManager moduleManager)
    {
        _moduleManager = moduleManager;
        InitializeComponent();
    }

    private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
    {
    
        string module = e.Uri.ToString().Substring(1);
        string moduleName = string.Format("JeremyLikness.Module{0}.InitModule", module); 
       
        _moduleManager.LoadModule(moduleName);

...

}

The key here was getting the reference to the module manager, then calling the module load on the request. The module manager is smart enough to know when a module has already been loaded, so I didn't have to worry about that. The idea here is that when I request the "bio" tab, the string manipulation maps it to JeremyLikness.ModuleBio.InitModule, then calls the manager. The manager checks to see if it is loaded. If not, it will download the required XAP file and initialize the module. The module would then register the view with the region and it would magically appear!

There were two problems that arose immediately. The first issue was visibility: the different pages were stacking on each other. I'd load the application and see the home page, then click on the bio tab and watch the bio appear overlaid on top of the home page. Definitely not the right solution! We'll address visibility in a minute.

The second was a little more disturbing. The pages were behaving a little strangely so I fired up the debugger and found something disturbing: every time I would navigate to a link, the navigation framework would generate a new instance of the Module.xaml. This, in turn, would create new instances of the views. Just a few clicks of the tab and suddenly I had lots of instances hanging out that I just didn't need.

The solution for this was to create a static view that would live between navigation requests and hold the views as they are stacked in place. The first step was to build the container for the views. Under the Views folder, I created ViewContainer.xaml. The XAML contained the region mapping:


<UserControl x:Class="JeremyLikness.Main.Views.ViewContainer"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
    xmlns:cal="clr-namespace:Microsoft.Practices.Composite.Presentation.Regions;assembly=Microsoft.Practices.Composite.Presentation"
   >
    <Grid x:Name="LayoutRoot" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
          cal:RegionManager.RegionName="ModuleRegion">
       </Grid>
</UserControl>

The code behind implemented the singleton pattern:


public partial class ViewContainer : UserControl
{
    private static readonly ViewContainer _viewContainer = new ViewContainer();

    private ViewContainer()
    {
        InitializeComponent();
    }

    public static ViewContainer GetViewContainer()
    {
        if (_viewContainer.Parent != null)
        {
            Grid grid = _viewContainer.Parent as Grid;
            grid.Children.Remove(_viewContainer); 
        }

        return _viewContainer;
    }
}

Note that an element can only have one parent, and cannot be added as a child to another element. The GetViewContainer method first detaches the view from its previous parent before returning the instance so that the consumer of the method can use the orphaned view. I removed the region reference from the Module.xaml and added this to the code behind:


public Module()
{
    InitializeComponent();
    MainGrid.Children.Add(ViewContainer.GetViewContainer()); 
}

Now the navigation will still create a new instance of Module every time we navigate, but the module will simply reuse the same container for the views. This ensures that the views persist once they are added and don't have to be reloaded or recreated.

Next, I needed to manage showing and hiding the views so they don't remain stacked on top of each other.

Managing Visibility

To manage the views, I decided to create a service that the views will register to. The service is called when navigation takes place, and then calls back to the views to manage their visibility.

First, I created a new project that would be common across other projects. This was named, ironically, JeremyLikness.Common. First was an interface that the views could use. They provide a name for themselves as well as a method to call to either show or hide. This offloads the need for the view manager to understand how to show or hide a view ... since the view is a UserControl, it should have a pretty good idea.


public interface IPrismView
{
    void Show();

    void Hide();

    string ViewName { get; }
}

Next, I added a reference to the common project in both modules and implemented IPrismView. This is what the Bio.xaml.cs looked like:


#region IPrismView Members

public void Show()
{
    this.Visibility = Visibility.Visible; 
}

public void Hide()
{
    this.Visibility = Visibility.Collapsed;
}

public string ViewName
{
    get { return "Bio"; }
}

#endregion

Now I can create my view manager. In the same common project, I created a class called ActivationManager (continuing the Prism concept of "activating" a view although it is probably misnamed in retrospect). ActivationManager contains a collection of views. It allows a view to register with it, then exposes a method called SwapToView that iterates the views and calls their Show or Hide methods. It looks like this:


public class ActivationManager
{
    List<WeakReference> _views = new List<WeakReference>();

    public void Register(IPrismView view)
    {
        _views.Add(new WeakReference(view));
        view.Show();
    }

    public void SwapToView(string viewName)
    {
        foreach (WeakReference weakRef in _views)
        {
            if (weakRef.Target != null)
            {
                IPrismView view = (IPrismView)weakRef.Target;
                
                if (view != null)
                {
                    if (view.ViewName.Equals(viewName))
                    {
                        view.Show();
                    }
                    else
                    {
                        view.Hide();
                    }
                }
            }
        }
    }
}

Now we need to wire the activation manager in. We only need one copy, so in the boot strapper in the main project, I set up the instance:


protected override DependencyObject CreateShell()
{
    Container.RegisterInstance<ActivationManager>(
        Container.Resolve<ActivationManager>());
    Shell shell = Container.Resolve<Shell>();
    Application.Current.RootVisual = shell;
    return shell;
}

Notice I did this before creating the shell. There is a reason: we need the shell to coordinate the views. The shell has a method that fires when the navigation changes. If you recall, we used this method to tell the module manager to load the appropriate module. Now we'll need to pass in the activation manager and ask it to activate the views. These are the changes to the Shell code-behind:


private ActivationManager _activationManager;

public Shell(IModuleManager moduleManager, ActivationManager activationManager)
{
    _moduleManager = moduleManager;
    _activationManager = activationManager;
    InitializeComponent();
}

private void ContentFrame_Navigated(object sender, NavigationEventArgs e)
{

    string module = e.Uri.ToString().Substring(1);
    string moduleName = string.Format("JeremyLikness.Module{0}.InitModule", module); 
   
    _moduleManager.LoadModule(moduleName);

    _activationManager.SwapToView(module); 

    foreach (UIElement child in LinksStackPanel.Children)
    {
        HyperlinkButton hb = child as HyperlinkButton;
        if (hb != null && hb.NavigateUri != null)
        {
            if (hb.NavigateUri.ToString().Equals(e.Uri.ToString()))
            {
                VisualStateManager.GoToState(hb, "ActiveLink", true);
            }
            else
            {
                VisualStateManager.GoToState(hb, "InactiveLink", true);
            }
        }
    }
}

The last step is to take each module and pass the activation manager into the constructor so the view can register itself. This could probably be moved back into the module initialization (i.e. when the view is injected into the region) and perhaps even something extended on the framework to handle it. However, this works for now, using Bio.xaml.cs as our example:


public Bio(ActivationManager activationManager) : this()
{
    activationManager.Register(this);
}

Notice that I use this() to ensure it calls the default parameterless constructor to InitializeComponent etc etc.

At this point, I claimed victory. After compiling and publishing the project, I was able to navigate between the home and bio tabs, and watch the bio module loaded dynamically when I navigated to the tab. The views swapped in and out nicely. If you were reading this for dynamic module loading ... there it is!

To round out the example, I wanted to show two more things. First, I wanted to take the idea of Show and Hide a step further, and second, I don't believe navigation or deep linking makes sense to discuss unless we also cover search engine optimization.

Visual State Manager

The visual state manager is the key to managing visual states on controls (seems like it was well-named, huh?). I have to thank Justin Angel for pointing this out to me when I was doing a lot of behaviors and triggers for animations that truly belonged in the VSM instead. Really, when we're swapping our views, we are changing the state of the view ("show me" or "hide me"). If we do this programmatically, then we're stuck with the attributes we set. However, if we do this through the VSM, then all of the the transition becomes customizable through the designer and in Expression Blend, so our designers can then tinker with the transitions without touching the code behind.

To demonstrate this, I decided to take both views and give them a ShowState and a HideState. To show the power of the Visual State Manager, I created two different transitions: the home page will explode in using a scale transform, while the biography page will fade in using an opacity animation.

Because there are a million examples that show how to add custom view states using Blend, I decided to document the code behind approach. The main container in our views is a grid, so that's what we're going to manipulate. I want the view to hide immediately, but when it goes to show, it should have a nice animation. This is what the Bio.xaml ends up looking like:


<Grid x:Name="LayoutRoot" Background="White">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="VisualStates">
                <VisualState x:Name="ShowState">
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:01.0000000" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Opacity)">
                            <EasingDoubleKeyFrame KeyTime="00:00:01" Value="1"/>
                        </DoubleAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:01.0000000" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Visibility)">
                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Visible</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="HideState">
                    <Storyboard>
                        <DoubleAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:00.0000100" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Opacity)">
                            <EasingDoubleKeyFrame KeyTime="00:00:00" Value="0"/>
                        </DoubleAnimationUsingKeyFrames>
                        <ObjectAnimationUsingKeyFrames BeginTime="00:00:00" Duration="00:00:01.0000100" Storyboard.TargetName="LayoutRoot" Storyboard.TargetProperty="(UIElement.Visibility)">
                            <DiscreteObjectKeyFrame KeyTime="00:00:00">
                                <DiscreteObjectKeyFrame.Value>
                                    <Visibility>Collapsed</Visibility>
                                </DiscreteObjectKeyFrame.Value>
                            </DiscreteObjectKeyFrame>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <TextBlock>This is my bio. It tells people about me and my background.</TextBlock>
    </Grid>

So there are a few things going on here. First, we define a "group" of visual states that I call, sure enough, VisualStates. I am defining two states: a HideState and a ShowState. The hide state has an animation with a zero duration and simply sets the Opacity to 0. The show state animates for a second (you'd normally make this faster, but it helps demonstrate my point) and fades in. While many people are used to the color and double animations, did you know there existed an object animation as well? This allows you to manipulate almost any property on your target control using the visual state. In this case, I won't even have to set the Visibility property in my code behind - I simply define the value in the view state! Notice in the ShowState that the property is set immediately so it becomes visible in time for the animation to fire and fade it in.

So now we simply go to the code-behind for our control and set the state. The VSM expects a control when you set the state, but it will traverse the hierarchy and find the state you specify. So while we set the state on our control, it will use the state definitions that are contained within the grid object. We also make sure we set a default state in the constructor. In this case, it will be hidden because the activation manager will call show when it is registered. This is what our new code-behind looks like:


public partial class Bio : UserControl, IPrismView
{
    public Bio()
    {
        InitializeComponent();
        VisualStateManager.GoToState(this, "HideState", false);
    }

    public Bio(ActivationManager activationManager) : this()
    {
        activationManager.Register(this);
    }

    #region IPrismView Members

    public void Show()
    {
        VisualStateManager.GoToState(this, "ShowState", true);
    }

    public void Hide()
    {
        VisualStateManager.GoToState(this, "HideState", true);
    }

    public string ViewName
    {
        get { return "Bio"; }
    }

    #endregion
}

As you can see, we simply move to a new state and leave it up to the designer to add any fancy animations or other transitions when that happens.

Getting Along with Search Engines

The final piece here is to get along with search engines. Deep linking is nice if you just want to add it to your favorites and jump right in, but doesn't really do much for search engines when they are scanning the page. The search enginges need to have some points like a page title and description to properly index your pages.

In the common project, I created a class called SEOHelper to contain my title, description, and keywords. It exposes a method called PageUpdate that then parses these values into the HTML page. Let's take a look at the class:


public class SEOHelper
{
    private const string TEMPLATE = "metatags = document.getElementsByTagName(\"meta\");"
        + "for (x=0;x<metatags.length;x++) {{"
        + "var name = metatags[x].getAttribute(\"name\");"
        + "if (name == '{0}'){{"
        + "var content = metatags[x].getAttribute(\"content\");" 
        + "metatags[x].setAttribute('{0}','{1}');break;}}}}";

    private List<string> _keywords = new List<string>();

    public string Title { get; set; }

    public string Description { get; set; }

    public void AddKeyword(string keyword)
    {
        _keywords.Add(keyword);
    }

    public void UpdatePage()
    {
        HtmlPage.Document.SetProperty("title", Title);

        string titleSet = string.Format(TEMPLATE, "title", Title);
        HtmlPage.Window.Eval(titleSet);

        string descriptionSet = string.Format(TEMPLATE, "description", Description);
        HtmlPage.Window.Eval(descriptionSet);

        if (_keywords.Count > 0)
        {
            StringBuilder keywordList = new StringBuilder();
            bool first = true;
            foreach (string keyword in _keywords)
            {
                keywordList.Append(keyword);
                if (first)
                {
                    first = false;
                }
                else
                {
                    keywordList.Append(",");
                }
            }
            string keywordSet = string.Format(TEMPLATE, "keywords", keywordList);
            HtmlPage.Window.Eval(keywordSet);
        }
    }
}

I'm not a big fan of burying JavaScript inside of C# classes, so I included it here only to help keep the solution in one place. Ordinarily I'd have some methods defined in a .js file that this would link to.

Setting the title is a call into the document object. Setting the meta tags is a little more convuluted. You could create the tags but in this example, I'm stubbing out empty title, description, and keywords meta-tags, then finding them and updating the content attribute. The TEMPLATE contains the code to iterate the tags, then update the content attribute. The PageUpdate method simply updates the meta tag name and content then asks the page to evaluate the javascript.

Now we just place the object inside of the appropriate view and call it when the view becomes visible. Here is the new Home.xaml.cs:


public partial class Home : UserControl, IPrismView
{
    SEOHelper _helper = new SEOHelper { Title = "Home Page", Description="Home page for the website." }; 

    public Home()
    {
        InitializeComponent();
        VisualStateManager.GoToState(this, "HideState", false);
        _helper.AddKeyword("Home");
        _helper.AddKeyword("SEO"); 
    }

    public Home(ActivationManager activationManager)
        : this()
    {
        activationManager.Register(this);
    }

    #region IPrismView Members

    public void Show()
    {
        VisualStateManager.GoToState(this, "ShowState", true);
        _helper.UpdatePage();
    }

    public void Hide()
    {
        VisualStateManager.GoToState(this, "HideState", true);
    }

    public string ViewName
    {
        get { return "Home"; }
    }

    #endregion
}

As you can see, the helper takes in the settings for the page, then whenever the view is shown (the Show) method, the helper is called to update the page. For testing, this could be abstracted even further to a ISEOHelper to avoid trying to actually access the html page when its not needed.

Important Note: as pointed out by Alexey Zakharov in the comments, most search engines won't be able to fire up the Silverlight plug-in and run the XAP to even affect the page to "see" the meta data. To be able to truly have it all indexed, you'll still either need to have a separate page per link (which isn't too efficient with XAP files loading) or have a JavaScript stub independent of the Silverlight plugin that updates this information ... the search engines DO understand javascript for the most part. Thanks for that and I'll probably blog a post about it later, unless I suddenly start seeing my content show up on Google links.

Trimming the XAP Size

Last but not least is trimming the XAP size. I put most of my references in the main XAP file, so they aren't needed in the modules. By going to the project properties, I can check "Reduce XAP size by using appliciation library caching." This will create a new ZIP file that contains some of the referenced controls on my modules when they build, and not include them in the main XAP. Fortunately, these are already loaded by the main XAP, so there is nothing more to do when the XAP is loaded.

There it is ... I know this was a long blog post for what, in the end, is a simple page with two tabs. However, I hope this has proven that you can combine Prism with Silverlight Navigation and use dynamic module loading will keeping your pages search engine friendly. The VSM piece might have made sense in a separate post but I also wanted to get a good example out there that shows how to wire it in when you're not making a custom control or using Blend. Enjoy!

Download the source

View the demo

Jeremy Likness

Friday, November 13, 2009

Inline Hyperlinks in Silverlight 3 (Equivalent of A HREF)

One common complaint I see regarding Silverlight is the inability to include inline hyperlinks. While Silverlight does provide a HyperlinkButton, you cannot do something simple like:


This is some text. This is <a href="http://www.wintellect.com/">a hyperlink</a>, in case you were wondering. 

There are a few solutions available via third-party controls, but it's a good exercise to understand the underlying fundamentals of the rendering engine, controls, and objects that make up a TextBlock. In this article, we'll set out on a quest to be able to take a simple set of text, formatted for a text block, and generate inline links.

The first step is to simply open your solution, start a new Silverlight 3 application, and get rolling with some text. The easiest way to grab text is to visit Lorem Ipsum, where you can request however much text you need and it will generate the paragraphs for you. I just grabbed a few sentences and set the width of my control to 200 so I could show the text wrapping. The final markup looks like this (I called my project "SLHyperlink"):

<UserControl x:Class="SLHyperlink.MainPage"
    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" d:DesignWidth="640" d:DesignHeight="480"
   Width="200">
      <Grid x:Name="LayoutRoot">
        <TextBlock TextWrapping="Wrap">Lorem ipsum dolor sit amet, consectetur adipiscing elit. 
            Aenean eget turpis id purus tempor tincidunt porttitor eu mi. 
            Vestibulum tincidunt odio quis nibh feugiat faucibus. 
            Fusce rhoncus tristique mi non posuere. 
            Nunc sit amet velit magna.
  </Grid>
</UserControl>

When I run the solution, this is what appears:

Lorem Ipsum

Now, we can get a little fancier. If you are familiar with the TextBlock control, you know you can mix and match different styles of text within the control, and even provide line breaks. You do this using the Run and the LineBreak markup. Let's spruce up the text a bit. I made the font size larger, added a few runs and a line break, and changed the width of the control to 300 to accommodate the larger text:


<TextBlock TextWrapping="Wrap" FontSize="20"><Run FontFamily="Times New Roman">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</Run> 
            Aenean eget turpis id purus tempor tincidunt porttitor eu mi. 
            <Run Foreground="Cyan">Vestibulum tincidunt odio quis nibh feugiat faucibus.</Run> 
            Fusce rhoncus tristique mi non posuere.<LineBreak/>
            Nunc sit amet velit magna.</TextBlock>

The result is this:

Lorem Ipsum

Anatomy of a TextBlock

Now that we have the inline text, we can start to dissect the classes. There are a few tools at our disposal. First, we can go into the Object Browser in Visual Studio and search for TextBlock. We find out that it inherits directly from FrameworkElement and is, unfortunately, a sealed class, so we cannot create our own dervied class. There are plenty of text-related dependency properties (font size, font family, font weight, etc) and then a few interesting methods like OnCreateAutomationPeer and the collection of Inlines.

Running RedGate's free .Net Reflector tool, we can generate the shell of the class:


[ContentProperty("Inlines", true)]
public sealed class TextBlock : FrameworkElement
{
    // Fields
    private FontSource _fontSource;
    public static readonly DependencyProperty FontFamilyProperty;
    public static readonly DependencyProperty FontSizeProperty;
    public static readonly DependencyProperty FontStretchProperty;
    public static readonly DependencyProperty FontStyleProperty;
    public static readonly DependencyProperty FontWeightProperty;
    public static readonly DependencyProperty ForegroundProperty;
    private static readonly DependencyProperty InlinesProperty;
    public static readonly DependencyProperty LineHeightProperty;
    public static readonly DependencyProperty LineStackingStrategyProperty;
    public static readonly DependencyProperty PaddingProperty;
    public static readonly DependencyProperty TextAlignmentProperty;
    public static readonly DependencyProperty TextDecorationsProperty;
    public static readonly DependencyProperty TextProperty;
    public static readonly DependencyProperty TextWrappingProperty;

    // Methods
    static TextBlock();
    public TextBlock();
    internal override string GetPlainText();
    protected override AutomationPeer OnCreateAutomationPeer();
    private void UpdateFontSource(FontSource fontSource);

    // Properties
    public FontFamily FontFamily { get; set; }
    public double FontSize { get; set; }
    public FontSource FontSource { get; set; }
    public FontStretch FontStretch { get; set; }
    public FontStyle FontStyle { get; set; }
    public FontWeight FontWeight { get; set; }
    public Brush Foreground { get; set; }
    public InlineCollection Inlines { get; }
    public double LineHeight { get; set; }
    public LineStackingStrategy LineStackingStrategy { get; set; }
    public Thickness Padding { get; set; }
    public string Text { get; set; }
    public TextAlignment TextAlignment { get; set; }
    public TextDecorationCollection TextDecorations { get; set; }
    public TextWrapping TextWrapping { get; set; }
}

What's interesting for us is the Inlines collection. In Silverlight, the System.Windows.Documents.Inline class is summarized as "An abstract class that provides a base for all inline flow content elements." There are exactly two classes that inherit from them, and both of them are sealed: Run and LineBreak. The Run class simply contains a text property:


[ContentProperty("Text", true)]
public sealed class Run : Inline
{
    // Fields
    private static readonly DependencyProperty TextProperty;

    // Methods
    static Run();
    public Run();

    // Properties
    public string Text { get; set; }
}

It turns out if you give your TextBlock a name (x:Name="tbSomething") and then debug, you'll find that the text we entered inside the TextBlock is turned into a collection. The free text goes into a run without additional attributes, so the collection is of runs and line breaks. Not only are these classes sealed, but they inherit directly from DependencyObject, so they are not FrameworkElement (or even UIElement) derived. This means we can't do things like check mouse events or bind to click events.

Instead of creating our own text block control, it appears that the existing has everything we need except the actual hyperlink. After brainstorming for a bit, I decided the easiest way to implement this would be to translate the text block into a wrap panel. A wrap panel does what we want: it allows elements of various sizes to stack together (like text flowing together). If we want an inline hyperlink, we simply need to stack a text block, followed by a navigation button for the hyperlink, followed by another text block. We can separate out the hyperlink by placing it in its own run.

An Extended Run

Even though our Run class is sealed, it derives from Inline which, in turn, is a DependencyObject, so we are allowed to generate attached properties.

The first thing I did was create a static class called RunExtender and add an attached property for a NavigateUrl. This is what I can attach to the run that will become a hyperlink. The code looks like this:


public static class RunExtender 
{  
    public static Uri GetNavigateUrl(DependencyObject obj)
    {
        return (Uri)obj.GetValue(NavigateUrlProperty);
    }
    
    public static void SetNavigateUrl(DependencyObject obj, Uri value)
    {
        obj.SetValue(NavigateUrlProperty, value); 
    }

    public static readonly DependencyProperty NavigateUrlProperty =
        DependencyProperty.RegisterAttached("NavigateUrl",
                                    typeof(Uri),
                                    typeof(RunExtender),
                                    null);
}

Now I can add an inline hyperlink and add the attached navigation property. This won't change anything yet, but it sets us up for the next iteration.

First, I add the namespace to the top of the control:


xmlns:local="clr-namespace:SLHyperlink"

Next, I add some more text to the text block and decorate it with the new attached property:


<TextBlock TextWrapping="Wrap" FontSize="20"><Run FontFamily="Times New Roman">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</Run> 
            Aenean eget turpis id purus tempor tincidunt porttitor eu mi. 
            Here is an <Run local:RunExtender.NavigateUrl="http://www.wintellect.com/">inline hyperlink</Run>.
            <Run Foreground="Cyan">Vestibulum tincidunt odio quis nibh feugiat faucibus.</Run> 
            Fusce rhoncus tristique mi non posuere.<LineBreak/>
            Nunc sit amet velit magna.</TextBlock>

The generated output isn't interesting yet, but no errors so far:

Lorem Ipsum

Warping the WrapPanel

Now things are getting interesting. What I want to do is make my text block a resource, then move it into a wrap panel. Attached properties come to our rescue again, because I can create an attached property for a wrap panel that points to my text block resource. When it is attached, we'll parse the inline collection and build out a new set of children for the wrap panel to render.

We're going to have to move a lot of values from the reference text block to our targets in the wrap panel. The first thing I did was to add some static collections of attributes for the text block, inline element, and a generic control (in the case of the hyperlink button we'll generate). I also want to clean the text as we move it from one to the other, so I set up a regular expression to help strip whitespace. This block looks like:


private static FieldInfo[] _tbInfo = typeof(TextBlock).GetFields(BindingFlags.Static | BindingFlags.Public);
private static FieldInfo[] _rInfo = typeof(Inline).GetFields(BindingFlags.Static | BindingFlags.Public);
private static FieldInfo[] _cInfo = typeof(Control).GetFields(BindingFlags.Static | BindingFlags.Public);
private static readonly Regex _reg = new Regex(@"\s+");

Notice we are just getting the public and static properties from the types.

The clone method I created could probably be refactored and cleaned up several times over, but it does the trick. I want to be able to transfer properties from a source to a target. If the source and target are the same type, we can reference the same dependency properties. If they are different types, we'll need to lookup the dependency property on the target that has the same name as the dependency property on the source. Take a peek at this:


private static void _Clone(DependencyObject src, DependencyObject tgt, FieldInfo[] fi)
{
    _Clone(src, tgt, fi, null);
}

private static void _Clone(DependencyObject src, DependencyObject tgt, FieldInfo[] fi, FieldInfo[] fiTgt)
{
    for (int i = 0; i < fi.Length; i++)
    {
        DependencyProperty dp = fi[i].GetValue(src) as DependencyProperty;

        bool eligible = dp != null;

        if (eligible && src is TextBlock)
        {
            eligible = fi[i].Name.StartsWith("Font") || fi[i].Name.StartsWith("Line") || fi[i].Name.StartsWith("TextAlignment")
                || fi[i].Name.StartsWith("TextWrapping") ||
                (!(tgt is HyperlinkButton) && fi[i].Name.StartsWith("Foreground"));
        }
        else if (eligible && src is Run) 
        {
               eligible = fi[i].Name.StartsWith("Font") || fi[i].Name.StartsWith("Foreground");
        }

        if (eligible)
        {
           DependencyObject obj = src.GetValue(dp) as DependencyObject;
            if (obj != null)
            {
                if (fiTgt == null)
                {
                    tgt.SetValue(dp, obj);
                }
                else
                {
                    FieldInfo fNew = null;
                        foreach (FieldInfo fInfo in fiTgt)
                        {
                            if (fInfo.Name.Equals(fi[i].Name))
                            {
                                fNew = fInfo;
                                break;
                            }
                        }
                        if (fNew != null)
                        {
                            DependencyProperty dpTgt = fNew.GetValue(tgt) as DependencyProperty;
                            tgt.SetValue(dpTgt, obj); 
                        }
                }
            }
            else
            {
                
                    if (fiTgt == null)
                    {
                        tgt.SetValue(dp, src.GetValue(dp));
                    }
                    else
                    {
                        FieldInfo fNew = null;
                        foreach (FieldInfo fInfo in fiTgt)
                        {
                            if (fInfo.Name.Equals(fi[i].Name))
                            {
                                fNew = fInfo;
                                break;
                            }
                        }
                        if (fNew != null)
                        {
                            DependencyProperty dpTgt = fNew.GetValue(tgt) as DependencyProperty;
                            tgt.SetValue(dpTgt, src.GetValue(dp)); 
                        }
                    }
            }
        }
    }
}

(Yes, I know there is some duplicated code that could be pulled out into another method)

So we essentially have our source and target, and the collection that contains the fields and properties. I only send a second collection in if the target is different.

We begin by iterating all properties on the source. We need to make sure we get a valid reference. I then filter the properties to only move what I'm interested in. For the text block, it's the font, line, and foreground properties, as well as the text alignment and text wrappig. I don't want, for example, to get the "Height" property (we'll let the engine size it for us) and I certainly don't want to move the "Text" property (we'll be iterating the collection of inlines for that).

For the run, it's just font information and the foreground.

If the value is a nested object (for example, a brush), we simply set the value on the target. If the target is a different type, I first iterate the target list of properties to find the property with the same name, then set the object to that.

If it's not a nested object, I get the value, then set the value on the target. Again, if the target is a different type, I have to find the corresponding dependency for the target and then set the value on that.

Now that we've got some nice helpers in place, we can set up a new attached property that points to a text block. Remember, my goal is to take that in, parse it, and output elements into a wrap panel.

Here is the setup:


public static TextBlock GetTargetTextBlock(DependencyObject obj)
{
    return (TextBlock)obj.GetValue(TargetTextBlockProperty);
}

public static void SetTargetTextBlock(DependencyObject obj, TextBlock value)
{
    obj.SetValue(TargetTextBlockProperty, value); 
}

public static readonly DependencyProperty TargetTextBlockProperty =
    DependencyProperty.RegisterAttached("TargetTextBlock",
                                typeof(TextBlock),
                                typeof(RunExtender),
                                new PropertyMetadata(null, new PropertyChangedCallback(OnTargetAttached)));

And now it's time to do the actual parsing. What I want to do is take each run and make it either a new text block (we are mapping to multiple text blocks because the wrap panel is going to wrap on the outermost container ... if we put all the runs inside a single text block, it will only wrap in the text block and not in the context of the wrap panel container) or a hyperlink button. The hyperlink button will take on the attributes of the text block except the foreground (we'll keep the default of that being blue to show that it's a link). All of these will become children of the new wrap panel.

Don't forget that the wrap panel exists in the toolkit that you can download here. We simply need to add a reference to System.Windows.Controls.Toolkit in our project.

Here's the code:


public static void OnTargetAttached(object obj, DependencyPropertyChangedEventArgs args)
{
    WrapPanel panel = obj as WrapPanel;
    if (panel != null)
    {
        TextBlock src = args.NewValue as TextBlock;
        if (src != null)
        {
            foreach (Inline inline in src.Inlines)
            {
                if (inline is LineBreak)
                {
                    TextBlock newBlock = new TextBlock();
                    newBlock.Inlines.Add(new LineBreak());
                    panel.Children.Add(newBlock); 
                }
                else if (inline is Run)
                {
                    Run run = inline as Run;
                    Uri navigateUri = run.GetValue(NavigateUrlProperty) as Uri;
                    if (navigateUri != null)
                    {
                       HyperlinkButton button = new HyperlinkButton();
                        button.Content = run.Text;
                        button.NavigateUri = navigateUri;
                        _Clone(src, button, _tbInfo, _cInfo); 
                        panel.Children.Add(button);
                    }
                    else
                    {
                        Run newRun = new Run { Text = _reg.Replace(run.Text.Replace('\n',' ')," ").Trim() };
                        _Clone(run, newRun, _rInfo);
                        TextBlock newBlock = new TextBlock();
                        _Clone(src, newBlock, _tbInfo);
                        newBlock.Inlines.Add(newRun);
                        panel.Children.Add(newBlock);
                    }
                }
            }
        }
    }
}

Now that we've got that all in place, it's time to update our main page. First, we'll add a reference to the toolkit:


xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"

Next, we pull our text block into the resources section and give it a key. This makes it a resource now instead of an inline text block:


<UserControl.Resources>
        <TextBlock x:Key="tb" TextWrapping="Wrap" FontSize="20"><Run FontFamily="Times New Roman">Lorem ipsum dolor sit amet, consectetur adipiscing elit.</Run> 
            Aenean eget turpis id purus tempor tincidunt porttitor eu mi.<LineBreak/> 
            Here is an <Run local:RunExtender.NavigateUrl="http://www.wintellect.com/">inline hyperlink</Run>.
            <Run Foreground="Cyan">Vestibulum tincidunt odio quis nibh feugiat faucibus.</Run> 
            Fusce rhoncus tristique mi non posuere.<LineBreak/>
            Nunc sit amet velit magna.</TextBlock>
    </UserControl.Resources>

In our main grid, instead of the text block, we'll now use a wrap panel and set the text block as the target:


<Grid x:Name="LayoutRoot">
   <controls:WrapPanel local:RunExtender.TargetTextBlock="{StaticResource tb}"/>
</Grid>

Now we run it, and this is what we get:

Lorem Ipsum

It's the same text as before, but with a nice, clickable hyperlink. Clicking the hyperlink validates that it does indeed take us to the Wintellect home page.

Now you can add inline hyperlinks to your heart's desire simply by decorating the run and using the wrap panel.

There are several third party controls available that help with rendering HTML and hyperlinks that you can use, but I always believe it's helpful to understand the how and why. Hopefully this exercise assisted you with a better understanding of dependency objects and properties and more specifically the text rendering functionality that is available out of the box with Silverlight 3.

You may download this example here.

Jeremy Likness