locked
VSPackage: Modifying toolbar button text/tooltip RRS feed

  • Question

  • Hello,

    I am developing a VSX package using VSPackage Builder (so this is VSCT in C# 4 and VS2010). I need to change the text and tooltip on a toolbar button based on things going on inside my code; for now, I'm simply trying to do this from within the execute handler of another button.

    This means that I have two buttons in a group on my toolbar, btnAction and btnLabel. btnAction is a simple icon toolbar button, while btnLabel looks like this in the .vsct:

    <Button guid="guidVSPackageBuilderTutorialCommandSet" id="btnLabel" priority="0x0100">
     <CommandFlag>DefaultDisabled</CommandFlag>
     <CommandFlag>DontCache</CommandFlag>
     <CommandFlag>NoCustomize</CommandFlag>
     <CommandFlag>TextChanges</CommandFlag>
     <CommandFlag>TextOnly</CommandFlag>
     <Strings>
      <CommandName>cmdidbtnLabel</CommandName>
      <ButtonText>btnLabel</ButtonText>
      <MenuText>btnLabel</MenuText>
      <ToolTipText>Tooltip btnLabel</ToolTipText>
     </Strings>
    </Button>
    

    The first problem with this is that when I use TextChanges, the ToolTipText string is ignored and the ButtonText is used for the tooltip as well.

    Now here is my simple handler code for the action button:

    private int iClickCount = 0;
    
    protected override void btnActionExecuteHandler(object sender, EventArgs e)
    {
     var svc = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
     
     CommandID idBtnLabel = new CommandID(GuidList.guidVSPackageBuilderTutorialCmdSet, (int)PkgCmdIDList.btnLabel);
     var cmd = svc.FindCommand(idBtnLabel) as OleMenuCommand;
     
     cmd.Text = "Clicked " + (++iClickCount) + " times";
    }
    

    This changes the "label" button's text, but the tooltip cannot be specified separately (the OleMenuCommand object only has a "Text" property, but no separate one for a tooltip) and is always set to the same string as the text. I figure this is because I am not actually accessing the button, but only the underlying command - but I don't care much about the command (if it doesn't let me change the tooltip, that is), I only want to access the button. How can I get my hands on the button or its tooltip ? The OleMenuCommandService does not seem to have ways of accessing specific "instances" of a certain command.

    If I use TextChangesButton instead of TextChanges, the button correctly displays my pre-set tooltip, but "in exchange" while the "Text" property of the btnLabel command is changed correctly, the change is not reflected on the button in the UI, which doesn't help much in alleviating my confusion.

    I have previously been developing an AddIn, where UI handling was done through the DTE object. Does the DTE still have anything to do with this in a VSPackage context ?

    Sunday, October 24, 2010 7:56 PM

Answers

  • >As for QueryStatus, from all I've seen I figured this would actually be more appropriate, but the concept seemed a little strange to me, as QueryStatus looks pretty isolated - from what I understand, to set any control properties in QueryStatus based on what has happened in my logic, I would have to hold all relevant state information of my application internally and update it after every action, just in case some control's QueryStatus handler "comes along" and wants to know what's new for its control. Is that correct ?

     Well it is expected that someone that understands a command has the state to answer questions about it (text, is it enabled, is it visible, etc...)  If you store that somewhere or figure it out 'just in time' is up to you (though if figuring it out is expensive please do store it somewhere, QueryStatus is a VERY hot path and having a slow handler slows everything down and upsets users :)) QueryStatus will be invoked when the shell feels it needs to update the command UI state, there are several reasons this can happen, but in general yes you need to be able to answer command status requests.  In fact what you are doing in your Exec is sort of doing that, you are just using the OleMenuCommand object to 'store' the state, since QueryStatus is answered based in MPF based on teh various properties of that object (that is why QueryStatus is actually called BeforeQueryStatus in MPF and you use that time to set properties on the OleMenuCommand object, which will then be read off / returned by the actual QueryStatus that MPF is handling when it invokes your BeforeQueryStatus handler).

    >The command behind it is completely irrelevant, the button will be disabled at all times (which I was only able to achieve by explicitly disabling it every time in the QueryStatus handler, so I actually already have one).

    This is the second request I have seen recently for a 'label' type control in VSCT, which we currently don't support.  You can put this information elsewhere (like in your toolwindow if you have a tool window) or use something like the status bar to communicate it, but those solutions may not work for you.  We don't support a 'label' control so your idea of a disabled button that displays text is probably as close as you can get with the standard VSCT supported mechanisms.

    There is no exposure of the underlying data models that drive the command UI through any IVs type service (it is something we are lacking that I think would be nice, but alas have not convinced anyone in managmenet that we should do it :().  It isn't the VS package that creates the command objects and controls, it is the shell and we can do this without loading your package at all (hence the reason for the VSCT), otherwise we would need to load dozens of packages at startup just to build the command UI.  The DTE object isn't in control of anything per se, it just knows how to get at the (otherwise unexpose) underlying model that the command UI is built on top of, so yes using DTE you can change the command UI.

    Unforuntately there is no 'new extensibility' in the command space, all our resources were spent in 2010 just getting the UI layer over to WPF and not breaking anyone in the process (or breaking as few people as we could) or requiring them to change their existing code for commanding (QueryStatus/Exec) or VSCT files. It would be nice if we had new extensibility (like MEF based command interaction), but we don't as of now :(


    Ryan 

    • Marked as answer by TeaDrivenDev Tuesday, October 26, 2010 6:11 PM
    Tuesday, October 26, 2010 5:09 PM

All replies

  • First, technically this kind of state updating should be done via QueryStatus, as that its only purpose in life :)  A button setting another buttons text seems very odd, but what you have works.

    The static command authoring bits from your VSCT file are generally used when there are no QueryStatus handlers to be found, so 'run time' dynamic text will override them.  The fact that your dynamic text overrides your tooltip does seem strange, but it is the same way things worked in Visual Studio 2008 (and probably before).

    I am guessing (the decision to behave this way far pre-dated my employment here) the general idea is a tooltip is intended to display information that the button can't when it is on a toolbar, since most buttons don't display text on toolbars. To facilitate this the tooltip text is the button text if it has dynamically changed, otherwise it is the static text from the ToolTipText tag, or the static ButtonText if you don't specify the ToolTipText tag, so that the information exposure can be similar to what the user would see if the button were placed on a  menu.

    You CAN get a hold of the DTE object and explicitly change your control's tooltip, but realize DTE is somewhat heavyweight as it needs to do some initialization before you can use it, and it can cause 'force flushing' of command UI, since it models actual command UI, even if said UI hasn't been created (for instance a drop down menu you have never shown).  So if all you want to do is change the tooltip you really have to ask yourself if it provides enough value to justify eating up a good chunk of the users memory (DTE objects are long lived and some of the bootstrapping is overly aggresive in creating models in some scenarios) just to do that.

    If you think it provides such value the property you want is CommandBarControl.ToolTipText.  You can get it from the DTE.CommandBars object by locating your control, so first get the command bar for the toolbar you are on (by name), then find your control instance, then set its ToolTipText property.

    Ryan

    Tuesday, October 26, 2010 2:34 AM
  • Hello Ryan,

    thank you for your answer.

    To explain the odd functionality - this is only a proof of concept for the purpose of understanding what I need to do. If I know how to set a button's text from another button, I will also know how to do so from somewhere else, which would mostly be in my extension's core logic.

    As for QueryStatus, from all I've seen I figured this would actually be more appropriate, but the concept seemed a little strange to me, as QueryStatus looks pretty isolated - from what I understand, to set any control properties in QueryStatus based on what has happened in my logic, I would have to hold all relevant state information of my application internally and update it after every action, just in case some control's QueryStatus handler "comes along" and wants to know what's new for its control. Is that correct ?

    What I basically need to do (and what the current Add-in version of the extension is doing) is interact with some files and web services from some of the buttons and a few forms I call, and then occasionally update the "label" button with "Doing this", "Doing that" and finally "Done this and that at 13:37". That is, to quote you, its only purpose in life. ;-) The command behind it is completely irrelevant, the button will be disabled at all times (which I was only able to achieve by explicitly disabling it every time in the QueryStatus handler, so I actually already have one).

    The logical way for me to do that was having my core logic do its work and then call a method to directly update the label with the new status information. If I wanted to do that via QueryStatus, I guess I would need to put the status message in a global (respective to my extension) variable and then have the QueryStatus handler actually write it to the label. Is that correct ? But as I see it, that would not guarantee the status is updated the instant that I set the internal status message, right ?

     

    But to get back to the original problem, CommandBarButton.ToolTipText is exactly what I'm currently using in the Add-in, and I was looking for an equivalent among all the IVs stuff. Currently, it sort of looks to me as if the VSPackage creates the commands and actual controls from the .vsct declaration, but then only retains full control over the commands and forgets about some of the details of the actual controls in the UI, at which point the only one who's fully in control of the controls (and probably also watches the watchers or so) is the DTE object. Is that correct ?

    Having to fall back to DTE generally doesn't bother me that much; all the current users of my Add-in have had to live with it for some time already. ;-) It would just have been nice to do this within the new extensibility context.

     

    Thanks again

    Tuesday, October 26, 2010 2:14 PM
  • >As for QueryStatus, from all I've seen I figured this would actually be more appropriate, but the concept seemed a little strange to me, as QueryStatus looks pretty isolated - from what I understand, to set any control properties in QueryStatus based on what has happened in my logic, I would have to hold all relevant state information of my application internally and update it after every action, just in case some control's QueryStatus handler "comes along" and wants to know what's new for its control. Is that correct ?

     Well it is expected that someone that understands a command has the state to answer questions about it (text, is it enabled, is it visible, etc...)  If you store that somewhere or figure it out 'just in time' is up to you (though if figuring it out is expensive please do store it somewhere, QueryStatus is a VERY hot path and having a slow handler slows everything down and upsets users :)) QueryStatus will be invoked when the shell feels it needs to update the command UI state, there are several reasons this can happen, but in general yes you need to be able to answer command status requests.  In fact what you are doing in your Exec is sort of doing that, you are just using the OleMenuCommand object to 'store' the state, since QueryStatus is answered based in MPF based on teh various properties of that object (that is why QueryStatus is actually called BeforeQueryStatus in MPF and you use that time to set properties on the OleMenuCommand object, which will then be read off / returned by the actual QueryStatus that MPF is handling when it invokes your BeforeQueryStatus handler).

    >The command behind it is completely irrelevant, the button will be disabled at all times (which I was only able to achieve by explicitly disabling it every time in the QueryStatus handler, so I actually already have one).

    This is the second request I have seen recently for a 'label' type control in VSCT, which we currently don't support.  You can put this information elsewhere (like in your toolwindow if you have a tool window) or use something like the status bar to communicate it, but those solutions may not work for you.  We don't support a 'label' control so your idea of a disabled button that displays text is probably as close as you can get with the standard VSCT supported mechanisms.

    There is no exposure of the underlying data models that drive the command UI through any IVs type service (it is something we are lacking that I think would be nice, but alas have not convinced anyone in managmenet that we should do it :().  It isn't the VS package that creates the command objects and controls, it is the shell and we can do this without loading your package at all (hence the reason for the VSCT), otherwise we would need to load dozens of packages at startup just to build the command UI.  The DTE object isn't in control of anything per se, it just knows how to get at the (otherwise unexpose) underlying model that the command UI is built on top of, so yes using DTE you can change the command UI.

    Unforuntately there is no 'new extensibility' in the command space, all our resources were spent in 2010 just getting the UI layer over to WPF and not breaking anyone in the process (or breaking as few people as we could) or requiring them to change their existing code for commanding (QueryStatus/Exec) or VSCT files. It would be nice if we had new extensibility (like MEF based command interaction), but we don't as of now :(


    Ryan 

    • Marked as answer by TeaDrivenDev Tuesday, October 26, 2010 6:11 PM
    Tuesday, October 26, 2010 5:09 PM
  • Thank you again Ryan. I was hoping for an easier way, but at least now my playing field is marked, and I will try and make the best of it. ;-)

    Tuesday, October 26, 2010 6:11 PM