Browse by Tags

All Tags » design   (RSS)

This is a quick walkthrough to demonstrate how to make what I call "clean" design-time view models. These are view models that provide design-time data for your application, but don't embed that data in the final product. The release DLL and XAP files will only contain run-time support.

First, fire up Visual Studio and create a new Silverlight application. Just use the default template.

Keep all of the defaults on the dialog that prompts for hosting the application in a new web site (leave this checked) and use Silverlight 4. There is no need to check the "RIA Services" box for this example. When you confirm the dialog, the default project is created for you.

Next, add a folder called "ViewModels."



In the ViewModels folder, add a new class named MainViewModel. Make it a partial class. Create a string property called "Name", an observable collection of strings called "Widget," and implement INotifyPropertyChanged. Here is the code for the view model:

public partial class MainViewModel : INotifyPropertyChanged 
{
    public MainViewModel()
    {
        Widgets = new ObservableCollection<string>();
    }

    private string _name;
    public string Name
    {
        get { return _name; }
        set
        {
            _name = value;
            RaisePropertyChanged("Name");
        }
    }

    public ObservableCollection<string> Widgets { get; private set; }

    protected void RaisePropertyChanged(string propertyName)
    {
        var handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

As you can see, it is a fully functional view model, and no design-time data has been included yet. Next add a special compiler symbol to allow switching from design mode to release mode. Right click on the Silverlight project and go to the properties tab. Click on the "Build" sub-tab, and add the "DESIGN" compiler symbol:

Next, add a class that extends the view model partial class. Name it MainViewModel.sampledata.cs. Place this code in the file:

public partial class MainViewModel
{
#if DESIGN
    private void _WireDesignerData()
    {
        if (!DesignerProperties.IsInDesignTool) return;

        Name = "This is a design-time name.";
        var widgets = new[] { "Widget One", "A Second Widget", "The Third and final Widget" };
        foreach (var widget in widgets)
        {
            Widgets.Add(widget);
        }
    }
#endif
}

Notice the use of the compilation symbol that was added earlier. When you save the file, your solution should look like this:

Wire the call to the new method from the main view model class, wrapping it in the conditional tag - you may have to compile first to get Intellisense for the design-time method:

public MainViewModel()
        {
            Widgets = new ObservableCollection<string>();
#if DESIGN
            _WireDesignerData();
#endif
        }

Build the solution so the view model is available for wiring into the designer surface. Finally, go into the MainPage.xaml and add a grid and a list box bound to the view model. Be sure to add the namespace for your view model:

xmlns:ViewModels="clr-namespace:DesignFriendlyExample.ViewModels" mc:Ignorable="d"

Then wire the remaining XAML:

<Grid x:Name="LayoutRoot" Background="White">
    <Grid.DataContext>
        <ViewModels:MainViewModel/>
    </Grid.DataContext>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>
    <TextBlock Grid.Row="0" Text="{Binding Name}"/>
    <ListBox Grid.Row="1" ItemsSource="{Binding Widgets}"/>
</Grid>

You should immediately see the design-time data appear in the designer:

You can now design with designer data. If you compile and run the application, you'll see the page is blank because the design-time data only is populated in the designer. The only problem is that the XAP and DLL right now are "dirty." If you look at the DLL generated and open it using ILDASM this is what you'll find:

The design-time data has made the DLL larger by adding extra methods and text literals to load into the view models, and adds code that is not used in the run time. When you are ready to release your Silverlight application, simply go back to the properties for the Silverlight project and remove the "DESIGN" compilation symbol. Rebuild the project - it will build and run fine. However, if you take a look at the generated DLL now, the design-time data is no longer there and the DLL is smaller because the data has not been included - as you can see, the method to wire the data doesn't event exist on the view model:

Obviously this example worked with a very specific way of adding the view model to the view. Different frameworks have different ways of binding views and view models. This walkthrough should give you an idea of how to keep te design-time data separate regardless of the method you use and produce cleaner applications when design-time data is no longer needed.

Jeremy Likness

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

The Managed Extensibility Framework (MEF) is a very powerful tool for building modular, extensible Silverlight applications. If you've followed this blog, you'll know that it is not just for applications that anticipate plug-ins, but can be used as an inversion of control container, can facilitate region management, and much more. In preparing the material for my upcoming presentation that is an Introduction to MVVM, I decided to take an existing, publicly available Silverlight application and refactor it to use MVVM.

Everyone seems to like Twitter feeds and RSS Readers, so I chose John Papa's example that demonstrates RSS syndication and isolated storage to refactor. It is an excellent little demo and of course for the article the focus was the syndication and isolated storage, not MVVM.

I'm not going to include the full refactor here - I'll discuss it at the event and then post the code later. What I do want to touch upon, however, is a common issue that people run into when using MEF in conjuction with the Model-View-ViewModel pattern: design-time compatibility. Because the built-in designer for Blend and VS 2010 ("Cider" is the name for the VS 2010 flavor) doesn't actually use the Silverlight runtime, your controls are run in a different CLR than the target application. The result is that MEF compositions fail, which means controls that rely on MEF ultimately don't get rendered in the designer.

Here is our before picture: you can clearly see what the application is going to look like, but there is no data so it's not clear how the data will fit into the control:

MEF Design-Time Before

There are really just a few easy steps to making MEF views design-time friendly. Let's walk through it.

Step One: Create Your ViewModel

This is straightforward and part of the MVVM pattern. Right now, we'll not worry about design-time as much as having a robust ViewModel to use. For my example, I went with a base ViewModel based on the Prism 4.0 drops. The ViewModel itself ended up looking like this:

[Export]
public class FeedsViewModel : BaseViewModel, IPartImportsSatisfiedNotification
{        
    private const string ERROR_INVALID_URI = "Invalid Uri.";
    private const string ERROR_DUPLICATE_FEED = "Duplicate feed not allowed.";

    private bool _add;

    [Import]
    public IFeedStore FeedStore { get; set; }

    [Import]
    public IFeedService FeedService { get; set; }

    public FeedsViewModel()
    {
        Feeds = new ObservableCollection<SyndicationFeed>();           

        AddFeedCommand = new DelegateCommand<object>(
            obj =>
                {
                    _ValidateNewFeed();

                    if (!AddFeedCommand.CanExecute(obj)) return;

                    _add = true;
                    _AddFeed(new Uri(_newFeed, UriKind.Absolute));
                    NewFeed = string.Empty;
                },
            obj => !HasErrors
            );

        Feeds.CollectionChanged += (o, e) =>
                                        {
                                            RefreshFeedCommand.RaiseCanExecuteChanged();
                                            RaisePropertyChanged(() => Items);
                                        };

        RefreshFeedCommand = new DelegateCommand<object>(
            obj =>
                {
                    Feeds.Clear();
                    _RefreshFeeds();
                },
            obj => Feeds.Count > 0);
    }

     public DelegateCommand<object> AddFeedCommand { get; private set; }

    public DelegateCommand<object> RefreshFeedCommand { get; private set; }

    public ObservableCollection<SyndicationFeed> Feeds { get; private set; }

    public int Count { get; set; }

    public IEnumerable<SyndicationItemExtra> Items
    {
        get
        {                
            var query =
                from f in Feeds
                from i in f.Items
                orderby i.PublishDate descending
                select new SyndicationItemExtra {FeedTitle = f.Title.Text, Item = i};

            Count = query.Count();
            RaisePropertyChanged(()=>Count);
            return query;
        }
    }

    private string _newFeed;

    public string NewFeed
    {
        get { return _newFeed; }
        set
        {
            _newFeed = value;                

            RaisePropertyChanged(() => NewFeed);
            _ValidateNewFeed();
        }
    }

    private void _ValidateNewFeed()
    {
        Uri testUri;
        if (Uri.TryCreate(_newFeed, UriKind.Absolute, out testUri))
        {
            if ((from feed in Feeds where feed.BaseUri.Equals(testUri) select feed).Count() > 0)
            {
                SetError(() => NewFeed, ERROR_DUPLICATE_FEED);
            }
            else
            {
                ClearErrors(() => NewFeed);
            }
        }
        else
        {
            SetError(() => NewFeed, ERROR_INVALID_URI);
        }

        AddFeedCommand.RaiseCanExecuteChanged();
    }

    private void _SaveFeeds()
    {
        FeedStore.SaveFeeds(Feeds.Select(feedItem => feedItem.BaseUri).AsEnumerable());
    }

    private void _AddFeed(Uri feedUri)
    {
        FeedService.FetchFeed(feedUri, (ex, feed) =>
                                            {
                                                if (ex != null)
                                                {
                                                    SetError(() => NewFeed, ex.Message);
                                                    return;
                                                }

                                                var oldFeed = (from feedEntry in Feeds
                                                                where feedEntry.BaseUri.Equals(feed.BaseUri)
                                                                select feedEntry).FirstOrDefault();

                                                if (oldFeed != null)
                                                {
                                                    Feeds.Remove(oldFeed);
                                                }

                                                Feeds.Add(feed);

                                                if (!_add) return;

                                                _add = false;
                                                _SaveFeeds();
                                            });
    }

    private void _RefreshFeeds()
    {
        var feedList = new List<SyndicationFeed>(Feeds);
            
        if (feedList.Count == 0)
        {
            FeedStore.LoadFeeds(list=>
                                    {
                                        foreach(var feed in list)
                                        {
                                            _AddFeed(feed);
                                        }
                                    });
            return;
        }

        foreach (var feed in feedList)
        {
            _AddFeed(feed.BaseUri);
        }
    }

    public void OnImportsSatisfied()
    {
        _RefreshFeeds();
    }
}

As you can see, this ViewModel relies on MEF to compose many of its parts. I've pulled out the saving of the feed list to an external service so I can tweak it as needed, and I've also abstracted the call to the syndication service. I've exposed properties and commands and use a query to aggregate the feeds together. This is a lot of functionality and without the required service and storage dependencies, breaks down in the designer. That's OK, there is hope ...

Step Two (Optional): Define an Interface

This might be step one, actually, it all depends on how you work. I like to get my ViewModel working fine, then define the interface and keep up with it. The only purpose of the interface here is to make it easier to define a design-time ViewModel. If you use a tool like JetBrains ReSharper, it's as easy as right-clicking, choosing "Refactor" and then "Extract Interface." We end up with this:

public interface IFeedsViewModel
{
    DelegateCommand<object> AddFeedCommand { get; }

    DelegateCommand<object> RefreshFeedCommand { get; }

    ObservableCollection<SyndicationFeed> Feeds { get; }

    int Count { get; set; }

    IEnumerable<SyndicationItemExtra> Items { get; }

    string NewFeed { get; set; }
}

Step Three: Create a Design-Time ViewModel

Now that we have an interface, we can implement it in another ViewModel we create specifically for runtime. This ViewModel can create new instances of collections and wire in sample data for us. In our example, I've done this:

public class DesignFeedsViewModel : IFeedsViewModel 
{
    private const string DESIGN_FEED = @"http://feeds.feedburner.com/csharperimage/";
    private const string DESIGN_TITLE = "Test Feed ";
    private const string DESIGN_DESCRIPTION = "A test feed for design-time display";
    private const string LOREM = "Lorem ipsum dolor sit amet, consectetur adipiscing elit.";
    private const string FEED_TITLE = "Feed ";

    public DesignFeedsViewModel()
    {
        Count = 10;
        NewFeed = DESIGN_FEED;

        Feeds = new ObservableCollection<SyndicationFeed>();

        for (var x = 0; x ();
            
        for (var x = 0; x < 10; x++)
        {
            var item = new SyndicationItem(LOREM, string.Empty,
                                            new Uri(DESIGN_FEED, UriKind.Absolute));
            designItems.Add(new SyndicationItemExtra { FeedTitle = FEED_TITLE + x, Item = item });
        }

        Items = designItems;
    }

    public DelegateCommand<object> AddFeedCommand
    {
        get { return new DelegateCommand<object>(); }
    }

    public DelegateCommand<object> RefreshFeedCommand
    {
        get { return new DelegateCommand<object>(); }
    }

    public ObservableCollection<SyndicationFeed> Feeds { get; set; }
        
    public int Count { get; set; }
        
    public IEnumerable<SyndicationItemExtra> Items { get; set; }

    public string NewFeed { get; set; }        
}

Now, a quick note: at this point, you probably realize you could have gotten away with just one ViewModel. In that case, you'd do something like this:

if (DesignerProperties.IsInDesignTool)
{
   // code for design-time
   return;
}

That's perfectly fine but does a lot of mixing of code ... I've grown to prefer keeping my design-time view models separate and synchronizing them with the interface. It's totally up to you!

Step Four: Bind Your Production ViewModel

This is a key step. If you are directly binding the ViewModel in XAML, and using CompositionInitializer to fire up MEF, you'll need to wrap a condition around it so it doesn't fire that command in design time. There are many ways to bind the ViewModel to the view. I've written about a few:

The bottom line is you can bind it however it makes sense. For simple solutions, this is a pattern I've come to enjoy. While it does involve code-behind, it cleanly separates the MEF ViewModel from design-time because it is not invoked in the constructor (this only works if you export the View as well):

[Export("RootVisual",typeof(UserControl))]
public partial class Reader : IPartImportsSatisfiedNotification
{                
    [Import]
    public FeedsViewModel ViewModel { get; set; }

    public Reader()
    {
        InitializeComponent();
    }       

    public void OnImportsSatisfied()
    {
        LayoutRoot.DataContext = ViewModel;
    }
}

Here, I'm taking advantage of the interface that MEF uses when it wires up a class. Once all dependencies are resolved, it will call OnImportsSatisfied and I can glue my ViewModel. In the designer, the control is simply created using new() so there is no MEF call. So how do we get our design-time data?

Step Five: Bind Your Design-Time ViewModel

Binding the design-time ViewModel is actually very straightforward, especially with the help of design-time extensions. At the top of our XAML, we'll two references: one for the design-time extensions if they aren't already there, and one for the location of the design-time ViewModel:

<UserControl
    xmlns:design="clr-namespace:SilverlightSyndication.DesignTime"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    .../>

Next, on the root grid where the ViewModel should be bound, we take advantage of the d:DataContext and d:DesignInstance extensions:

<Grid d:DataContext="{d:DesignInstance design:DesignFeedsViewModel, IsDesignTimeCreatable=True}" ...>

Here we are defining a data context that is only valid at design time. We bind it to a "design instance" of our view model we created specifically for the designer.

Now, when we run the application, MEF finds and binds the production ViewModel for us. However, in the designer, the designer finds and binds the design-time view model. This gives us plenty of rich data to work with and keeps the designer so happy they're likely to buy you a steak dinner (that last was just in case any of the designers I work with are reading this).

With a little Toolkit Theme love, a rebuild, and a refresh of the XAML in the designer, I now get this:

MEF Design-Time After

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