I want to add program options to configure my new add-in. How can I extend the existing options dialog with my own options panels?

asked 01 Jun '10, 21:31

Scott%20Whitlock's gravatar image

Scott Whitlock ♦♦
696262833
accept rate: 50%

edited 01 Jun '10, 21:32


OptionsItems vs. OptionsPads

When you open the Options Dialog (Tools->Options) in the SoapBox Core framework, you will see a rectangle on the left, and a blank part of the screen on the right. The rectangle on the left is actually a TreeView that holds anything that's an IOptionsItem. This is a Tree-like structure so the IOptionsItem has an Items property that can hold more IOptionsItem items. Each IOptionsItem also has a Pad property of type IOptionsPad. The IOptionsPad is what gets displayed on the right side of the screen when you select an item in the tree on the left.

IOptionsItem

Let's say you want your Add-In to add an item in the tree. Here is all you have to do:

[Export(SoapBox.Core.ExtensionPoints.Options.OptionsDialog.OptionsItems, 
                  typeof(IOptionsItem))]
class MyOptionsItem : AbstractOptionsItem
{
    public MyOptionsItem()
    {
        Header = "My Options Item"; // Should use a Resource
    }
}

The Export statement tells the Host that we're extending the SoapBox.Core.ExtensionPoints.Options.OptionsDialog.OptionsItems extension point, which is the top level in the tree. If you compile this into your Add-In and run it, you should see this new item in the tree. What you're actually defining is a ViewModel object for the OptionsItem. You don't have to define a View because SoapBox Core does that for you. Now if you want this OptionsItem to have sub-items, you can just create another extension point an import them like this:

[Export(SoapBox.Core.ExtensionPoints.Options.OptionsDialog.OptionsItems, 
                                  typeof(IOptionsItem))]
class MyOptionsItem : 
     AbstractOptionsItem, 
     IPartImportsSatisfiedNotification
{
    public MyOptionsItem()
    {
        Header = "My Options Item"; 
    }

    [Import(SoapBox.Core.Services.Host.ExtensionService)] 
    private IExtensionService extensionService { get; set; }

    [ImportMany(ExtensionPoints.MyOptionsItem.MyOptionsSubItems, 
                 typeof(IOptionsItem), AllowRecomposition=true)]
    private IEnumerable<IOptionsItem> items { get; set; }

    public void OnImportsSatisfied()
    {
        Items = extensionService.Sort(items);
    }
}

[Export(ExtensionPoints.MyOptionsItem.MyOptionsSubItems, 
                 typeof(IOptionsItem))]
class MyOptionsSubItem : AbstractOptionsItem
{
    public MyOptionsSubItem()
    {
        Header = "My Options Sub-Item"; 
    }
}

IOptionsPad

Above, we defined the items in the tree, but we also need to define what gets displayed in the right hand side of the Options Dialog when someone selects this item in the tree. We can do that by setting the Pad property to an IOptionsPad object:

[Export(SoapBox.Core.ExtensionPoints.Options.OptionsDialog.OptionsItems, 
                typeof(IOptionsItem))]
class MyOptionsItem : AbstractOptionsItem
{
    public MyOptionsItem()
    {
        Header = "My Options Item"; // Should use a Resource
        Pad = new MyOptionsPad();
    }
}
IOptionsPad ViewModel

Now in the OptionsPad itself, we need to expose some properties for editing. This example shows a common pattern I've used for settings, including using the Settings in the project properties as the backing store:

class MyOptionsPad : AbstractOptionsPad
{
    public MyOptionsPad()
    {
        Name = "MyOptionsPad"; // Needs to be unique
    }

    public override void Commit()
    {
        base.Commit();
        Properties.Settings.Default.Save();
    }

    #region "MyProperty"

    public bool MyPropertyEdit
    {
        get
        {
            return m_MyPropertyEdit;
        }
        set
        {
            if (m_MyPropertyEdit != value)
            {
                m_MyPropertyEdit = value;
                CommitActions.Add(
                    () => Properties.Settings.Default.MyProperty = 
                              m_MyPropertyEdit);
                CancelActions.Add(
                    () =>
                    {
                        m_MyPropertyEdit = Properties.Settings.Default.MyProperty;
                        NotifyPropertyChanged(m_MyPropertyEditArgs);
                        NotifyPropertyChanged(m_MyPropertyArgs);
                    });
                NotifyOptionChanged();
                NotifyPropertyChanged(m_MyPropertyEditArgs);
            }
        }
    }
    private bool m_MyPropertyEdit = Properties.Settings.Default.MyProperty;
    static readonly PropertyChangedEventArgs m_MyPropertyEditArgs =
        NotifyPropertyChangedHelper.CreateArgs<MyOptionsPad>(o => o.MyPropertyEdit);

    public bool MyProperty
    {
        get
        {
            return Properties.Settings.Default.MyProperty;
        }
    }
    static readonly PropertyChangedEventArgs m_MyPropertyArgs =
        NotifyPropertyChangedHelper.CreateArgs<MyOptionsPad>(o => o.MyProperty);
    #endregion
}

I know that's a lot to edit one small boolean property, but it's nice and self-contained. If you want to add another property, just copy and paste the MyProperty region, and search and replace MyProperty with your property name. What's it doing though? It defines two properties: MyProperty, which is the committed value, and MyPropertyEdit, which is the edited value before the user clicks OK to commit their changes. The AbstractOptionsPad base class provides a list of actions to execute on a commit, and a list of actions to execute on a cancel. Therefore, when a user edits a property, we add some actions to those lists (to actually save the value, or discard it).

If you can hook into this object elsewhere in your program, you can use the MyProperty property value anywhere you need to know the committed value. It also raises the PropertyChanged event, so you can be notified if the user changes the value.

IOptionsPad View

In this case, we need to define a View for the OptionsPad as well (like most Views in SoapBox Core, this consists of a ResourceDictionary and a code behind for the Resource Dictionary):

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="clr-namespace:MyCompany.MySolution.MyProject"
    x:Class="MyCompany.MySolution.MyProject.MyOptionsPadView">

    <DataTemplate DataType="{x:Type local:MyOptionsPad}">
        <CheckBox IsChecked="{Binding MyPropertyEdit}">
            <TextBlock Text="My Property"/> <!-- Should use a Resource here -->
        </CheckBox> 
    </DataTemplate>
</ResourceDictionary>

using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Windows;

namespace MyCompany.MySolution.MyProject
{
    [Export(SoapBox.Core.ExtensionPoints.Host.Views, typeof(ResourceDictionary))]
    public partial class MyOptionsPadView : ResourceDictionary 
    {
        public MyOptionsPadView()
        {
            InitializeComponent();
        }
    }
}
link

answered 01 Jun '10, 21:32

Scott%20Whitlock's gravatar image

Scott Whitlock ♦♦
696262833
accept rate: 50%

Your answer
toggle preview

Follow this question

By Email:

Once you sign in you will be able to subscribe for any updates here

By RSS:

Answers

Answers and Comments

Markdown Basics

  • *italic* or _italic_
  • **bold** or __bold__
  • link:[text](http://url.com/ "Title")
  • image?![alt text](/path/img.jpg "Title")
  • numbered list: 1. Foo 2. Bar
  • to add a line break simply add two spaces to where you would like the new line to be.
  • basic HTML tags are also supported

Tags:

×23
×4

Asked: 01 Jun '10, 21:31

Seen: 1,375 times

Last updated: 01 Jun '10, 21:32

powered by OSQA