Giter VIP home page Giter VIP logo

optionssample's Introduction

Options example

Build status

Applies to Visual Studio 2015 and newer

This sample shows how to correctly specify and consume options for a Visual Studio extension that is both thread-safe and performant.

The goal is to use best practices to achieve the following:

  • A simple way to provide custom options
  • Expose the options in the Tools -> Options dialog
  • Thread-safe way to access and modify the settings
  • Both synchronous and asyncronous support
  • No need to load the package for the settings to initialize

Let's get started

The source code in this sample adds two options pages to the Tools -> Options dialog - General and Other which is nested under the My Options node.

Options

To make them show up in that dialog they have to be registered on the package class itself using the ProvideOptionPage attribute, like so:

[ProvideOptionPage(typeof(DialogPageProvider.General), "My Options", "General", 0, 0, true)]
[ProvideOptionPage(typeof(DialogPageProvider.Other), "My Options", "Other", 0, 0, true)]
public sealed class MyPackage : AsyncPackage
{
    ...
}

Both GeneralOptions (source) and OtherOptions (source) are classes containing the settings as regular public properties. They are inheriting from the generic base class BaseOptionModel (source).

internal class GeneralOptions : BaseOptionModel<GeneralOptions>
{
    [Category("My category")]
    [DisplayName("Message box text")]
    [Description("Specifies the text to show in the message box")]
    [DefaultValue("My message")]
    public string Message { get; set; } = "My message";
}

These classes can be used from anywhere in the extension when options are needed, but we need to provide a way to expose them to VS and for that we're going to create a class called DialogPageProvider:

internal class DialogPageProvider
{
    public class General : BaseOptionPage<GeneralOptions> { }
    public class Other : BaseOptionPage<OtherOptions> { }
}

The classes specified inside the DialogPageProvider class are inheriting from the second generic base class in this sample - BaseOptionPage (source). It's sole responsibility is to function as the entry point for the Tools -> Options dialog.

That's it. We have now created custom options pages and registered them on the package.

Using the custom options

The options are ready to be consumed by our code and there are two ways to go about it:

1. From the UI thread

Whenever our code executes on the UI thread we can easily access the settings like so:

string message = GeneralOptions.Instance.Message;

This will throw when not on the UI thread so you'll catch any misuse during development.

2. From a background thread

This is a thread-safe way to obtain the options instance. When in doubt, use it this way.

GeneralOptions options = await GeneralOptions.GetLiveInstanceAsync();
string message = options.Message;

See how it is being used from the TextviewCreationListener.cs MEF component in the source code.

Modify the options

You can programmatically modify the options like this:

GeneralOptions options = await GeneralOptions.GetLiveInstanceAsync();
options.Message = "My new message";
await options.SaveAsync();

The above method can be called in a syncronous way on the UI thread, like so:

GeneralOptions.Instance.Message = "My new message";
GeneralOptions.Instance.Save();

It is recommented to do it async if possible.

There you have it. Custom options using best practices.

Futher reading

Read the docs for all the details surrounding these scenarios, but notice that while they do provide more detailed documentation, they don't follow the best practices outlined in this sample.

optionssample's People

Contributors

frogman7 avatar madskristensen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

optionssample's Issues

Avoid showing message boxes from background threads

In the snippet below, you explicitly switch to a background thread and then show a MessageBox.

System.Threading.Tasks.Task.Run(async () =>
{
// Make the call to GetLiveInstanceAsync from a background thread to avoid blocking the UI thread
GeneralOptions options = await GeneralOptions.GetLiveInstanceAsync();
System.Windows.Forms.MessageBox.Show(options.Message);
});

I've seen this pattern be the cause of the message box being created "behind" VS's main window such that it is not visible to the user. In some cases IIRC it can even be modal, such that VS is unresponsive until the user eventually discovers the window that's running on the other thread and brings it to the front.

Can you switch to the main thread before displaying the dialog?

Loss in derived-type information in property deserialization

The way you deserialize options, you'll drop any data that was serialized and unique to a derived type that may have been the instance of the property value when it was serialized. For example: if the options property is a Bag but an instance of class DoubleBag : Bag was assigned to that property, the whole of DoubleBag would be serialized but only Bag would be deserialized.

object value = JsonConvert.DeserializeObject(serializedProp, property.PropertyType);

On a related note, I see you're using JsonConvert for serialization. Is that a requirement? Or should we perhaps define two virtual methods for serialization/deserialization so that this can be overridden by another serializer if necessary? The .NET binary formatter or xml serializer for instance could preserve information about the instance type of the properties.

Change notification

How can I know when the settings were changed by the user? I have to update colours and other options from those settings. But I don't know when the user has changed the settings.

And, does it have to be that complicated? I mean, pages of code only get extension options right? Something is seriously wrong with the product of Visual Studio and its extensibility design. I'm using a much simpler approach now like in https://github.com/madskristensen/TrailingWhitespace/tree/master/src. It also works fine.

BTW, I'm targeting VS 2019 in an async package. If it also works in VS 2017 that's fine, but I'm not creating it for that version. I'm switching to VS 2019 completely soon.

Race condition in initialization of singleton options

You have a static field that gets initialized by various static or instance members of the class via an EnsureInitialized method, which contains this line:

_liveModel = _liveModel ?? new AsyncLazy<T>(CreateAsync, ThreadHelper.JoinableTaskFactory);

If multiple threads execute this concurrently when the field is not initialized, the result can be that the field is set to multiple unique values, perhaps leading some to observe a different set of options initially or later on since only one of the instances is actually 'live'.

I suggest you use field initializers for your AsyncLazy fields. This implicitly gives you thread-safety in the simplest way, and AsyncLazy is a safe thing to initialize from static initializers.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.