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.
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.
[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.
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.
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
.
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.
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.
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.
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.
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.
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.
The XAML that is used to create this UI:
<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.
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:
Classes and Interfaces
Here are the diagrams of the classes and interfaces used in the tutorial.
Source Code
The code for the project can be downloaded here.