locked
Dynamically hiding a command through UIContext? RRS feed

  • Question

  • Hi,

    I'm not sure if this is possible, but I'd like to enable/disable and show/hide a command's menu item based on a custom UIContext.  I've got my command setup like this, in the .vsct file:

       <Button guid="guidWorkflowVSDesignerCmdSet" id="cmdidWorkflowToolboxTool" priority="0x0100" type="Button">
        <Parent guid="guidSHLMainMenu" id="IDG_VS_VIEW_DEV_WINDOWS"/>
        <!--<Icon guid="guidImages" id="bmpPic2" />-->
        <CommandFlag>DefaultInvisible</CommandFlag>
        <CommandFlag>DynamicVisibility</CommandFlag>
        <Strings>
         <CommandName>cmdidWorkflowToolboxTool</CommandName>
         <ButtonText>Workflow Toolbox</ButtonText>
        </Strings>
       </Button>
    

     

    This is good, in that my command doesn't appear until my editor is created for the first time, in a given visual studio isolated shell instance.  But, when I switch to a different editor, my command doesn't disappear (nor get disabled) from the menu.  I thought I could use the VisibilityConstraints part of the .vsct, and this is where I'm not sure I'm doing the right thing:

     <VisibilityConstraints>
      <VisibilityItem guid="guidWorkflowVSDesignerCmdSet" id="cmdidWorkflowToolboxTool" context="guidWorkflowVSDesignerEditorFactory" />
      <!--<VisibilityItem guid="guidWorkflowVSDesignerCmdSet" id="cmdidWorkflowToolboxTool" context="guidDesignerCmdUIContext" />-->
     </VisibilityConstraints>
    
    

    In the VisibilityItem line above that is commented out, I set the context to the guid of my custom UI context.  Programmatically, I'm setting the cmd UI context via IVsMonitorSelection.SetCmdUIContext when the document changes.  I'm using this context in a custom toolwindow, by means of the ProvideToolWindowVisibility attribute, and that is working well for me for the tool window's visibility, but not for the menu item's visibility.  In the VisiblilityItem line that is not commented out, I chose a guid that was generated for me when the package was created, but I don't think this is right.

    My question is, then is there a way to control command (menu item) visibility via a custom UI context?  Is is it by using the VisibilityConstraints element, or some other way?

    Thanks,

    Notre

    Thursday, January 27, 2011 12:30 AM

Answers

  • Ok, I tried your idea and I think for the most part it works.  I modified your code snippet to fix some compilation errors and I added a similar implementation for IOleCommandTarget::Exec, which is also needed.

        int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
          IOleCommandTarget tgt = (IOleCommandTarget)GetService(typeof(IOleCommandTarget));
          if (tgt != null)
          {
            return tgt.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
          }
    
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    
        int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
          // validate parameters
          if (prgCmds == null || cCmds != 1)
          {
            return VSConstants.E_INVALIDARG;
          }
    
          if (pguidCmdGroup == GuidList.guidWorkflowVSDesignerCmdSet && prgCmds[0].cmdID == (uint)PkgCmdIDList.cmdidWorkflowToolboxTool)
          {
            return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
          }
          else
          {
            IOleCommandTarget tgt = (IOleCommandTarget)GetService(typeof(IOleCommandTarget));
            if (tgt != null)
            {
              return tgt.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
            }
          }
    
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    

    I said it works for the most part because I ran into one more snag.  I want to do a lot of this logic from my editor pane (created from the editor factory).  The code you see above is from there.  The challenge I ran into here was that my QueryStatus function was not called when my editor was not the active editor type.  So, what that meant is my menu item still appeared when another editor was active - my whole purpose of using the UI context!

    If I implemented IOLeCommandTarget on my package class, in addition to on my editor pane, then QueryStatus would be called on the package that provides the editor factory (and ultimately the editor pane), when my editor was not the active editor being used.

        int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    
        int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
          // validate parameters
          if (prgCmds == null || cCmds != 1)
          {
            return VSConstants.E_INVALIDARG;
          }
    
          if (pguidCmdGroup == GuidList.guidWorkflowVSDesignerCmdSet && prgCmds[0].cmdID == (uint)PkgCmdIDList.cmdidWorkflowToolboxTool)
          {
            return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
          }
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    

    This partial duplication of logic is not ideal, but I guess it is necessary...

    Thanks,

    Notre

    Thursday, January 27, 2011 10:52 PM

All replies

  • When you say the second one 'doesn't work' are you sure your code to set the command context has been run? 

    Has anything happened that would cause the shell to be aware it needs to update the command UI, say a focus change between toolwindows? You can try calling IVsUIShell::UpdateCommandUI(0) to ensure that (the command update) happens. 

    I don't think setting the command UI context automatically makes us re-evaluate command visibility oddly enough, looking at the code around this area it looks like we automatically set the command UI context on selection change but the code that does that also explicitly tells us to update the command state (visibility, enabled, etc...).  Since that code wouldn't be running if ALL you are doing is calling SetCmdUIContext then we (the shell) have no idea that we need to do a command update pass.

    Ryan

    Thursday, January 27, 2011 1:27 AM
  • Ohhh, also note that if your package is loaded we will not do auto-visibility using UI contexts unless your package level handler returns a failure from QueryStatus for the command ID. If your package is not loaded we will always do auto-visibility checks and check default command state from the command authoring (after everyone else in the command route fails to handle the QueryStatus request for the command).

    Ryan

    Thursday, January 27, 2011 1:30 AM
  • Hi Ryan,

    In response to your question:

    When you say the second one 'doesn't work' are you sure your code to set the command context has been run? 

    For updating the menu items related to my custom command, neither UI context setting (the one commented nor the one uncommented above) in the VisiblityItem works.  As for my code that sets the command context, yes, I'm sure it's running because my custom toolwindow appears/disappears visually when I change the document being edited, and I can set a breakpoint in the code and see it being hit.  I'll show the modified code below.

    Your next question is:

    Has anything happened that would cause the shell to be aware it needs to update the command UI, say a focus change between toolwindows? You can try calling IVsUIShell::UpdateCommandUI(0) to ensure that (the command update) happens.

     I expect the menu items to be automatically updated when the active document has changed.  This is also where I set my custom UI context.  At your suggestion, I also tried calling IVsUIShell::UpdateCommandUI(0) explicitly, but it didn't resolve the problem.  Here's my abbreviated code:

        int IVsSelectionEvents.OnElementValueChanged(uint elementid, object varValueOld, object varValueNew)
        {
          if (elementid == (uint)VSConstants.VSSELELEMID.SEID_DocumentFrame)
          {
            IVsWindowFrame newFrame = varValueNew as IVsWindowFrame;
    
            object docView;
            if (newFrame != null && newFrame.GetProperty((int)(__VSFPROPID.VSFPROPID_DocView), out docView) == VSConstants.S_OK)
            {
              EditorPane designer = docView as EditorPane;
              IVsMonitorSelection selectionService = GetService(typeof(IVsMonitorSelection)) as IVsMonitorSelection;
    
              if (designer != null)
              {
                if (selectionService != null)
                {
                  selectionService.SetCmdUIContext(_cmdUIContextMenuCookie, 1);
                  IVsUIShell vsShell = (IVsUIShell)GetService(typeof(IVsUIShell));
                  if (vsShell != null)
                  {
                    int hr = vsShell.UpdateCommandUI(0);
                  }
                }
              }
              else
              {
                if (selectionService != null)
                {
                  selectionService.SetCmdUIContext(_cmdUIContextMenuCookie, 0);
                  IVsUIShell vsShell = (IVsUIShell)GetService(typeof(IVsUIShell));
                  if (vsShell != null)
                  {
                    int hr = vsShell.UpdateCommandUI(0);
                  }
                }
    
              }
            }
          }
          return VSConstants.S_OK;
        }
    

    I don't think setting the command UI context automatically makes us re-evaluate command visibility oddly enough, looking at the code around this area it looks like we automatically set the command UI context on selection change but the code that does that also explicitly tells us to update the command state (visibility, enabled, etc...).  Since that code wouldn't be running if ALL you are doing is calling SetCmdUIContext then we (the shell) have no idea that we need to do a command update pass.

    That makes sense to me, but I'm triggering the UI context change in response to a document change (which I think implies focus change) so, I would expect a command requery.  I also did the UpdateCommandUI thing above, to no net gain.

    Ohhh, also note that if your package is loaded we will not do auto-visibility using UI contexts unless your package level handler returns a failure from QueryStatus for the command ID. If your package is not loaded we will always do auto-visibility checks and check default command state from the command authoring (after everyone else in the command route fails to handle the QueryStatus request for the command).

    I don't quite follow this part.  If my package is not loaded, then I don't need the UI context stuff at all; the DefaultInvisible and DynamicVisibility command flags seem to do the trick, right?  When my package is loaded, are you saying that I need to return a failure from QueryStatus?  I'm confused.  I thought that the UI context was a means of taking care of this stuff for me automatically.  Also, won't my QueryStatus only be called if my document is the active one, receiving commands at that time (assuming I'm not registering as priority command target)?

    Also, this link implies that I can influence command behaviour based on a UI context, unless I'm reading it wrong... 

    Thanks,

    Notre

    Thursday, January 27, 2011 6:04 PM
  • I don't quite follow this part.  If my package is not loaded, then I don't need the UI context stuff at all; the DefaultInvisible and DynamicVisibility command flags seem to do the trick, right?  When my package is loaded, are you saying that I need to return a failure from QueryStatus?  I'm confused.  I thought that the UI context was a means of taking care of this stuff for me automatically.  Also, won't my QueryStatus only be called if my document is the active one, receiving commands at that time (assuming I'm not registering as priority command target)?

    Also, this link implies that I can influence command behaviour based on a UI context, unless I'm reading it wrong... 

    Thanks,

    Notre


    VisbilityItem gives you a richer way to influence command visibility in addition to the static command authoring (DefaultInvisible).  DynamicVisibility doesn't mean anything other than the visibility can change and thus we should call QueryStatus for the command and listen to the visibility flags it may return.  It is necessary in order to dynamically change command visibility but it is quite different than DefaultInvisible.  UI context visibility can allow your command to be made visible/hidden automatically without your package being loaded based on more complex requirements than DefaultInvisible (which isn't really complex, it just says 'hide my command by default').  You can have your command appear for instance when a solution is loaded and have it not appear when there is no solution loaded, or when we enter debug mode but have it hidden when not in debug mode.

    The reason command handlers are interesting is that VisibilityItem lookup occurs AFTER command target querying in the command routing, this allows you to override simple visibilty based on UI contex with richer determination once your package is loaded (if you want to).  The reason you would need to return E_OLECMDERR_NOTSUPPORTED if you wanted 'normal' auto-visibility to apply is because otherwise you are saying you have responded to the QueryStatus and we should terminate the command routing, which would mean 'skip any handlers you haven't yet called and also skip the auto-visibility / static command authoring checks'.

    Depending on where you handler is located it can be called even if your document isn't active, if no one else recognizes the command in the command route and we need to determine its status and your package is loaded your package will be QueryStatused for the command.  If you have a package level handler (i.e. you added a handler to your package's IMenuCommandService OR you implemented QueryStatus yourself on your package) then it can be called regardless of if your document has focus or not.

    You can influence command UI visibility via UI context, it is a very common use and there are hundreds or thousands of commands in the shell that do just that, so the fact it isn't working for you leads me to believe there is something you are doing wrong, as it works perfectly fine for lots and lots of other commands in the shell.

    Ryan

    Thursday, January 27, 2011 6:28 PM
  • The reason command handlers are interesting is that VisibilityItem lookup occurs AFTER command target querying in the command routing, this allows you to override simple visibilty based on UI contex with richer determination once your package is loaded (if you want to).  The reason you would need to return E_OLECMDERR_NOTSUPPORTED if you wanted 'normal' auto-visibility to apply is because otherwise you are saying you have responded to the QueryStatus and we should terminate the command routing, which would mean 'skip any handlers you haven't yet called and also skip the auto-visibility / static command authoring checks'.

     

    So, are you saying that I should always return E_OLECMDERR_NOTSUPPORTED in QueryStatus if I want to leverage the UI context functionality for my command?  My goal is simple, I think: I want my menu item to appear when my custom UI context is active (which right now this translates to my editor being the active editor) and to make it disappear (and be disabled) otherwise.  Also, I'm using IMenuCommandService for this set of commands rather than IOleCommandTarget.  To effectively return E_OLECMDERR_NOTSUPPORTED, would I set the OleMenuCommand.Enabled property to false in my 'on query' event handler?? Finally, if I return E_OLECMDERR_NOTSUPPORTED always for this command, will by Exec handler (or my IMenuCommandService 'on' event handler) ever be called?

    Well, it is good to know that command UI visibility can be controlled via UI context and I have little doubt I'm doing something wrong ... just trying to figure out what :)

    Notre

    • Edited by Notre Thursday, January 27, 2011 7:05 PM Modify question re: QueryStatus
    Thursday, January 27, 2011 7:03 PM
  • >So, are you saying that I should always return E_OLECMDERR_NOTSUPPORTED in QueryStatus if I want to leverage the UI context functionality for my command? 

    I would say just don't even register handlers for your command if using VisibilityItem is enough to handle your visibility changes.  Unfortunately that doesn't work as well if you want to tweak enabled/text in addition to visibility :(  We don't have a good story around things like this since QueryStatus is 'a bunch of state' and it isn't unreasonable (just not supported) that someone may want to handle QueryStatus callbacks to determine things like enabled/display text BUT leave visbility up to UI context.

    The best you can do if you want to handle QueryStatus is to emulate the UI context visibility stuff yourself.  Your QueryStatus will only ever be invoked once your package is loaded (and we don't load packages to QueryStatus).  So as soon as you are getting called you could use IVsMonitorSelection yourself to check on the UI context and toggle your Visible state based on it being present / not present.

    As for 'returning OLECMDERR_E_NOTSUPPORTED', it isn't technically that you need to return that code specifically, you need to return something other than S_OK.  The MPF bits (IMenuCommandService) will do that automatically if it fails to find a registered OleMenuCommand object for the given GUID/ID.  If it finds a registered item though it will ALWAYS return S_OK, which indicates to the shell to STOP routing the command, use the returned QueryStatus bits and terminate the part of the update that involves locating a handler.  This has the effect of also skipping past all non-called handlers in the route AND skipping the 'static' determination we would normally do if the package wasn't yet loaded and no handlers could be found in the command route.

    Ryan

    Thursday, January 27, 2011 7:34 PM
  • Ok, I think I understand what you're saying. As for handling the commands, it turns out I'm not adding a handler for 'on query' event via MPF (IMenuCommandService) for this command, and I'm currently setting up this command during my package initialization.  Here's my code:

        protected override void Initialize()
        {
          Trace.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
          base.Initialize();
    
          base.RegisterEditorFactory(new EditorFactory(this));
    
          OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
          if (null != mcs)
          {
            CommandID toolwndCommandID = new CommandID(GuidList.guidWorkflowVSDesignerCmdSet, (int)PkgCmdIDList.cmdidWorkflowToolboxTool);
            MenuCommand menuToolWin = new MenuCommand(ShowToolWindow, toolwndCommandID);
            mcs.AddCommand(menuToolWin);
          }
        }
    

    So, shouldn't the custom UI context be kicking in here, and the VisibilityConstraints be respected?

    Thanks,

    Notre

    Thursday, January 27, 2011 8:01 PM
  • It doesn't matter if you are adding a handler for OnBeforeQueryStatus, the OleMenuCommandService will consider it a 'hit' if it finds a MenuCommand object in its map with a matching GUID / DWORD id.  In this case it is finding the one you set here and thus it is returning S_OK from QueryStatus along with the other bits it is pulling from the MenuCommand object.  I will check if there is a way to register a MenuCommand (to handle Exec) but NOT have it return S_OK from QueryStatus, but I doubt it.

    Ryan

    Thursday, January 27, 2011 8:47 PM
  • Ok, then if there's no way to not return S_OK when using OleMenuCommandService, that means I have two options, right?

    1. Keep using OleMenuCommandService and implement a handler for OnBeforeQueryStatus, where I explicit check the UI command context and set the command's Enable and Visible (?) properties appropriately.

    2. Convert to using IOleCommandTarget and in my QueryStatus's handling of a command that should be governed by a UI context, return  OLECMDERR_E_NOTSUPPORTED (or something other than S_OK)?

    Can I implement IOleCommandTarget for just those commands that I want to respect a UI context, and use OleMenuCommandService for other commands?

    Thanks,

    Notre

    Thursday, January 27, 2011 9:18 PM
  • IOleCommandTarget is an interface, so there is no way to specify 'some commands' tha tyou want to handle and others you don't.  That said you could implement it (it has to be done explicitly at the base class does it explicitly :() and simply defer to OleMenuCommandService for all commands you don't want AutoVis for.  An example would look something like this (browser written code, not tested or even compiled, caveat emptor):

    int IOleCommandTarget.QueryStatus(ref Guid guidGroup, uint nCmdId, OLECMD[] oleCmd, IntPtr oleText) 
    {
      //Test here if the incoming guidGroup and nCmdId are one of the commands for which you want visibility dictated solely by VisibilityItem, if so then just
      //return OLECMDERR_E_NOTSUPPORTED, otherwise do the fetching of IOleCommandTarget as shown below. This is what the base package class does and
      //the GetService(typeof(IOleCommandTarget)) will result in fethcing the MenuCommandService (which implements IOleCommandTarget) and simply <br/>  //redispatching to it.
      IOleCommandTarget tgt = (IOleCommandTarget)GetService(typeof(IOleCommandTarget));
      if (tgt != null) 
      {
        return tgt.QueryStatus(ref guidGroup, nCmdId, oleCmd, oleText);
      }
      return(NativeMethods.OLECMDERR_E_NOTSUPPORTED);
    }
    

     Ryan

    Thursday, January 27, 2011 9:25 PM
  • Ok, I tried your idea and I think for the most part it works.  I modified your code snippet to fix some compilation errors and I added a similar implementation for IOleCommandTarget::Exec, which is also needed.

        int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
          IOleCommandTarget tgt = (IOleCommandTarget)GetService(typeof(IOleCommandTarget));
          if (tgt != null)
          {
            return tgt.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
          }
    
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    
        int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
          // validate parameters
          if (prgCmds == null || cCmds != 1)
          {
            return VSConstants.E_INVALIDARG;
          }
    
          if (pguidCmdGroup == GuidList.guidWorkflowVSDesignerCmdSet && prgCmds[0].cmdID == (uint)PkgCmdIDList.cmdidWorkflowToolboxTool)
          {
            return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
          }
          else
          {
            IOleCommandTarget tgt = (IOleCommandTarget)GetService(typeof(IOleCommandTarget));
            if (tgt != null)
            {
              return tgt.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
            }
          }
    
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    

    I said it works for the most part because I ran into one more snag.  I want to do a lot of this logic from my editor pane (created from the editor factory).  The code you see above is from there.  The challenge I ran into here was that my QueryStatus function was not called when my editor was not the active editor type.  So, what that meant is my menu item still appeared when another editor was active - my whole purpose of using the UI context!

    If I implemented IOLeCommandTarget on my package class, in addition to on my editor pane, then QueryStatus would be called on the package that provides the editor factory (and ultimately the editor pane), when my editor was not the active editor being used.

        int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    
        int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
          // validate parameters
          if (prgCmds == null || cCmds != 1)
          {
            return VSConstants.E_INVALIDARG;
          }
    
          if (pguidCmdGroup == GuidList.guidWorkflowVSDesignerCmdSet && prgCmds[0].cmdID == (uint)PkgCmdIDList.cmdidWorkflowToolboxTool)
          {
            return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
          }
          return (int)(Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED);
        }
    

    This partial duplication of logic is not ideal, but I guess it is necessary...

    Thanks,

    Notre

    Thursday, January 27, 2011 10:52 PM
  • The problem you ran into is a problem around the command route, well not a problem, it is behaving as expected.  The command route in VS is partially dynamic and partially static (to some extent).  When your window has focus the command target associated with it will be receiving QueryStatus requests, when it doesn't it won't.  This is by design.  Your package will always get QueryStatus for commands it defines (not all commands), but only when it is loaded.

    As I said the idea of VisibilityItem was designed (as I understand it) to control visibility WITHOUT your package having to be loaded.  Once your package is loaded its QueryStatus should handle that, which can lead to duplication in checking UI context (though that code is fairly trivial). 

    The alternative would mean we (the shell) would somehow need to magically know that your QueryStatus results didn't really include visibility bits and that we should use UI context for that, but we don't have a way for you to communicate such information and our telepathic abilities are pretty bad :)

    The problems you had with S_OK vs OLECMDERR_E_NOTSUPPORTED is just friction introduced by MPF, I don't use MPF terribly often myself but I know there are numerous gotchas, which is unfortaunte because it is a way to avoid some of the nastier details of the command system but you need to really understand the command system in all its gory detail before you can effeciently use MPF and avoid these kind of gotchas :(

    As for duplication of code. You could make a static object that both your package and pane delegate to, there is nothing that says they themselves have to contain the logic.  Also your second (package level) impl doesn't 'thunk' into MenuCommandService like your pane level one does.  So if you ever add package level handlers to the IMenuCommandService they won't be called with what you have above since you don't do the GetService/re-dispatch dance like you do in the pane level handler.

    Ryan

    Thursday, January 27, 2011 11:41 PM
  • Thanks for the explanation Ryan.  I realize my package level implementation doesn't 'do the dance' but would need to if I added package level handlers there; thanks for the note.

    I see what you're saying about the design intent of UI contexts, and how they are helpful when my package is not yet loaded.  I totally agree.

    But it seems like it's a bit of a kludge to make a menu item disappear when my editor (tool window, etc) is not the active element. I guess I could eschew the UI context approach and instead just do a simple QueryStatus check at the package level (so commands I create are always routed to my code), where the implementation would check to see if any of my tool windows, editors, etc, are active and then enable / show menu items if they are, and hide/disable them otherwise. UI context seems a bit better than this because I can declare some stuff declaritively (via .vsct), and also change the conditions about what constitutes a ui context being active or not, rather than coding this for each individual commands, toolwindows, etc.

    Thanks,

    Notre

    Thursday, January 27, 2011 11:57 PM
  • >But it seems like it's a bit of a kludge to make a menu item disappear when my editor (tool window, etc) is not the active element.

    Not really, it is quite common.  The Refactor menu disappears when you move away from the editor for instance.  The idea is to not clutter the users UI with a bunch of menus/commands that make no sense based on their context, and if their focus is in a TFS window then a Refactor menu item makes no sense.

    >UI context seems a bit better than this because I can declare some stuff declaritively (via .vsct), and also change the conditions about what constitutes a ui context being active or not, rather than coding this for each individual commands, toolwindows, etc.

    They are strictly LESS powerful than QueryStatus as it is simply is this UI context active or not.  QueryStatus can be as complicated or simple as you like.  Declarative is nice but the number of items whose visibility can be determined declaritively is small, it works 'in a pinch' but more robust logic is generally required.  As for 'doing it for each command', well you can certainly centralize the determination in your QueryStatus code, you don't have to repeat the code over and over.

    VS' general approach is to try and make easy things easy and more complex things possible.  With QueryStatus you can make arbitrarily complex command state determinations, without it you can make slightly complex ones but really only with UI contexts and only visible/not visible (no enabled, or changing text, or checked state, etc...) Without them you can make only REALLY trivial declarative things like DefaultInvisible.  It is a gradient of functionality, a step up the 'complexity' scale buys you much more power, but also involves more work, there is no tradeoff that is infintely powerful and infinitely easy, in any product or any technology.

    Ryan

    Friday, January 28, 2011 12:12 AM
  • No, I agree the end user experience is great.  I meant the means to get there (the package author's steps) to get that dynamic behaviour is bit more complicated than I would have expected.

    Notre

    Friday, January 28, 2011 12:16 AM
  • Yeah, you definetly hit a corner case here :(  Like I said most uses of VisibilityItem I have seen are

    1)  Used ONLY before the package is loaded and the QueryStatus handler does ALL state determination once the package is loaded.

    2)  Done a lot more in native code or when people are implementing IOleCommandTarget.  Part of your problem really came from MPF not offering a way to return a 'failure' even when it found a OleCommand match.  This makes it impossible for instance to register an Exec handler but still have QS 'fail' as needed in this scenario :( 

    You could ALMOST set Supported = false on your MenuCommand object, that would cause QueryStatus to return a failure, but it woud also cause your Exec callback to never be invoked :(  EDIT: To remove incorrect information I put before, it won't invoke the command if Supported is false (in Exec), I was misreading the code :)

    Ryan

    Friday, January 28, 2011 12:27 AM
  • Ah, well - it does work.  And it's far easier to be a critic, rather than create.

    Notre

    Friday, January 28, 2011 12:38 AM
  • Nope, feedback is always welcome and I do agree this area is far more complicated than it needs to be.  My biggest handcuff is backwards compatibility.  We have (literally) thousands of extensions, most all of them use the command system, most all of the various dark corners and weird quirks of the existing command system are used and relied on (sometimes very unfortunately, even the weird design decisions/bugs are *vital* to *someone*). Every time I conspire to break them in some architecturally sound way (accidently or on purpose) there is very strong feedback that doing so will cause the sky to fall, all partners will hunt us down with torches and pitchforks and I will be responsible for some kind of kitten genocide :)

    Ryan

    Friday, January 28, 2011 12:52 AM
  • Geeze Ryan, that's why we gave you a flak jacket!   :-)
    Ed Dore
    Friday, January 28, 2011 6:11 AM