visual-studio-extensionsvs-extensibilityvspackage

How to update the contents of a DynamicItemStart button inside a VS package


I'm doing a VS package that has a DynamicItemStart button inside a Menu. I don't have any problem by loading the contents of the Dynamic button when VS starts, but I'm trying to add more commands to its contents after some events like Open a project for example. I add the new commands to this "Place holder" button but I cannot see the Visual Studio UI updated. I have tried the UpdateUI Command:

Microsoft.VisualStudio.Shell.ServiceProvider serviceProvider = new Microsoft.VisualStudio.Shell.ServiceProvider(((DTE)Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.GetService(typeof(DTE))) as Microsoft.VisualStudio.OLE.Interop.IServiceProvider);
IVsUIShell uiShell = serviceProvider.GetService(typeof(SVsUIShell)) as IVsUIShell;
uiShell.UpdateCommandUI(1);

I'm calling this code at the BeforeQueryStatus event of the Button, but this doesn't work. Does anyone has achieved anything similar?

Any hints are really appreciated.

Edit 1: Here's the code I use to add the new commands

private void InitMRUMenu(ref OleMenuCommandService mcs)
{
  InitializeMRUList();
  if (_connectionsList != null)
  {
    int i = 0; 
    foreach (var conn in _connectionsList)
    {
        var cmdID = new CommandID(GuidList.guidAdvancedVSCTSampleCmdSet, this.baseMRUID + i);
        var mc = new OleMenuCommand(new EventHandler(OnMRUExec), cmdID);
        mruList.Add(conn.DisplayName);
        mc.BeforeQueryStatus += new EventHandler(OnMRUQueryStatus);
        mcs.AddCommand(mc);
        i++;
    }        
  }
}

private void OnMRUQueryStatus(object sender, EventArgs e)
{
  OleMenuCommand menuCommand = sender as OleMenuCommand;
  if (null != menuCommand)
  {
    int MRUItemIndex = menuCommand.CommandID.ID - this.baseMRUID;
    if (MRUItemIndex >= 0 && MRUItemIndex < this.mruList.Count)
    {
      menuCommand.Text = this.mruList[MRUItemIndex] as string;
    }       
  }
}

private void OnMRUExec(object sender, EventArgs e)
{
  //Do some actions
}

Solution

  • You need to add some code to the implementation of your IOleCommandTarget.QueryStatus, something like this (in C#):

    int IOleCommandTarget.QueryStatus(ref Guid cmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        if (cmdGroup == yourCommandSet)
        {
            if (prgCmds[0].cmdID >= yourDynamicId && prgCmds[0].cmdID < (yourDynamicId + 16)) // suppose you want a maximum of 16 dynamic items
            {
                int index = (int)prgCmds[0].cmdID - yourDynamicId;
    
                OLECMDTEXT.OLECMDTEXTF flags = OLECMDTEXT.GetFlags(pCmdText);
                if (flags == OLECMDTEXT.OLECMDTEXTF.OLECMDTEXTF_NAME)
                {
                    OLECMDTEXT.SetText(pCmdText, "yourText" + index);
                }
    
                prgCmds[0].cmdf = (uint)(OLECMDF.OLECMDF_ENABLED | OLECMDF.OLECMDF_SUPPORTED); // for example
            }
        }
    }
    

    Of course, the associated IOleCommandTarget.Exec must be also modified to correspond (using the same compute index).

    The OLECMDTEXT is a pretty hacky utility class that can be found on various places on the internet, here it is:

    /// <devdoc>
    /// Helper class for setting the text parameters to OLECMDTEXT structures.
    /// </devdoc>
    internal static class OLECMDTEXT
    {
        /// <summary>
        /// Flags for the OLE command text
        /// </summary>
        public enum OLECMDTEXTF
        {
            /// <summary>No flag</summary>
            OLECMDTEXTF_NONE = 0,
            /// <summary>The name of the command is required.</summary>
            OLECMDTEXTF_NAME = 1,
            /// <summary>A description of the status is required.</summary>
            OLECMDTEXTF_STATUS = 2
        }
    
        /// <summary>
        /// Gets the flags of the OLECMDTEXT structure
        /// </summary>
        /// <param name="pCmdTextInt">The structure to read.</param>
        /// <returns>The value of the flags.</returns>
        public static OLECMDTEXTF GetFlags(IntPtr pCmdTextInt)
        {
            if (pCmdTextInt == IntPtr.Zero)
                return OLECMDTEXTF.OLECMDTEXTF_NONE;
    
            Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT pCmdText = (Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT)Marshal.PtrToStructure(pCmdTextInt, typeof(Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT));
    
            if ((pCmdText.cmdtextf & (int)OLECMDTEXTF.OLECMDTEXTF_NAME) != 0)
                return OLECMDTEXTF.OLECMDTEXTF_NAME;
    
            if ((pCmdText.cmdtextf & (int)OLECMDTEXTF.OLECMDTEXTF_STATUS) != 0)
                return OLECMDTEXTF.OLECMDTEXTF_STATUS;
    
            return OLECMDTEXTF.OLECMDTEXTF_NONE;
        }
    
        /// <devdoc>
        /// Accessing the text of this structure is very cumbersome.  Instead, you may
        /// use this method to access an integer pointer of the structure.
        /// Passing integer versions of this structure is needed because there is no
        /// way to tell the common language runtime that there is extra data at the end of the structure.
        /// </devdoc>
        public static string GetText(IntPtr pCmdTextInt)
        {
            Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT pCmdText = (Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT)Marshal.PtrToStructure(pCmdTextInt, typeof(Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT));
    
            // Get the offset to the rgsz param.
            IntPtr offset = Marshal.OffsetOf(typeof(Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT), "rgwz");
    
            // Punt early if there is no text in the structure.
            if (pCmdText.cwActual == 0)
                return String.Empty;
    
            char[] text = new char[pCmdText.cwActual - 1];
            Marshal.Copy((IntPtr)((long)pCmdTextInt + (long)offset), text, 0, text.Length);
            StringBuilder s = new StringBuilder(text.Length);
            s.Append(text);
            return s.ToString();
        }
    
        /// <include file='doc\NativeMethods.uex' path='docs/doc[@for="OLECMDTEXTF.SetText"]/*' />
        /// <devdoc>
        /// Accessing the text of this structure is very cumbersome.  Instead, you may
        /// use this method to access an integer pointer of the structure.
        /// Passing integer versions of this structure is needed because there is no
        /// way to tell the common language runtime that there is extra data at the end of the structure.
        /// </devdoc>
        /// <summary>
        /// Sets the text inside the structure starting from an integer pointer.
        /// </summary>
        /// <param name="pCmdTextInt">The integer pointer to the position where to set the text.</param>
        /// <param name="text">The text to set.</param>
        public static void SetText(IntPtr pCmdTextInt, string text)
        {
            Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT pCmdText = (Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT)Marshal.PtrToStructure(pCmdTextInt, typeof(Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT));
            char[] menuText = text.ToCharArray();
    
            // Get the offset to the rgsz param.  This is where we will stuff our text
            IntPtr offset = Marshal.OffsetOf(typeof(Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT), "rgwz");
            IntPtr offsetToCwActual = Marshal.OffsetOf(typeof(Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT), "cwActual");
    
            // The max chars we copy is our string, or one less than the buffer size,
            // since we need a null at the end.
            int maxChars = Math.Min((int)pCmdText.cwBuf - 1, menuText.Length);
    
            Marshal.Copy(menuText, 0, (IntPtr)((long)pCmdTextInt + (long)offset), maxChars);
    
            // append a null character
            Marshal.WriteInt16((IntPtr)((long)pCmdTextInt + (long)offset + maxChars * 2), 0);
    
            // write out the length +1 for the null char
            Marshal.WriteInt32((IntPtr)((long)pCmdTextInt + (long)offsetToCwActual), maxChars + 1);
        }
    }