August, 2007

  • Cloudy in Seattle

    Adding Design Time Metadata to Cider and Blend

    • 9 Comments

    The extensibility points in Cider are all metadata based.  That means that you use attributes to attach design time features to your custom controls.  This was also true with the extensibility in Windows Forms however there is a significant difference. In Windows Forms, you might've done something like the following:

    [Designer(“CoolDesigner, CustomControl.Design")]
    public class CoolControl {
      [Category(“Layout”)] 
      [Browsable(true)]
      public string CoolTitle { … }
     …  }

    Notice how the attribute is added directly to the control.  This means that the assembly in which that attribute is defined needs to be present whenever that control is loaded.  In Windows Forms, this was ok since the design time attributes shipped with the .Net Framework redistributable.  In the Cider case, this is not true, Cider assemblies are shipped only with Visual Studio 2008 and cannot be redistributed by 3rd parties.

    Additionally, changing the metadata requires that the control be re-built.  Since WPF shipped their controls with Vista and Cider is shipping with Visual Studio 2008, we needed to be able to add metadata to existing controls.

    The solution is that at design time, lookups for metadata are made to a "MetadataStore".  That is, if you add attributes for a type to the MetadataStore, it will be as if you had added them declaratively -- but only at design time.

     The question then becomes: how do I add my 3rd party metadata to the MetadataStore?  We use a naming convention to load 3rd party "metadata assemblies".  A Metadata Assembly is simply an assembly that contains an implementation of Microsoft.Windows.Design.Metadata.IRegisterMetadata which is defined in Microsoft.Windows.Design.dll.

     When an assembly named "CustomAssembly" is loaded in Cider or Blend, the designer will look for an assembly named CustomAssembly.Design.dll.  If found, it will load it, instantiate any types that implement IRegisterMetadata and call the Register() method.  This is your opportunity to add metadata to the metadata store. 

     Cider will also look for an assembly named CustomAssembly.VisualStudio.Design.dll and load it (after loading CustomAssembly.Design.dll) for Cider specific metadata.  Likewise Blend will do the same for assemblies named CustomAssembly.Expression.design.dll.  This is important because if Cider only extensibility features such as Adorners and MenuActions were added in in a CustomAssembly.Design.dll, this would cause Blend to fail to load that assembly on machines that only have Blend installed as it will have references to Cider only assemblies such as Microsoft.Windows.Design.Interaction.dll or Microsoft.Windows.Design.Extensibility.dll -- only Microsoft.Windows.Design.Dll is shared between Cider and Blend.

    The implementation of the IRegisterMetadata.Register() method will look like this:

    internal class Metadata : IRegisterMetadata {
        public void Register() {
            AttributeTableBuilder builder = new AttributeTableBuilder();

      // To add an attribute such as this using the MetadataStore API
      // [Feature(typeof(OpacitySliderAdornerProvider))]
      // public class ButtonWithDesignTime { }

       builder.AddCustomAttributes(typeof(ButtonWithDesignTime), new FeatureAttribute(typeof(OpacitySliderAdornerProvider)));
       MetadataStore.AddAttributeTable(builder.CreateTable());  
       }
    }

    There is also an override that allows you to specify the property to add the attribute to.

    Note that you can still use attributes that are defined in the .Net runtime assemblies such as BrowsableAttribute and the like declaratively as you did in Windows Forms however you lose the ability to change your design time metadata independently of your control.

    This functionality is available for you to play with in Visual Studio 2008 beta 2 and later.

    For an example of a project with extensibility features that are wired up using the MetadataStore and Metadata Assemblies, please see http://blogs.msdn.com/jnak/archive/2007/08/10/recording-of-my-teched-talk-on-cider-wpf-designer-design-time-extensibility.aspx

     

  • Cloudy in Seattle

    How are Metadata Assemblies Found?

    • 7 Comments

    [Update: please see: http://blogs.msdn.com/jnak/archive/2007/10/24/bug-in-finding-metadata-assemblies.aspx about a bug we have in this area] 

    Adding on to my last post about adding metadata to Cider and Blend's MetadataStore by using Metadata Assemblies I thought it would be good to talk about how those Metadata Assemblies are found.

    If you have a control library assembly that is named MyControls.dll and it is not loaded from the GAC, the first place Cider will look for MyControls.Design.dll and then consequently MyControls.VisualStudio.Design.dll is in the same location that MyControls.dll is loaded.  This is a little tricky because many of us, myself included sometimes forgot to change the build output directory of the Metadata Assembly (i.e. MyControls.Design.dll) to be the location where MyControls.dll is loaded.

    The next location we will look, which typically will be the same location above is the location where MyControls.dll is referenced.  For example, if you have a test application that uses the controls in MyControls.dll, it will need to add a reference to MyControls.dll and the corresponding Metadata Assembly will be loaded from that location as well.  One situation where the location of MyControls.dll will be different from MyControls.Design.dll is when is loaded from the GAC.  References aren't ever made to files in the GAC, so we look at the location of the reference to find the location to search for Metadata Assemblies.

    Finallly, when MyControls.dll is loaded from the GAC, another location we will look is in the SDK folders which are defined by the entries in:

    • HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\AssemblyFolders

    You can add additional directories to search for by adding a key whose default value is the path you want to add to the above registry key.

    This is typically where 3rd Party Control Vendors will add an entry to point to the install location of their controls.  This also puts the assemblies in the .Net tab of the Add References dialog and adds controls in the assemblies found in the AssemblyFolders to the list of items in the Choose Toolbox Items dialog.

    Because we didn't want to have the .Net Tab of the Add References dialog or the Choose ToolBox Items Dialog to become overly cluttered with Metadata Assemblies, we also search a "Design" subfolder.  This is the recommended location for your Metadata Assemblies when using the SDK folders.

  • Cloudy in Seattle

    Recording of my TechEd talk on Cider (WPF Designer) Design Time Extensibility

    • 5 Comments

    For those of you who were at TechEd and missed my talk, you can see the recording at: http://msteched.com/viewrecordedsession.aspx?code=DEV326  Unfortunately, this is limited to TechEd attendees only as you need to login to that site.

    I have attached the slides and the sample from the session -- look for future postings that discuss each of the features covered in the talk.

    Note: You may have to update the references to the Cider assemblies that are installed as part Visual Studio 2008.  Microsoft.Windows.Design.dll, Microsoft.Windows.Design.Extensibility.dll, and Microsoft.Windows.Design.Interaction.dll.  They are located in the Visual Studio install directory under the Common7\IDE\PublicAssemblies subfolder.  For example:  C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PublicAssemblies

     This will work with Visual Studio 2008 beta 2 and later.

  • Cloudy in Seattle

    WPF Designer Design Time Extensibility -- Now that Visual Studio 2008 Beta 2 is out...

    • 4 Comments

    Our Cider extensibility points are finally pretty much locked down and all of the functionality you need to write a design time is available for you to play with.  A good starting point is my TechEd sample which is attached to my posting here: http://blogs.msdn.com/jnak/archive/2007/08/10/recording-of-my-teched-talk-on-cider-wpf-designer-design-time-extensibility.aspx

     Over the coming weeks I'll be blogging about the main design time features in Cider and all the cool things you can do.  Stay tuned!

  • Cloudy in Seattle

    Adding MenuActions (think DesignerVerbs) to Cider's Right Click ContextMenu

    • 2 Comments

    [Attached a sample project that shows the setup for metadata assemblies as well as adds a couple of MenuActions] 

    Now that you know how to add design time metadata vis Metadata Assemblies and how those Metadata Assemblies are found we can now move on to adding some extensibility features to your control.

    Given a custom control, in this case, I'm just going to use a simple derived button:

        public class ButtonWithDesignTime : Button
        {
            public ButtonWithDesignTime()
            {
            }
        } 

    When this ButtonWithDesignTime is loaded up in Cider and I right click on it, I want to add some items into the popup Context Menu so that it looks like this:

     

    Where the SetBackground group and the Blue and Cleared items are the ones I added.

    This menu items are called MenuActions and the grouping is called a MenuGroup.  These are classes in Cider's Microsoft.Windows.Design.Extensibility.dll under the Microsoft.Windows.Design.Interaction namespace.

    The other class in the namespace and assembly that is going to help us is the PrimarySelectionContextMenuProvider class which is the class that you derive from to add MenuActions when your control is the primary selection and is right-clicked.

    The implementation follows, you simply instantiate MenuActions, put them in a MenuGroup and add that MenuGroup to the Items property that is provided by the PrimarySelectionContextMenuProvider class you are deriving from.  Note that you can also add MenuActions directly to the Items collection.

        class CustomContextMenuProvider : PrimarySelectionContextMenuProvider
        {
            private MenuAction _SetBackgroundToBlueMenuAction = new MenuAction("Blue");
            private MenuAction _ClearBackgroundMenuAction = new MenuAction("Cleared");

            public CustomContextMenuProvider()
            {
                _SetBackgroundToBlueMenuAction.ImageUri = new Uri("pack://application:,,,/CustomControlLibrary.VisualStudio.Design;component/Images/live_logo.png", UriKind.Absolute);
                _SetBackgroundToBlueMenuAction.Execute += new EventHandler<MenuActionEventArgs>(SetBackgroundToBlue_Execute);

                _ClearBackgroundMenuAction.Execute += new EventHandler<MenuActionEventArgs>(ClearBackground_Execute);

                // Flyouts with actions
                MenuGroup backgroundFlyoutGroup = new MenuGroup("SetBackgroundsGroup", "Set Background");

                // if this is false, this group will not show up as a flyout but inline
                backgroundFlyoutGroup.HasDropDown = true;
                backgroundFlyoutGroup.Items.Add(_SetBackgroundToBlueMenuAction);
                backgroundFlyoutGroup.Items.Add(_ClearBackgroundMenuAction);
                this.Items.Add(backgroundFlyoutGroup);

                // Called right before this provider shows its tabs, opportunity to set states
                UpdateItemStatus += new EventHandler<MenuActionEventArgs>(CustomContextMenuProvider_UpdateItemStatus);
            }

            void CustomContextMenuProvider_UpdateItemStatus(object sender, MenuActionEventArgs e)
            {
                     // your opportunity to update state, make changes before your context menu items are shown
            }

            void ClearBackground_Execute(object sender, MenuActionEventArgs e)
            {
                ModelItem selectedControl = e.Selection.PrimarySelection;
                selectedControl.Properties[Control.BackgroundProperty].ClearValue();
            }

            void SetBackgroundToBlue_Execute(object sender, MenuActionEventArgs e)
            {
                ModelItem selectedControl = e.Selection.PrimarySelection;
                selectedControl.Properties[Control.BackgroundProperty].SetValue(Brushes.Blue);
            }
        }

     Now all you have to do is tie up this CustomContextMenuProvider to the ButtonWithDesignTime.  Since you already know how to add metadata, you simply have to do the following in your implementation of IRegisterMetadata:

                builder.AddCustomAttributes(typeof(ButtonWithDesignTime), new FeatureAttribute(typeof(CustomContextMenuProvider)));

    Which is essentially:

    [Feature(typeof(CustomContextMenuProvider))]
    public class ButtonWithDesignTime {. . .}

    Of course, as described in my metadata postings, you can't do that because that would require that you make references to the Cider assemblies from your runtime assembly.

  • Cloudy in Seattle

    Cider Extensibility Help Documentation

    • 2 Comments

    One of our User Education writers who takes care of much of the Cider Extensibility documentation that will go into MSDN helped make the very early and beta set of docs on these subjects available for all to read. 

    You'll find them attached to this blog post.

    You'll also find that aside from this blog and Brian and Soak's blog, there is very little little information out there on how to write things such as property editors, design time adorners, MenuActions (adding context menu items to the right click menu) etc. etc..  which makes these docs really helpful at this point in time.

    Enjoy.

Page 1 of 1 (6 items)