Commands and UI

Today was a big source code check-in day. I sent well over 50 files to be checked into our source code control system, and I will be here late sending off a lot more. What kind of things did I change? Well, one of the most asked for features is the ability to add different UI types to a CommandBar, and with different settings.


For example, today when you call Commands.AddNamedCommand we will create a command. You can then add UI to a command bar for that command with the Command.AddControl method. But that UI element is always a button, not combo boxes, not menu controllers (the split drop-down button like that for the undo / redo command), etc. The change for Whidbey allows you to specify which control type should be used. This is the enum that contains the different control types available (this list is subject to change, either with the addition or subtraction of additional elements):


    enum vsCommandControlType


        vsCommandControlTypeSeparator = 1,

        vsCommandControlTypeButton = 2,

        vsCommandControlTypeMenuButton = 4,

        vsCommandControlTypeSwatch = 8,

        vsCommandControlTypeSplitDropDown = 16,

        vsCommandControlTypeDropDownCombo = 32,

        vsCommandControlTypeMRUCombo = 64,

        vsCommandControlTypeDynamicCombo = 128

    } vsCommandControlType;


The second problem that people have with the AddNamedCommand/AddControl combination is in how all commands you create have both text and a picture, you cannot change the UI to have just text, just the bitmap, etc. This is the enum that we have defining the different types available (again, subject to change):


    enum vsCommandStyle


        vsCommandStylePict = 1,

        vsCommandStyleText = 2,

        vsCommandStylePictAndText = 3,

        vsCommandStyleContextUseButton = 4,

        vsCommandStyleTextMenuUseButton = 8,

        vsCommandStyleTextMenuControlUseMenu = 16,

        vsCommandStyleTextCascadeUseButton = 32,

        vsCommandStyleComboNoAutoComplete = 64,

        vsCommandStyleComboCaseSensitive = 128

    } vsCommandStyle;


Of course, we have fixed a number of the usability problems developers would have when developing Add-ins. In versions of VS prior to Whidbey, if you would run the Add-in wizard, press F5, then kill the debugged process, you command would be lost and you would need to run devenv /setup to reset the state (which would also reset any of your customizations). We changed when we save data saying that the UISetup stage of your Add-in ran from startup to shutdown, fixing this common problem. We also decided today on a few other enhancements to make developing Add-ins easier, stay tuned for more details…

Comments (5)

  1. I’m completely ignorant in this area, so forgive me if this is a stupid question…

    I notice the enums above are bits in a bitfield (powers of two). Under what scenario would you actually want to OR multiple enum values together? It doesn’t make sense for something to be both a separator and a button, does it?

  2. Craig says:

    The vsCommandStyle enum should be a bitfield since, for example, vsCommandStylePictAndText is a conbination of the values vsCommandStylePict and vsCommandStyleText. As for vsCommandControlType, there was no real reason why I did it, just a personal style. I like to plan ahead for the future, where we can add flags without any conflict. A perfect example of this is the vsCMTypeRef enum. In V7, there was one person within MS (who works for a group other than VS, and shall remain nameless – he had some semi-valid reasons) who complained that this enum was a bit-field. So we changed it. Two days ago, we were working on a vsCMTypeRef2 enum because we needed to add, among other values, an unsigned flag so you could represent “unsigned int”. vsCMType is not a bit-field so our options were either create an enum value which had the value set to 0x4000, or to create new flags (vsCMTypeRefUnsignedInt, vsCMTypeRefUnsignedChar, vsCMTypeRefUnsignedLong, vsCMTypeRefUnsignedShort).

    I am still not sure which way we should do it because there are good arguments for either way. First, if we use the bit-field flag set to 0x4000 it does not fit in well with the current way of determining the value, they are not bits, why should this change now? Also, what happens when we decide to add a new value that is necessary in a future version? Lastly, this make your code overly complex, you need to do something like:

    if(kind == vsCMTypeRefInt)

    { … }

    else if(kind == vsCMTypeRefInt|vsCMTypeRefUnsigned)

    { … }

    but how do you know which flags can take an vsCMTypeRefUnsigned?

    OTOH, what does the existing code model do in the 2002/2003 version? Does it return vsCMTypeRefInt for an unsigned int value? If so, having it return vsCMTypeRefUnsignedInt could break existing code since that code will not know what to do with this new value. Using the bits, old code will still work while new code can know to look specifically for that or’ed in value. Then there is the possibility where the value vsCMTypeRefOther is returned for an unsigned int, and then you need to figure out what to do in that case.

    So, back to the original question, why a bit-field? Well, first there is the personal style, but there is also the reason of making it work cleanly in the future. We would not be having this philosophical debate over vsCMTypeRef2 if we planned for it in the past. Suppose in the next version, we add (I am not saying this is a good idea or that we will do it) a value that says draw the UI element with the Windows theme, not the Office flat style. Making this value a bit-field fixes some of the problems we have with other enums.

  3. Frank Hileman says:

    Yipee! These are great enhancements! Maybe get some good docs on that DTE stuff too? Ref docs, intellisense, not so good there.

    Also, would be nice if DTE functions, properties, and indexers did not throw exceptions on expected conditions, such as an object not found in a collection. Catching exceptions for no reason is tedious and slows everything down.

  4. Craig says:

    Having some of the methods return NULL/null/Nothing is something that we thought long and hard about, but decided against it. The reason is because, deep down, we are still COM based. The convention is that COM should return E_INVALIDARG when a value is passed that is not valid. While most dev work against the object model is .NET based, we still could not cut off the COM people. Since .NET maps any return value that is not S_* into an exception, that is why you are seeing that exception.

    I have often considered creating an interface that has an IsItemIndexAvailable property, I doubt it will be added for the Whidbey version of VS (there are some other things I would like to do), but it is possible for the next.

  5. Frank Hileman says:

    Hello Craig,

    Yes, I figured the COM wrapper was the problem. What I hoped for was a smarter COM wrapper — a real .Net class that wraps the COM object and makes it more friendly. This would not affect the COM people but of course it would be more work for you guys.

    One more peeve: The simple COM wrappers now provided make it impossible to view the properties of the DTE objects in the debugger. If you try to expand them there is nothing there. Better wrappers is again the solution.

    I know what I am asking for is a lot of work. That is why I was hoping someday that more of VS would be managed code. At least the part close to the developers (the object model). I think most developers prefer managed code in general. COM is such pain by comparision — I don’t think I would ever want to go back. Most everyone feels the same. Perhaps someday VS will only have a small "kernel" of unmanaged code?