none
Button status is not changing when it is used in a tool bar. RRS feed

  • Question

  • Hello,

    I have a number of buttons implemented in my VS2017 extension. Buttons are described in VSCT file and get registered like this:

    ...

    foreach(var c in commands) { var menuCommandID = new CommandID(c.commandSet, (int)c.id); var menuItem = new OleMenuCommand(c.handler, null, c.update, menuCommandID); commandService.AddCommand(menuItem); }

    ...

    As you can see, the command update handler is registered in the OleMenuCommand constructor. The update handler works as expected. When I open the menu, the menu button status corresponds to the extension state. However, the same button in a tool bar is not shown properly. For example, 'Run' button in the extension menu is shown as enabled, while the same button in a tool bar is shown as 'disabled'. I have two questions: 1. Why the toolbar button state is not the same as menu button state? 2. Is there a way to force status update for buttons in a tool bar?

    Thank you.

    Tuesday, July 30, 2019 6:08 PM

Answers

  • When your extension internal state for a command changes, you need to call IVsUIShell.UpdateCommandUI.

    Sergey Vlasov | Vlasov Studio | Innovative Visual Studio extensions and tools

    • Marked as answer by vlh7 Tuesday, August 6, 2019 6:44 PM
    Tuesday, August 6, 2019 3:37 AM
  • OK. I have figured it out. I just had to add another async method that I call from OnProcessComplete. I call SwitchToMainThreadAsync there before calling IVsUIShell.UpdateCommandUI. Like this:

            private async Task<int> WaitForProcAsync(Process ps)
            {
                Task<int> ret;
                ret = ps.WaitForExitAsync();
                await ret.AppendAction(OnProcComplete);
                return await ret;
            }
    
            private void OnProcComplete()
            {
                isRunning = false;
                isDebugging = false;
                NotifyUIAsync();
            }
    
            private async void NotifyUIAsync()
            {
                try
                {
                    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
                    IVsUIShell uiShell = Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell;
                    uiShell.UpdateCommandUI(0);
                }
                catch(Exception e)
                {
                    WriteMessage(e.Message);
                }
            }
    
    

    This seems to be working.

    Thanks again for pointing me in the right direction.

    Victor.

    • Marked as answer by vlh7 Tuesday, August 13, 2019 6:26 PM
    Tuesday, August 6, 2019 7:19 PM

All replies

  • Hi friend,

    Welcome to MSDN forum.

    Do you mean you have defined a button in vsct file which displayed in both menu and Tool bar? Please share more details about how you define them in vsct file.

    And as for OleMenuCommand class, please check this document .

    OleMenuCommand(EventHandler invokeHandler, EventHandler changeHandler, EventHandler beforeQueryStatus, CommandID id).

    Change handler will be called when command's status changes while beforeQueryStatus handler called when a client requests the command status. What's the content in your update handler and what's the result if you pass the c.update to changeHandler instead of beforeQueryStatus  handler?

    Does this issue also occur in a simple project, it could be better if you can share a reproducible sample so that we can check for it directly.

    Looking forward to your reply.

    Best Regards

    Lance


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Wednesday, July 31, 2019 10:28 AM
  • Hi Lance,

    Thank you for your response. There are many commands in the VSCT files. More than 50. They are defined differently, depending on the command function. Most definitions look something like this:

          <Button guid="guidPurifyCmdSet" id ="IDM_RUN" priority="0x100" type="Button">
            <Parent guid="guidPurifyPlusPackageCmdSet" id="PurifyMenuGroup"/>
            <Icon guid="guidPurifyPlusImages" id="pngRun" />
            <CommandFlag>DefaultDisabled</CommandFlag>
            <CommandFlag>DontCache</CommandFlag>
            <Strings>
              <ButtonText>Purify Run</ButtonText>
              <MenuText>&amp;Run</MenuText>
            </Strings>
          </Button>
    

    If I pass c.update to changeHandler instead of beforeQueryStatus handler, then command status update does not work. I change command status only in response to client request on status change. If there is no client request - there is no status change. There are different command handlers that define status update depending on the command function. For example, status update handler for the 'Run' command looks like this:

            private static void UpdateRun(object sender, EventArgs e)
            {
                ThreadHelper.ThrowIfNotOnUIThread();
                MenuCommand command = sender as MenuCommand;
                PurifyPlusToolWindow tw = FindToolWindow(command.CommandID.Guid, false, false);
                command.Enabled = projectInfo != null && projectInfo.IsValid;
                if(command.Enabled && tw != null && tw.Server != null)
                {
                    command.Enabled = !(tw.Server.IsRunning || tw.Server.IsDebugging);
                }
            }
    
    

    As you can see, command is enabled or disabled depending on the extension internal state.

    Given that the command state does get updated in the menu, I assume that it is implemented correctly. The fact that the same command gets updated in the menu but not in the toolbar looks like VS Shell bug. This is the same command after all. In some cases, toolbar commands do get updated when I perform some action, like collapsing and expanding branch in the project explorer. My guess is that VS shell forgets to update button bar elements in response to certain events, like command status change. But this is just a guess.

    Currently I do not have a simple example for reproducing this problem. I can implement one if you think this will be helpful.

    Best regards,

    Victor.

    Wednesday, July 31, 2019 8:49 PM
  • Hi vlh7,

    Thanks for your reply. I can't reproduce the same issue cause I'm not clear about your project structure and the way you define the vsct file. It could be f great help if you can share a simple sample which reproduces same issue, then I can reproduce it and try to find a workaround. Or to help report this issue to Product Team.

    Thanks for your understanding, looking forward to your reply!

    Best regards

    Lance


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Thursday, August 1, 2019 8:09 AM
  • Hi Lance,

    I have put together a relatively simple VSIX project that demonstrates the problem I am having with toolbars. I am not sure how to share it with you. Do you have a repository where I can upload it?

    Regards,

    Victor

    Tuesday, August 6, 2019 2:10 AM
  • Hi friend, you can share it by one-drive link or share it in a github free public repos. And please make sure you've deleted personal info in the sample before you share it.

    Best Regards

    Lance


    MSDN Community Support
    Please remember to click "Mark as Answer" the responses that resolved your issue, and to click "Unmark as Answer" if not. This can be beneficial to other community members reading this thread. If you have any compliments or complaints to MSDN Support, feel free to contact MSDNFSF@microsoft.com.

    Tuesday, August 6, 2019 2:14 AM
  • When your extension internal state for a command changes, you need to call IVsUIShell.UpdateCommandUI.

    Sergey Vlasov | Vlasov Studio | Innovative Visual Studio extensions and tools

    • Marked as answer by vlh7 Tuesday, August 6, 2019 6:44 PM
    Tuesday, August 6, 2019 3:37 AM
  • Thank you Sergey. This is exactly what I was looking for. This resolved the toolbar update issue in the smaller example I was using to demonstrate it. In the real VSIX project I am working on there is a problem with using IVsUIShell interface: The internal status change in my VSIX extension typically is not happening on the main thread. For example:

            private async Task<int> WaitForProcAsync(Process ps)
            {
                Task<int> ret;
                ret = ps.WaitForExitAsync();
                await ret.AppendAction(OnProcComplete);
                return await ret;
            }
    
            private void OnProcComplete()
            {
                isRunning = false;
                isDebugging = false;
                //IVsUIShell uiShell = Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell;
                //uiShell.UpdateCommandUI(0);
            }
    
    
    As you can see, the status change (isRunning = false) is not happening on the UI thread. It is happening inside the action associated with task attached to Process.WaitForExitAsync. Meanwhile UpdateCommandUI has to be invoked on the main thread. Of course, I can register a user message or a command that I can send to the main message loop from the action processor and call UpdateCommandUI when this message arrives on the UI thread. However, it seems clunky to me. Is there a better way to process status change that is not happening on the UI thread?

    Tuesday, August 6, 2019 7:05 PM
  • OK. I have figured it out. I just had to add another async method that I call from OnProcessComplete. I call SwitchToMainThreadAsync there before calling IVsUIShell.UpdateCommandUI. Like this:

            private async Task<int> WaitForProcAsync(Process ps)
            {
                Task<int> ret;
                ret = ps.WaitForExitAsync();
                await ret.AppendAction(OnProcComplete);
                return await ret;
            }
    
            private void OnProcComplete()
            {
                isRunning = false;
                isDebugging = false;
                NotifyUIAsync();
            }
    
            private async void NotifyUIAsync()
            {
                try
                {
                    await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
                    IVsUIShell uiShell = Package.GetGlobalService(typeof(SVsUIShell)) as IVsUIShell;
                    uiShell.UpdateCommandUI(0);
                }
                catch(Exception e)
                {
                    WriteMessage(e.Message);
                }
            }
    
    

    This seems to be working.

    Thanks again for pointing me in the right direction.

    Victor.

    • Marked as answer by vlh7 Tuesday, August 13, 2019 6:26 PM
    Tuesday, August 6, 2019 7:19 PM