Browse by Tags

All Tags » expression blend   (RSS)

When building large Silverlight applications, it makes sense to build sets of styles that can be shared across the application. Many examples on the web illustrate placing these in the App.xaml file and then you are off to the races. Unfortunately, when you are building modular applications, it's not that simple. The dependent modules still require design-time friendly views, and to make that happen requires a few steps.

The Central Theme

The first step is to take your theme for the application and place it in a separate project. These are themes that only have dependencies on the core Silverlight controls. What you want is a simple project that any application or module can reference to pull in the theme.

Create a new Silverlight class library, then add a ResourceDictionary to house your themes. In larger projects, it makes sense to break out themes into smaller pieces, like this:

Resource Dictionaries

Then, you can aggregate these into your main dictionary, called Theme.xaml or something similar. It is important that you load the child dictionaries in order. For example, building blocks like colors and gradients will come before more complicated control templates that use the colors and gradients. Your Theme.xaml might look like this:

<ResourceDictionary
   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">

    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="ColorStyles.xaml" />
        <ResourceDictionary Source="BrushStyles.xaml" />
        <ResourceDictionary Source="TextStyles.xaml" />
        <ResourceDictionary Source="ButtonStyles.xaml" />
        <ResourceDictionary Source="ComboBoxStyles.xaml" />
    </ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

A Point of Reference

As you build your styles, you may end up including references to things like the Silverlight Toolkit or some third-party set of custom controls. This is fine, but you may be surprised when you fire up your project and suddenly the application crashes because it's unable to resolve any styles that have prefixed targets (i.e. data:). The reason has to do with how Silverlight resolves and evaluates references.

The easiest way to manage this is simple:

In your theme project, for every reference you add, right click and choose copy local: false. The references will work, but they won't be associated with that project. In your main project, the one that contains the App.xaml, add every reference that the theme depends on (and leave them as copy local: true). This ensures the DLLs are available when the theme is pulled down.

Setting up the Main Module

In the main module that drives your application, the one with the App.xaml, you can simply reference the theme project and then merge the resources in. Your App.xaml will contain something like this:

<Application.Resources>
    <ResourceDictionary>
        <ResourceDictionary.MergedDictionaries>                                
            <ResourceDictionary Source="/MyApp.MyThemeProject;component/Theme.xaml"/>
        </ResourceDictionary.MergedDictionaries>
    </ResourceDictionary>
</Application.Resources>

The path is known as pack format in its abbreviated form. The first part is the full name of the assembly that the theme resides in. The second part always has component/ followed by the path to your main theme. In this case, ours is in the root. This is all it takes to pull that theme into your application.

Dynamically Loaded Themes

What if you are trying to cut down on load time, so your main application (the one with App.xaml), doesn't have a reference to the theme or the dependent assemblies? This is often the case because the control libraries are rather large. You won't be able to merge the theme in the App.xaml because the required references don't exist.

The solution is to move all of the theme logic to the main module you load after your startup module. A common pattern is to have a small "stub" that is the root application, then show a friendly message to the user while you load the main application components.

First, move the reference to the theme to the module you are dynamically loading. Second, add the dependent references (such as the toolkit) to that module instead of your main module. Finally, when the module is loaded, you'll have to perform some magic to get the theme into the application.

Once the dynamic module is loaded, you will execute this code to pull the theme into the application:

const string MAINTHEME = "/MyApp.MyThemeProject;component/Theme.xaml";
var rd = new ResourceDictionary
                         {
                             Source = new Uri(MAINTHEME, UriKind.Relative)
                         };
Application.Current.Resources.MergedDictionaries.Add(rd);

That uses the same pack notation but programmatically loads the theme into the application-level resources.

Get by with a Little Help from my Blend

Finally, there is one more hurdle to jump. When you create your dynamic modules, you can reference the theme to your heart's content, but the designer isn't going to know about it. Both Blend and Cider (the built-in designer with Visual Studio 2010) rely on hints such as the App.xaml to find themes. Modules don't have their own App.xaml, so the theme isn't found.

If you have a solution for making this work in Cider, let me know - I haven't found anything satisfactory, but I do know what works in Blend.

First, the latest Blend will often come to your rescue the first time you edit the item in Blend. When it finds a missing style, it will prompt you with the following dialog:

Blend Dialog

From there, you can pick which resource dictionaries from which assemblies to use in the designer.

Behind the scenes, Blend creates a file called DesignTimeResources.xaml under the Properties folder. You can create this file yourself if you wish to be proactive about integrating your themes in the designer.

The contents simply contain the now-familiar merged dictionaries that you wish to have available:

<ResourceDictionary
 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <ResourceDictionary.MergedDictionaries>
        <ResourceDictionary Source="/MyApp.MyThemeProject;component/Theme.xaml"/>
    </ResourceDictionary.MergedDictionaries>
    <!-- Resource dictionary entries should be defined here. -->
</ResourceDictionary>

At this point, you should be fine in both runtime and Blend. If anyone nows how to make themes available at design-time in Cider, please let us know through the comments below.

Jeremy Likness

OK, so we've been through quite a series of iterations and refactoring for something simple: triggering a story board animation based on an event somewhere else. We used dependency and attached properties, we used custom behaviors, and now we're going to do it completely "out of the box."

I figured this one would be easiest to show as a video, so this is the first one I've posted to my blog. I don't have good audio so I used the "notepad" technique. What I'm showing is how to take a storyboard and trigger it based on an event fired by something else, using only what comes built-in with Blend. There is no code-behind and in fact the only XAML I edit is for the list boxes (sorry, I know there's a data tab but I still code HTML in notepad).

Here it is:

Click to View Video

Jeremy Likness

So far we've explored how to use dependency properties and attached properties to create reusable behaviors and triggers. I showed you recently how to refactor an attached property to use the Behavior base class instead. Today, we'll look at the TriggerAction that is also available in System.Windows.Interactivity (either as a part of Expression Blend, or available through the Blend SDK).

If you recall in TextBox Magic, I used an attached property to define an attribute that, when set to true, would bind to the TextChanged event and force data-binding to allow validation and binding without having to lose focus from the text box.

Because the action is tied to an event, it makes sense to refactor as a trigger action.

The first step is simply to build a class based on TriggerAction which requires adding a reference to System.Windows.Interactivity. Like the Behavior class, the TriggerAction can be strongly typed, so we will type it to the TextBox:

public class TextBoxBindingTrigger : TriggerAction<TextBox>
{
}

The method you must override is the Invoke method. This will be called when the action/event the trigger is bound to fires. If you recall from the previous example, we simply need to grab a reference to the binding and force it to update:

protected override void Invoke(object parameter)
{
    BindingExpression binding = AssociatedObject.GetBindingExpression(TextBox.TextProperty);
    if (binding != null)
    {
        binding.UpdateSource();
    }
}

That's it! We now have a trigger action that is ready to force the data binding. Now we just need to implement it with a text box and bind it to the TextChanged event. In Expression Blend, the new action appears in the Behaviors section under assets.

Behavior in Expression Blend

You can click on the trigger and drag it onto an appropriate UI element. Because we typed our trigger to TextBox, Blend will only allow you to drag it onto a TextBox. Once you've attached the trigger to a UI action, you can view the properties and set the appropriate event. In this case, we'll bind to the TextChanged event.

Trigger Properties in Expression Blend

Notice how the associated element defaults to the parent, but can be changed in Blend to point to any other TextBox available as well.

Of course, all of this can be done programmatically or through XAML as well. To add the trigger in XAML, simply reference both System.Windows.Interactivity as well as the namespace for your trigger. Then, you can simply add to the TextBox lik this:

...
<TextBox 
    Text="{Binding Name, Mode=TwoWay, NotifyOnValidationError=true, ValidatesOnExceptions=true}" 
    Grid.Row="0" Height="30" Grid.Column="0" Margin="5" Width="200">
 <i:Interaction.Triggers>
  <i:EventTrigger EventName="TextChanged">
   <local:TextBoxBindingTrigger/>
  </i:EventTrigger>
 </i:Interaction.Triggers>
</TextBox>

As you can see, this is a much more elegant way to create behaviors attached to events as it allows you to easily attach the trigger as well as define the event that the trigger is coupled with.

Jeremy Likness

This is one of those quirks that until you try it, you may not know it exists or what the answer is.

I am working on a WPF project and have a separate control library (actually, a module because I am using the Component Application Guidance/PRISM pattern). I pulled it into Expression Blend and was surprised to see that I had no "design" view. The option simply didn't exist/was grayed out in the menu.

So, I did some digging around and it turns out that because my controls file is just a C# class library, Expression doesn't know it is "allowed" to design the controls. To tell it this, you simply need to open your .csproj file that the XAML is in, then add this to the first PropertyGroup tag that you find:

<PropertGroup>
...
<ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
...
</PropertyGroup>

Just enter it exactly with the same Guids ... now close out of Expression, reload, and voila! you can now design.

Jeremy Likness