locked
Solution tree item commands RRS feed

  • Question

  • Hello from Australia. I'm researching VS extensibility for the first time in an attempt to add custom commands to solution tree items that are visible for specific item/file types. For example, right-click a .bat file would provide a Run command, a .nuspec file would provide a Pack command, and so on. Commands for different item types would be a great productivity boost.

    My test extension is based on various samples found in searches. I'm confused by the results of testing the command. Setting the command invisible by default means it will never be constructed. The BeforeQueryStatus event is only raised after the first invoke, so I can't inspect the item to see if the command should be visible or not. Then I am utterly stumped about how to find which item(s) are selected.

    I am basically lost and confused beyond hope. The command event sequence makes no sense and I can't navigate the bewildering object model, classes, enums and services. So I'm wondering if anyone in this forum has some samples that are even vaguely similar to what I'm attempting. If I can just figure out how make a command visible only for certain solution tree item types then I will be off and running. Can anyone help?

    Thanks, Greg

    Saturday, September 30, 2017 11:43 PM

All replies

  • Hi, I've done what your trying and experienced similar pain. But it is doable and works well when you get it right. I managed it in the end. Which version of visual studio are you using as 2017 extensions are a little different. I haven't written any extensions in 2017 yet but can help with earlier versions.

    I'll have to dig out the code and refresh my memory a bit so let me know if that'll help. Even if you are using 2017 I think (I read about it a while back now) the changes are structural/packaging mainly. I could be wrong though.

    Sunday, October 1, 2017 2:06 AM
  • I'm using Visual Studio 2017 Pro. Any sample code that even vaguely lead me in the right direction would be most appreciated. If your code is for an older version, then I can probably overlook that and experiment with how to update it.

    I was wondering if anyone had already written some sort of general purpose extension that lets you associate arbitrary commands with different types of solution tree items, but searches reveal nothing so far. If I get my planned extension working then I might try to turn it into a general purpose one.

    Sunday, October 1, 2017 3:29 AM
  • Hi, ok I've found a couple of add-ins that implement BeforeQueryStatus. I'll have to build a 2015 vm to check these out I think and that won't be until tomorrow now.

    In the meantime. One way of achieving what you want is to load a lightweight add-in initially and that loads your functionality when required.

    I might have done it that way, I can't remember, but it is an option. Regardless, what I have worked so if you can hang on a day or so I can likely help you out properly. Sorry to keep you hanging on I'm just really busy deploying things over this weekend.

    To see if were on the same page, does you Initialize method look anything like this...

    protected override void Initialize()
            {
                Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
                base.Initialize();
    
                // Add our command handlers for menu (commands must exist in the .vsct file)
                OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
                if (null != mcs)
                {
                    // Create the command for the menu item.
                    CommandID menuCommandID = new CommandID(GuidList.guiddocNitroVSCmdSet, (int)PkgCmdIDList.cmdidSFCreateCollectionFromModel);
                    MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID);
                    mcs.AddCommand(menuItem);
    
                    // Create the command for the Solution Explorer menu item.
                    CommandID collectionFromModelCommandID = new CommandID(GuidList.guiddocNitroProcessEdmx, (int)PkgCmdIDList.cmdidCreateCollectionFromModel);
                    OleMenuCommand createCollectionFromModel = new OleMenuCommand(CreateCollectionFromModel, collectionFromModelCommandID);
                    createCollectionFromModel.BeforeQueryStatus += new EventHandler(createCollectionFromModel_BeforeQueryStatus);
                    //createCollectionFromModel.BeforeQueryStatus += createCollectionFromModel_BeforeQueryStatus;
                    //createCollectionFromModel.Visible = false;
                    //createCollectionFromModel.Enabled = false;
                    mcs.AddCommand(createCollectionFromModel);
    
                    // Create the command for the Server Explorer menu item.
                    CommandID collectionFromDatabaseCommandID = new CommandID(GuidList.guiddocNitroProcessDatabase, (int)PkgCmdIDList.menuidServerExplorerMenu);
                    OleMenuCommand collectionFromDatabase = new OleMenuCommand(CreateCollectionFromDatabase, collectionFromDatabaseCommandID);                
                    mcs.AddCommand(collectionFromDatabase);
                    _applicationObject = GetDTE2();
                }
            }

    Sunday, October 1, 2017 9:12 PM
  • Hi Greg,

    You need to use the BeforeQueryStatus event to get the selected treenode (or treenodes!) in the Solution Explorer and then decide if your command should be visible+enabled, visible+disabled or invisible. I assume that you know how to do that and that your problem is that BeforeQueryStatus is only queried after "first invoke".

    This is because, by default, packages use "delay loading", which means that their commands and user interface (buttons, etc.) are created when installed but the package is only loaded when one of its commands is executed. This is a good practice (for packages that have all commands always visible/enabled) because otherwise all installed packages would load when VS is loaded and would be very slow.

    However, this is not appropriate when your package must decide if a command/UI item must be visible/enabled depending on some "context". For this case you can:

    - Mark your package to load always on startup.

    - Mark your package to load when some "UI context" has happened (for example, a solution is loaded.

    In both cases you use the ProvideAutoLoad attribute.

    See:

    HOWTO: Autoload a Visual Studio package
    http://www.visualstudioextensibility.com/articles/packages/

    Loading VSPackages
    https://docs.microsoft.com/en-us/visualstudio/extensibility/loading-vspackages

    If you are targeting VS 2015/2017 and not below, strive to use rule-based UI context to avoid loading the package if not absolutely required:

    How to: Use Rule-based UI Context for Visual Studio Extensions
    https://docs.microsoft.com/en-us/visualstudio/extensibility/how-to-use-rule-based-ui-context-for-visual-studio-extensions


    My portal and blog about VSX: http://www.visualstudioextensibility.com; Twitter: https://twitter.com/VSExtensibility; MZ-Tools productivity extension for Visual Studio: https://www.mztools.com



    Wednesday, October 4, 2017 10:00 AM
  • Thanks Carlos and everyone, the default delay loading behaviour would explain the weird sequence of events I experienced. This weekend I'll take the advice, samples and links and see if I can get a skeleton of my extension loading correctly. Once I can get the tree item selection I'll be able to finish it. I'll report back after I discover more -- Greg
    Wednesday, October 4, 2017 9:47 PM
  • Just to say, my visual studio extension work was based on what Carlos has made available in that area over the years. Incredibly helpful!

    Thanks a million Carlos! We've never spoken but I feel like I'm meeting an old friend, I learnt a lot about extensions from you :)

    All the best

    Steve

    Thursday, October 5, 2017 5:13 PM
  • Hi Steve,

    I'm glad to help :-)


    My portal and blog about VSX: http://www.visualstudioextensibility.com; Twitter: https://twitter.com/VSExtensibility; MZ-Tools productivity extension for Visual Studio: https://www.mztools.com

    Thursday, October 5, 2017 5:49 PM
  • Carlos' hint led me eventually to this page which led me to add these attributes to my command class:

    [PackageRegistration(UseManagedResourcesOnly = true)]
    [ProvideAutoLoad(UIContextGuids80.SolutionExists)]
    [Guid("...")]

    This caused my extension to load early, so the ctor preceded the BeforeQueryStatus event in a sensible order.

    Now, after two hours of searching and experimenting I have failed to determine which solution item(s) are selected when my command query event runs. I have found dozens of samples which are contradictory, incomplete, incomprehensible or possibly irrelevant (I can't tell due to lack of experience in this area). Some use an _application object but never explain how to get an instance. Some use GetENVx methods but don't explain where they are, and they might be deprecated anyway. So I am once again completely and utterly lost.

    If someone can explain how to get the name and type of selected solution tree item(s) when the query event runs I will be close to by goal. Once I get the fully qualified path of a selected item I will be able to run my command logic against it.

    Greg

    P.S. I can see that VS Extensibility is so staggeringly complicated that it would practically be a full-time occupation to get a competent understanding. It even looks more complicated than writing a device driver.

    Saturday, October 7, 2017 4:41 AM
  • Hi Greg,

    Yes, Visual Studio Extensibility is complicated. In the origins there was a kind of extension named add-ins and an API named automation model (EnvDTE). Add-ins used an _application instance of the type EnvDTE.DTE that they received in the OnConnection method of add-ins.

    For example, your question would be solved as an add-in using my sample:

    HOWTO: Get the selected nodes in the Solution Explorer from a Visual Studio add-in
    http://www.visualstudioextensibility.com/articles/add-ins/

    Once you get an EnvDTE.ProjectItem, you use ProjectItem.FileNames(1) to get the path. For an EnvDTE.Project, you use Project.FullName. For solution, you use DTE.Solution.FullName. All the three types have a Properties collection that you can use also to get their properties.

    Now, modern extensions are packages, and can use the native VS API (VS Services), which is complicated, or the old API EnvDTE. To get an instance of EnvDTE.DTE from your package see:

    HOWTO: Get an EnvDTE.DTE instance from a Visual Studio package.
    http://www.visualstudioextensibility.com/articles/packages/

    Applying the second article and then the first article you should solve the problem.

    If you want the native VS way you would have to use IVsUIHierarchyWindow :

    https://msdn.microsoft.com/en-us/library/microsoft.visualstudio.shell.interop.ivsuihierarchywindow.aspx

    which you can get with:

    VsShellUtilities.GetUIHierarchyWindow

    (search samples on the web)


    My portal and blog about VSX: http://www.visualstudioextensibility.com; Twitter: https://twitter.com/VSExtensibility; MZ-Tools productivity extension for Visual Studio: https://www.mztools.com

    Monday, October 9, 2017 3:21 PM