Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / desktop / WPF

Adding Options in Expression Blend extensions

5.00/5 (2 votes)
4 Aug 2010CPOL5 min read 15.4K  
Often extensions for Expression Blend have to need to store some global variables. Why not use the mechanisms Blend itself offers?

Introduction

Often extensions for Expression Blend need to store some global variables. Why not use the mechanisms Blend itself offers? I'd like to show you how to add a custom options page to the options dialog.

I've started the example project by creating a new C# WPF Custom Control Library project and naming it ExtendingBlendOptionsTutorial. The details on how to create a new Blend Extension can be found here. You need to make sure the Microsoft.Expression.Extensibility and Microsoft.Expression.Framework assemblies are referenced in your project. Both can be found in the Expression Blend installation folder on your machine.

The architectural design is taken from the existing option pages that are used by Blend. Every existing page uses a '’…Page’ , a ‘…Control’ which contains the XAML for the dialog and a ‘…Model’. The Model is bound to the Control. The example in this tutorial follows the same pattern.

IPackage Implementation

Let’s start at the beginning. When Blend is started, it loads the extensions using MEF. Therefore the extension has to be an implementation of the IPackage interface and has to be decorated with an Export attribute.

image

The code is pretty simple. From the IServices provided by Blend, an instance of the IOptionsDialogService is requested. This services contain a collection of all option pages. Every options page is an implementation of IOptionsPage.

In this case, a new TutorialOptionsPage is created in the Load method. A new TutorialOptionsModel is passed into this. After that, the TutorialOptionsPage is added to the list of option pages.

C#
[Export(typeof(IPackage))]
public class OptionsTutorialPackage:IPackage 
{   
    public void Load(IServices services)
    {
       IOptionsDialogService optionsDialogService = 
                services.GetService<IOptionsDialogService>();
            
        TutorialOptionsPage optionsPage = 
               new TutorialOptionsPage(new TutorialOptionsModel(services));
        optionsDialogService.OptionsPages.Add(optionsPage);
    }
 
    public void Unload()
    {
    }
}  

The Options Page

The TutorialOptionPage is basically an implementation of the IOptionPage interface.

image

Properties:

  • Content - contains a framework element containing the view of the options page. It should contain input elements to set the configuration options.
  • Title - contains the title of the options page. It is shown in the list on the left of the options dialog.
  • Name - contains the internal name of the page.

Methods:

  • Cancel - is called when the user hits the cancel button of the options dialog.
  • Commit - this is called when the user hits the Ok button.
  • Load - is called when Blend starts.

First, let’s have a look at the declarations and the constructor. The TutorialOptionPage class has to implement the IOptionsPage interface to work. This interface can be found in the Microsoft.Expression.Framework.Configuration namespace. References to the TutorialOptionsControl and the TutorialOptionsModel, are kept. As well as a reference to the IConfigurationObject interface.

The constructor takes an instance of TutorialOptionsModel and stores it.

C#
public class TutorialOptionsPage:IOptionsPage
{      
    private TutorialOptionsControl tutorialOptionsControl;
    private TutorialOptionsModel tutorialOptionsModel;
    private IConfigurationObject configurationObject;
 
    public TutorialOptionsPage(TutorialOptionsModel model)
    {
        tutorialOptionsModel = model;
    }

The implementations of the Title and Name properties is very simple. They both just return a string.

C#
public string Title
{
    get { return "TutorialOptionsTitle"; }
}
 
public string Name
{
    get { return "TutorialOptionsName"; }
}

The Content property returns an instance of the TutorialOptionsControl, the UI of the options page. It needs an instance of the IConfigurationObject to load the data for the model. This interface is passed to the Load method which I'll describe in a second.

If the model isn't loaded, which happens when the changes in the options dialog are cancelled, a new one has to be instantiated. The configuration data has to loaded as well in this case.

The Load method on the model is called. After this, it is passed to the constructor of the TutorialOptionsControl where it will be bound to the UI.

C#
public object Content
{
    get
    {
        if (this.tutorialOptionsModel == null)
        {
            this.tutorialOptionsModel = new TutorialOptionsModel();
            this.tutorialOptionsModel.Load(configurationObject);
        }
        if (this.tutorialOptionsControl == null)
        {
            this.tutorialOptionsControl = 
                new TutorialOptionsControl(this.tutorialOptionsModel);
        }
        return this.tutorialOptionsControl;
    }
}

Right, the Load method of the TutorialOptionsPage class. It takes the IConfigurationObject interface as a parameter, which is provided by Blend. It is stored in the class to use it again later. The model is loaded by using the IConfigurationObject.

C#
public void Load(IConfigurationObject value)
{            
    configurationObject = value;
    this.tutorialOptionsModel.Load(this.configurationObject);    
}

The Cancel method is called when the user hits the “Cancel” button on the options dialog. Both the TutorialOptionsControl and the TutorialOptionsModel are set to null to make sure they will be reloaded the next time the options dialog is opened.

C#
public void Cancel()
{
    this.tutorialOptionsControl = null;
    this.tutorialOptionsModel = null;
}

The Commit method is called when the user hits the "Ok” button on the options dialog to save the settings. First the configurationObject has to be cleared to remove any unwanted settings that might be left over. The Save method on the model is called and the configurationObject is given to it.

Last both the TutorialOptionsControl and the TutorialOptionsModel are reset to be reloaded the next time they're needed.

C#
public void Commit()
{
    if(this.tutorialOptionsModel!=null)
    {
        this.configurationObject.Clear();
        tutorialOptionsModel.Save(configurationObject);
        this.tutorialOptionsControl = null;
        this.tutorialOptionsModel = null;              
    }
}

The Model

In this example, the model contains only two properties, two strings. To make sure the UI will be updated correctly, the INotifyPropertyChanged interface is implemented.

Saving and loading of the properties is done in the Load and Save methods. Both methods make use of an IConfigurationObject. The properties are saved using the SetProperty method from the IConfigurationObject interface. To load the properties, the GetProperty method is called for each property. The same keys are used as with saving and the returned objects are cast to the correct type.

C#
public class TutorialOptionsModel : INotifyPropertyChanged
{  
    private string _value1;
    public string Value1
    {
        get { return _value1; }
        set
        {
            _value1 = value;
            InvokePropertyChanged("Value1");
        }
    }
 
    private string _value2;
    public string Value2
    {
        get { return _value2; }
        set
        {
            _value2 = value;
            InvokePropertyChanged("Value2");
        }
    }      
 
    public void Save(IConfigurationObject configurationObject)
    {
        configurationObject.SetProperty("OptionsTutorialValue1", this.Value1);
        configurationObject.SetProperty("OptionsTutorialValue2", this.Value2);
    }
 
    public void Load(IConfigurationObject configurationObject)
    {
        Value1 = 
            configurationObject.GetProperty("OptionsTutorialValue1") as string;
        Value2 = 
            configurationObject.GetProperty("OptionsTutorialValue2") as string;
    }
 
    public event PropertyChangedEventHandler PropertyChanged;
 
    public void InvokePropertyChanged(string propertyName)
    {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null)
        {
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

In an actual extension, the model can be used in other parts of the extension to make the configuration available for other classes. Just make sure you call the Load method.

User Interface

The user interface for the Options Dialog is just XAML. In this example, I have created a WPF UserControl with two textboxes to let the user enter some text. Both values are stored in Blend's configuration files. The textboxes are bound to the two properties of the model.

image

The XAML that is used to create this UI:

XML
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="48" />
        <RowDefinition Height="48" />
        <RowDefinition Height="*" />
    </Grid.RowDefinitions>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="124" />
        <ColumnDefinition Width="176*" />
    </Grid.ColumnDefinitions>
    <TextBox Margin="4"  
                VerticalAlignment="Center"                 
                Grid.Column="1" 
                Text="{Binding Value1}" />
    <Label Content="Value 1:"                
            HorizontalAlignment="Right" 
            VerticalAlignment="Center" />
    <TextBox  Margin="4,12" 
                Grid.Column="1" 
                Grid.Row="1" 
                Text="{Binding Value2}"/>
    <Label Content="Value 2:" 
            HorizontalAlignment="Right" 
            VerticalAlignment="Center"  
            Grid.Row="1"  />
</Grid>

The data context of this UserControl needs to be set to the instance of TutorialOptionsModel. This is done through the constructor of the control.

C#
public partial class TutorialOptionsControl : UserControl
{
    public TutorialOptionsControl(TutorialOptionsModel tutorialOptionsModel)
    {
        InitializeComponent();
        this.DataContext = tutorialOptionsModel;
    }
}

After compiling and starting the options page should look something like this now:

image

Classes and Interfaces

Here are the diagrams of the classes and interfaces used in the tutorial.

image

image

Source Code

The code for the project can be downloaded here.

 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)