• 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

    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

    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

    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

    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

    Watch Me Speak at TechEd 2007

    • 2 Comments

    This week (June 4-8) if you are at TechEd in Orlando, please come by to watch me speak:

    DEV326 Authoring Custom Controls and Components in Microsoft Windows Presentation Foundation at 8:30AM in N320 E.  The abstract is as follows:

    Make it easy for developers to use your Windows Presentation Foundation (WPF) Controls and stimulate leverage and reuse. Learn how to develop a WPF Custom Control and leverage Cider and Blend’s design time extensibility features to create a great user experience reusing this control in a designer. This session demonstrates how easy it is to develop a control with a rich design time interaction, including detecting design time, design time metadata and UI, adding actions to the context menu and property editing.

     

    The title is slightly misleading as the session was named prior to my involvement -- my whole talk will focus on how you can create and control the design time features for your WPF custom controls inside of Visual Studio "Orcas" and Blend.

  • Cloudy in Seattle

    Seen the Code Equivalent of this in Your Source?

    • 0 Comments
    I love Seth Godin's blog and I recently bought a book of his "Small is the New Big" which I'm really looking forward to reading.  Anyhow, he has just the most classic picture in this post.  I looked at that and thought, I've seen the code equivalent of that many times in the past (not at Microsoft) -- have you seen this in your code base?
  • Cloudy in Seattle

    Help us Name the Adorner "Placement" APIs

    • 0 Comments

    Looking back to my most recent article on adorners you'll recall that we had this API on AdornerPanel that allowed the Control Developer to specify where and with what size their design time adorners were positioned on the design surface:

    AdornerPanel.SetHorizontalStretch(_slider, AdornerStretch.Stretch);
    AdornerPanel.SetVerticalStretch(_slider, AdornerStretch.None);
    AdornerPanel.SetTargetSizeFactor(_slider, new Vector(1.0, 0));
    AdornerPanel.SetAdornerSizeFactor(_slider, new Vector(0, 1.0));
    AdornerPanel.SetAdornerOriginFactor(_slider, new Vector(0, -1.0));
    AdornerPanel.SetAdornerOriginOffset(_slider, new Vector(0, -3));

    This API set was actually restrictive in ways that we need to overcome so Richard Bailey took a crack at redesigning these APIs.  His first post on this is here and his second post is here

    As Richard mentions on his blog, we are looking for feedback on how to name the following classes (the following text is lifted directly from his blog):

    Contribution Group
    This is a collection of class instances that declare adjustments to the adorner size and position.

    Adorner Space
    The adorner space properties allow you to adjust the size off the adorner based on the desired size determined by the style size of the adorner.  Adorner position can be adjusted relative to the final computed size fo the adorner.  Size and position can also be adjusted in pixels.  Adorner space does nto scale when the designer zooms in/out.

    SizeRelativeToAdornerDesiredHeight(relativeTo, factor, offset)
    SizeRelativeToAdornerDesiredWidth(relativeTo, factor, offset)
    PositionRelativeToAdornerHeight(relativeTo, factor, offset)
    PositionRelativeToAdornerWidth(relativeTo, factor, offset)

    Render Space
    The render space properties allow you to adjust the size or position of the adorner relative to the adorned element and by an offset measured in pixels in thatcoordinate system.  Render space size and positions adjustments are affected by and transform that affects the render size and shape of the adorned element.

    SizeRelativeToContentRenderHeight(relativeTo, factor, offset)
    SizeRelativeToContentRenderWidth(relativeTo, factor, offset)
    PositionRelativeToContentRenderHeight(relativeTo, factor, offset)
    PositionRelativeToContentRenderWidth(relativeTo, factor, offset)

    Layout Space
    The layout space properties allow you to adjust the size or position of the adorner relative to the layout slot fo the adorned element and an offset measured in pixels of that coordinate system.  Layout space size and positions are afected by transforms that affect the render space of the adorned element parent.

    SizeRelativeToContentLayoutHeight(relativeTo, factor, offset)
    SizeRelativeToContentLayoutWidth(relativeTo, factor, offset)
    PositionAtContentLayoutSlot(relativeTo )
    PositionRelativeToContentLayoutHeight(relativeTo, factor, offset)
    PositionRelativeToContentLayoutWidth(relativeTo, factor, offset)

    Example
    An example of using these APIs for a grab handle that is positioned on the top right of the selected element (also stolen from Richards post):

    ContributionGroup placement = new ContributionGroup();
    placement.Add(new SizeRelativeToAdornerDesiredWidth(this, 1.0, 0));
    placement.Add(new SizeRelativeToAdornerDesiredHeight(this, 1.0, 0));
    placement.Add(new PositionAtContentLayoutSlot(null));
    placement.Add(new PositionRelativeToContentLayoutWidth(null, 1.0, 0));
    placement.Add(new PositionRelativeToAdornerWidth(this, 0, -3.0));
    placement.Add(new PositionRelativeToAdornerHeight(this, -1.0, 3.0));
    AdornerPanel.SetPlacement(this, placement);

    Now before you panic, we do plan to have an API that is easier to approach that will wrap this API.  You will be able to use this simple API for the most common types of adorner layout, use the ContributionGroups and Size* & Position* classes to further customize your adorners layout and finally, you can define your own Contributions and terms to create your own layout management of your adorners.

    Summary
    We're looking for names to the following:

    ContributionGroup
    Contribution

    SizeRelativeToAdornerDesiredHeight(relativeTo, factor, offset)
    SizeRelativeToAdornerDesiredWidth(relativeTo, factor, offset)
    PositionRelativeToAdornerHeight(relativeTo, factor, offset)
    PositionRelativeToAdornerWidth(relativeTo, factor, offset)

    SizeRelativeToContentRenderHeight(relativeTo, factor, offset)
    SizeRelativeToContentRenderWidth(relativeTo, factor, offset)
    PositionRelativeToContentRenderHeight(relativeTo, factor, offset)
    PositionRelativeToContentRenderWidth(relativeTo, factor, offset)

    SizeRelativeToContentLayoutHeight(relativeTo, factor, offset)
    SizeRelativeToContentLayoutWidth(relativeTo, factor, offset)
    PositionAtContentLayoutSlot(relativeTo )
    PositionRelativeToContentLayoutHeight(relativeTo, factor, offset)
    PositionRelativeToContentLayoutWidth(relativeTo, factor, offset)

    Please post your suggestions as a comment.

    Thanks for your help!

  • Cloudy in Seattle

    Detecting Design Mode

    • 11 Comments

    A while back, Brian posted an article about how we proposed to implement the equivalent to the Windows Forms Control.DesignMode property in Cider.

    In his follow up article, he talks about how we dropped the original proposal and worked with the WPF team to get a the new DesignerProperties class into WPF's PresentationFramework assembly.  This change was driven by the feedback he got on his blog -- your feedback makes a difference.

    So instead of looking up a DependencyProperty through the AppDomain data context by name, you can check the DependencyProperty defined in the DesignerProperties class.  A far better solution that both Cider and Sparkle will support.

    When the designer starts up, it will change the default value of the IsInDesignMode attached property from false to true by overriding its metadata.  For parts of the designer like the Property Browser or adorner layer that will host controls in a runtime context (as part of the design time UI), the IsInDesignMode property will be set to false on those visual trees.

    What that means is that whenever a control is created, it will be able to check whether or not it is in design mode and get back a value of true if that control was created by the designer.  This will, for the most part, be the correct and final value, unless that control is hosted in the Property Browser or Adorner context where the IsInDesignMode property will switch from true to false for that control when it is parented to its final visual tree.

    Getting the IsInDesignMode Property

    Here is a simple example of making a runtime versus design time decision using the DesignerProperties class:

        public class CustomButton : Button
        {
            public CustomButton()
            {
                if (System.ComponentModel.DesignerProperties.GetIsInDesignMode(this))
                {
                    Content = "In Design Mode";
                }
                else
                {
                    Content = "Runtime";
                }
            }
        }

    Note how this code can be run from the constructor.

    Setting the IsInDesignMode Property

    Isn't setting the IsInDesignMode property the responsiblity of the designer?  In most cases, yes... but not all cases. 

    Consider the situation where a control has design time adorners that popup a dialog when clicked.  Since the dialog is not in the visual tree of the adorner layer, it will get the default value of the IsInDesignMode property which is set to true when the designer starts up. 

    In this case, the control developer will want to set the IsInDesignMode property to false for that dialog so that the controls on that dialog operate in a runtime context.

    The DesignerProperties class has a SetIsInDesignMode property to accommodate this kind of scenario:

    System.ComponentModel.DesignerProperties.SetIsInDesignMode(element, newValue);

    Tracking Changes to the IsInDesignMode Property

    It is important to track changes to the IsInDesignMode property as its initial value may not be its final value -- the Property Browser Editor or Adorner Layer example mentioned above.

    For example, the value of the IsInDesignMode property when your custom control is instantiated by Cider will almost always be true.

    If that control is being used within the Cider Property Browser in an Extended Editor you've written, it will change to false when that control is added into the Property Browsers visual tree where the IsInDesignMode property is false.

    To track state changes of the IsInDesignMode property, override OnPropertyChanged.

  • Cloudy in Seattle

    3rd Party Hosting of Cider

    • 3 Comments

    One of the recurring questions I get is the following:

    Will Visual Studio’s WPF Designer be available as a reusable component outside of Visual Studio, like the Windows Forms designer is?

    I just updated the FAQ on Cider's Channel9 Wiki with the answer: http://channel9.msdn.com/wiki/default.aspx/Cider.FAQHostingCider

  • Cloudy in Seattle

    MonthCalendar Adorner Sample

    • 6 Comments

    Co-authored by Jim Nakashima and Timothy Wong.

    This post is a follow up to the post I had written showing an example of using adorners.  It will go through an example of writing the design time for an existing MonthCalendar control. 

    It covers a few things the last post didn't cover: opening up a ChangeGroup to modify the underlying property and providing undo/redo support, binding between the adorner and the Editing Model, and getting this to work on the June CTP all wrapped up in a more realistic sample.

    The Control Being Adorned
    First let's have a look at the Calendar control (internal Microsoft sample):

    MonthCalendarApp

    The actual control we are adorning really isn't important as we are simply changing the FontSize of this control.

    The Desired Adorner and Design Time Experience
    What we're trying to build is an adorner that is a slider that changes the FontSize of the adorned control relative to the slider position. Here are two screen shots showing different slider placements and the change in the font size:

    MonthCalendarDesignTimeFontSizeChangedAdornerExample

    Adding the Adorner to the Design Time
    We know from my previous post that the first thing we need to do is create an AdornerProvider subclass that will add the slider adorner when the control we are adorning gets selected.  In this sample, that will be the FontSizeSliderAdornerProvider:

    class FontSizeSliderAdornerProvider : PrimarySelectionAdornerProvider
    {
        . . .
    }

    This class derives from PrimarySelectionAdornerProvider which means that these adorners will only be activated when the control it adorns is the primary selection.

    When the control is selected, the FontSizeSliderAdornerProvider is instantiated and has its Activate() method called. 

    An important thing to note here is that since your AdornerProviders are Extensions, they can come and go, which means your Activate() method needs to be able to setup in any context and your Deactivate() method needs to clean everything up.

    The initalization of the adorner is shared between the constructor and the Activate() method. 

    The constructor will create an AdornerPanel, create and add a slider to it, and add that AdornerPanel to the Adorners collection.  This includes setting up how the adorner lays itself out relative to the control it adorns with all of the AdornerPanel set methods.  How these APIs work is beyond the scope of this post however I will point out the reason there is so many APIs is so that there is flexibility in terms of controlling the adorners position, stretching and scaling as the control resizes.  EventHandlers and data binding are also setup at this point.

    public FontSizeSliderAdornerProvider()
    {
        // Add slider control
       
    _slider = new Slider();
        _slider.Minimum = 8;
        _slider.Maximum = 24;

        // Layout
       
    AdornerPanel.SetHorizontalStretch(_slider, AdornerStretch.Stretch);
       
    AdornerPanel.SetVerticalStretch(_slider, AdornerStretch.None);
       
    AdornerPanel.SetTargetSizeFactor(_slider, new Vector(1.0, 0));
       
    AdornerPanel.SetAdornerSizeFactor(_slider, new Vector(0, 1.0));
        AdornerPanel.
    SetAdornerOriginFactor(_slider, new Vector(0, -1.0));
       
    AdornerPanel.SetAdornerOriginOffset(_slider, new Vector(0, -3));

        _myPanel.Children.Add(_slider);

        // handle the value changes of the slider control
       
    _slider.PreviewMouseLeftButtonUp += new System.Windows.Input.MouseButtonEventHandler(slider_MouseLeftButtonUp);
        _slider.PreviewMouseLeftButtonDown +=
    new System.Windows.Input.MouseButtonEventHandler(slider_MouseLeftButtonDown);
        _slider.DataContext =
    this;

        Binding sliderBinding = new Binding("FontSize");
       
    sliderBinding.Mode = BindingMode.TwoWay;
       
    _slider.SetBinding(Slider.ValueProperty, sliderBinding);

        Adorners.Add(_myPanel);
    }

    Activate() will save off the ModelItem which will be used later to modify a property on the adorned control.  It also syncs up the slider to the current value in the ModelItem's FontSize ModelProperty.

    protected override void Activate(ModelItem item, UIElement view)
    {
        _calendarModelItem = item;
        _calendarModelItem.PropertyChanged +=
    new System.ComponentModel.PropertyChangedEventHandler(_calendarModelItem_PropertyChanged);

        FontSize = (double)_calendarModelItem.Properties[AdornedMonthCalendar.FontSizeProperty].Value.GetCurrentValue();
    }

    Up to here we have created a FontSizeSliderAdornerProvider and we have set it up to display a slider above the control it will adorn with the same width as the control it's adorning when that control becomes the primary selection.  We now need to modify the FontSize property of the adorned control when the slider changes its value.

    Modifying the Underlying Property Through the Editing Model
    This is done by using a ChangeGroup and the ModelItem we saved off in the Activate() method.  The key thing that needs to be done is this:

        // Make the change on the property
       
    using (ChangeGroup change = _calendarModelItem.OpenGroup("FontSize change"))
        {
             _calendarModelItem.Properties[AdornedMonthCalendar.FontSizeProperty].SetValue(newValue);
            change.Commit();
        }

    For each commited ChangeGroup, there will be a undo/redo item added with the string passed in to OpenGroup().  That is, what occurs in a ChangeGroup will be undoable and redoable with that level of granularity.

    If a ChangeGroup is aborted or disposed before Commit() is called, the ChangeGroup will reverse the changes made to the model item.  Additionally, ChangeGroups are global to the designer.  That is, a ChangeGroup can be opened on any item in the designer, not just for the specific item you are modifying.  Finally, they support nesting but must be closed in order.

    The ModelItem is used to modify the underlying control's property -- although it is possible to get at the underlying control that will circumvent Cider and is not recommended or supported.

    So where do we call the code above to open the change group and modify the property?  Well, we could do it in the Slider's ValueChanged event but that won't provide the kind of granularity we desire.  Instead, we want to open the change group when the MouseLeftButtonDown event occurs and commit that change when the MouseLeftButtonUp event occurs.

    void slider_MouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {

        if (_calendarModelItem != null)
       
    {
           
    _batchedChange = _calendarModelItem.OpenGroup("FontSize Change");
       
    }
    }

    void slider_MouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
    {
       
    if (_batchedChange != null)
       
    {
           
    _batchedChange.Commit();
           
    _batchedChange.Dispose();
           
    _batchedChange = null;
        }
    }

    Data binding Between the Slider and the Editing Model
    So the question now is how the ModelItem FontSize ModelProperty gets updated when the Slider gets updated.  To accomplish this, we want to data bind between the slider and the ModelItem.  ModelItem implements INotifyPropertyChanged but that isn't sufficient since we need to data bind the Slider's value to the FontSize ModelProperty.  To solve this, I implemented INotifyPropertyChanged on the FontSizeSliderAdornerProvider and wrote a FontSize property which updates the ModelItem FontSize ModelProperty:

    public double FontSize
    {
       
    get { return _fontSize; }
       
    set
        {
           
    if (_fontSize == value)
            {
                
    return;
            }
            _fontSize = value;
            _calendarModelItem.Properties[
    AdornedMonthCalendar.FontSizeProperty].SetValue(_fontSize);
            OnPropertyChanged(
    "FontSize");
        }
    }

    As we saw previously, the Binding is setup in the constructor:

    Binding sliderBinding = new Binding("FontSize");
    sliderBinding.Mode =
    BindingMode.TwoWay;
    _slider.SetBinding(
    Slider.ValueProperty, sliderBinding);

    And the ModelItem.PropertyChanged is handled to raise a PropertyChanged event on the FontSizeSliderAdornerProvider:

    void _calendarModelItem_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
       
    if (String.CompareOrdinal(e.PropertyName, "FontSize") == 0)
        {
            FontSize = (
    double)_calendarModelItem.Properties[AdornedMonthCalendar.FontSizeProperty].Value.GetCurrentValue();
        }
    }

    Now, as the Slider is updated, a ChangeGroup is opened and the Editing Model is updated.  Because of the two way data binding, when undo/redo is clicked and the Editing Model changes, the Slider is also updated appropriately.

    Tying the AdornerProvider to the Control
    What we need to do, is the equivalent of the following:

    [Extension(typeof(FontSizeSliderAdornerProvider))]
    public class MonthCalendar : Control {. . . }

    This won't work for a number of reasons.  The first is that our scenario is that we are creating this design time for an existing control that we can't modify the code... which happens to be true for WPF controls since WPF ships much earlier than Visual Studio Orcas.

    The second is that the type FontSizeSliderAdornerProvider derives from PrimarySelectionAdornerProvider which is in Cider's PresentationDesignFramework assembly and ExtensionAttribute is defined in Cider's PresentationDesignCore assembly.  None of Cider's assemblies ship with the WPF redisributable.  That means that on the end user's machine, Cider's assemblies will not be available.

    The solution is that Cider will load loosely coupled Metadata Assemblies which will inject design time metadata into the system.  The design time code (i.e. FontSizeSliderAdornerProvider) will reside in a design time assembly which can have references to Cider assemblies.

    Since that mechanism isn't yet in place, we will cheat by creating a derived class which declaratively specifies the ExtensionAttribute -- note that this is not suitable solution, simply an interim solution until Metadata Asemblies and the Metadata Store are up and running in Cider.

    [Extension(typeof(FontSizeSliderAdornerProvider))]
    public class AdornedMonthCalendar : MonthCalendar
    {

    All that remains now is plopping this AdornedMonthCalendar on a Window loaded in Cider. (a file reference from the WPF Application to this design time assembly is used since project references and in project custom types are not yet supported in Cider).

    Making this Sample Work in the June CTP
    I've worked around the main limitations with the June CTP - using an ExtensionAttribute directly, using a file reference between the WPF Application and the assembly that contains the adorned control (no project references or in project custom types supported right now) so the main issue remaining is finding the Cider assemblies in the June CTP.

    They are installed into the same folder as Visual Studio under a Cider subfolder.  For example, on my machine, they are installed to: C:\Program Files\Microsoft Visual Studio 8\Common7\IDE\Cider.  In this example, the assemblies of interest are PresentationDesignCore.dll (for ExtensionAttribute) and PresentationDesignFramework.dll for all of the Adorner types.

  • Cloudy in Seattle

    Licensing in Cider

    • 5 Comments

    We're just finishing up the implementation of licensing in Cider, I'll let you know when it will be visible in a CTP. 

    What we did was implement the System.ComponentModel Licensing that was also used in Windows Forms.  Our feedback was that this is a sufficient model -- yeah it isn't great but it works and circumventing that licensing is sufficient to show intent.

    So how does licensing work in a nutshell?  In essence, it's as simple as follows:

    1. A control developer creates a licensed control. A control, a LicenseProvider and a license file. (potentially creates a derived License)
    2. An application developer instantiates this licensed control at design time by drag dropping the control from the toolbox onto the design surface.
    3. The LicenseProvider reads the license file and parses it to ensure the contents are valid.  Note that different features/capabilities can be encoded into the license file.
    4. If the license file is valid, the control will be created and a licx file added to the project.  If it isn't, control cannot be instantiated on the design surface -- this happen every time the control is drag dropped onto the design surface. 
    5. At compile time, the LC.exe tool will run and based on the licx file, add licensing data into the assembly using the licensed control.
    6. At runtime, the LicenseProvider gets the licensing data that was embedded into the assembly using the control and performs a runtime license check.
    7. If the runtime license check fails an exception will be thrown from the constructor of the licensed control.

    We provide a default implementation of the LicenseProvider called the LicFileLicenseProvider.  It is very simple, the license file is a file with a string that corresponds to:  "<type.FullName> is a licensed component.".  That is the license key.

    When a control that uses the LicFileLicenseProvider is instantiated at design time, a lic file with the aforementioned license key is checked.  At build time, that license key is embedded in the assembly (this is determined by the call to LicenseContext.SetSavedLicenseKey that the LicenseProvider makes) and at runtime when the control is instantiated, the license key is extracted from the calling assembly and verfiied by the LicFileLicenseProvider.

    There are a couple of good resources out there to fill in the gaps if you want to add licensing to your control.  Check out:

    http://windowsforms.net/articles/licensing.aspx

    http://www.myagent.dk/2005/02/net_licensing.html

    License Initialization and Cleanup in Cider
    In WPF, there isn't a concept of IDisposable so cleaning up the License in Dispose() won't work.  Additionally, there are situations where a control can be unloaded and reloaded multiple times.  So for licensing in WPF, the following pattern is recommended for calling LicenseManager.Validate() and License.Dispose():

    [LicenseProvider(typeof(JimsLicenseProvider))]
    public class CustomLicensedButton : Button
    {
       
    private License license = null;
       
    public CustomLicensedButton()
        {
            ValidateLicense();
           
    this.Loaded += new System.Windows.RoutedEventHandler(CustomLicensedButton_Loaded);
           
    this.Unloaded += new System.Windows.RoutedEventHandler(CustomLicensedButton_Unloaded);
       
    }

        void CustomLicensedButton_Unloaded(object sender, System.Windows.RoutedEventArgs e)
       
    {
           
    CleanupLicense();
       
    }

        void CustomLicensedButton_Loaded(object sender, System.Windows.RoutedEventArgs e)
       
    {
           
    ValidateLicense();
       
    }

        private void ValidateLicense()
       
    {
           
    if (license == null)
           
    {
                
    license = LicenseManager.Validate(typeof(CustomLicensedButton), this);
           
    }
       
    }

        private void CleanupLicense()
       
    {
           
    if (license != null)
           
    {
                
    license.Dispose();
                
    license = null;
           
    }
       
    }
    }

    Adding Licensed Controls Through XAML
    In this case, if the control is licensed but does not have a valid license file, the designer will not show the UI (Window, Page, UserControl etc) where it is being used, instead it will show a message indicating that the control could not be instantiated.

    If the license file is valid, the control will work but the licx file will not be added/updated based on the addition of a licensed control.  If the licx file does not contain the type for your control, the license key will not be embedded into the assembly using that control and the runtime license check will fail.

    The workaround is to drag drop a control from the toolbox onto any Windows, Page, UserControl etc. as the licx file is created/updated per project.  Alternatively, you can create the licx file by hand.

    Writing the Licx File
    The other difference between Windows Forms and Cider is that in Cider we don't re-write the licx file if has been modified by hand.  In VS 2005, you could delete a type key from the licx file and when the designer reloaded the UI it would ensure all of the keys for the licensed control used on that UI were in the licx.  This is no longer the case, the licx file is only created/updated when a control is drag dropped from the toolbox onto the designer surface.

    Partial Trust
    There is a bug in the current implementation that our Control Developer customers lamented at the last dev lab.  The bug is that licensing doesn't work in partial trust.  To give you an update, a bug has been filed on this and the initial prognosis is good although this is not something that is handled by the Cider team.

  • Cloudy in Seattle

    Shared Bytes, Private Bytes and Fixups

    • 0 Comments

    This post is actually a re-post of a post I did a little under year ago during PDC '05 after attending a talk by Rico Mariani and chatting with him afterwards.  The original post is here.

    The reason I thought to re-post this is I was talking to a couple of fellow Cider-tonions about some perf analysis work that was being done and I figured it would be a useful thing to share.  Here it is:

    Shared Bytes vs Private Bytes
    A shared byte is one that can be shared across multiple processes. A private byte cannot. So what bytes qualify as shareable? Unaltered pages of a dll where the backing file for that dll is not the page file but the dll itself.

    Anytime a page of code or data from a dll needs to change for a particular process, it is marked dirty and becomes private. This can occur on pages that have references to objects on the heap, pages that have code offsets that get modified after load time, or on pages in the data segment that change.

    A quick way to have almost all of your dll marked private is to have it rebased during load time.

    This concept mainly applies to NGEN'd images as non-NGEN'd images mainly consist of IL that will be JIT compiled on the loader heap and therefore will all be private. The IL is shared, but it is discarded after JIT anyhow. [addition: if a non-NGEN'd assembly gets rebased, the whole assembly will still be loaded and checked for fixups which can be a performance hit]

    It's important to realize that the benefit of shared bytes are just that: that they can be shared across multiple processes and their cost can be amortized across the number of processes using those bytes. For assemblies like the .Net Framework assemblies, having shared bytes is a clear benefit. That said, in non-sharing cases, optimizing for shared bytes may sub-optimize your code so caveat emptor.

    Fixups
    The term "fixups" refers to "fixing up" an address in the code to a new address. Since this modifies a page for a given process, any page that has a fixup, also becomes private.

    Reducing Private Bytes
    The following are some ways you can reduce the number of private bytes:

    • put code that will be private together such that the number of pages that need to be marked private is decreased
    • prevent rebasing
    • fix addresses so that they don't become a fixup

    String Freezing and Hard Binding
    String Freezing and Hard Binding are both great examples of how the concepts above are being applied.

    In .Net 2.0, you can mark an assembly such that it will freeze its string literals. What that means is that all of the strings in that assembly will be put in a separate segment (during NGEN) so that all of the references to string literals will not require a fixup.

    The reason strings need fixups is because the literals need to be wrapped in a string instance and the code then points to those instances.

    With string freezing, there is a benefit in that the string isn't duplicated in the literal and the string instance as well as the reduction in private pages. Note as well that those string instances are interned (with opt in/opt out in whibey see the article here) to avoid duplication.

    The downside of string freezing is that such an assembly cannot be unloaded -- because the reference to that string now resides in a segment of that dll instead of on the heap and code in other assemblies may be depending on that reference.

    Hard binding refers to another new NGEN feature where NGEN assumes that a reference from one assembly to another is always going to be there. That allows NGEN to hard code offsets from one assembly to another reducing the need for load time fixups.

    The downside is that any assemblies that are hard bound will be loaded at the same time as the referencing assembly. This is in order to guarantee that the desired load addresses are gotten.

  • Cloudy in Seattle

    The Results Are In

    • 2 Comments

    For those of you who attended the dev lab last week at Microsoft, you'll recall I handed out a questionnaire asking you how you would spend $100 across our extensibility feature set. 

    Well, the results are in!  Here is how you, our customer, decided how you would spend that money.  Note that some of the surveys went over budget (>$100) and some came in under budget (<$100) -- I normalized the results to $100 (hence the floats).

    Licensing 190.4353
    Access to other controls/components on the design surface 131.384
    Access to resources, style, data sources etc. 115.0248
    Adorners 111.3557
    Actions/Verbs 107.2425
    Custom Property Editors 103.9696
    Sub-properties 94.22967
    API for determining when a control is in design mode 91.37385
    Specify a toolbox icon 84.61041
    Custom Object Editor 76.09384
    Custom Collection Editors 75.26635
    Reusing Designer Components (editors, data binding picker and Property Grid) 73.42868
    Customizing Control Creation 67.02088
    Launch wizard on object creation 64.11646
    Specify additional assembly references to add to the project when a control is added to the design surface 61.77895
    Shadow a property at design time 57.33183
    Support for Cider/Sparkle Metadata 57.19479
    Default value initialization 48.14241

  • Cloudy in Seattle

    WPF RSS Reader Walkthrough

    • 0 Comments

    Wanted to point you to an article that one of our SDETs, Nikola Mihaylov, wrote -- it's an RSS reader built exclusively with XAML.  Very cool -- http://blogs.msdn.com/nikola/archive/2006/06/20/640262.aspx

     

  • Cloudy in Seattle

    Introducing the Orcas Cider Extensibility Team

    • 0 Comments

    Here is a picture of the extensibility team from a recent morale event we had with the Cider team.  We'll be bringing you experience of developing and using a 3rd Party Control.

    OrcasCiderExtensibilityCrew

    Pictured from Left to Right: Subhag Oak, Jim Nakashima (me), Tim Wong, and Juraj Hlavac (not pictured: Brian Pepin)

  • Cloudy in Seattle

    WPF Application Building Screencast

    • 0 Comments

    Recently a screencast by Filipe Fortes has been making its way around Microsoft.  Lots of great feedback so I thought I'd check it out.

    Wow, what a great demo, it's well presented, very fluid, and it shows the power of the WPF platform. 

    Having written Photo and Video management/editing/viewing software in the past, I have a great appreciation for how easy WPF makes it to write media rich applications and Filipe shows it off brilliantly.

    It's a great introduction to WPF, well worth the 20 minutes.

  • Cloudy in Seattle

    .Net Framework 3.0

    • 0 Comments

    A while back, I had an acquaintance of mine make the statement "Microsoft is dropping .Net".

    With the popularity .Net, why do you suppose he had that impression?

    It turned out he had confused WinFX with .Net and figured that WinFX was a replacement for .Net. An understandable confusion even though WinFX is built upon the .Net Framework and includes the .Net Framework in its redistributable.

    Well guess what?  To alleviate this problem, WinFX has now been renamed to the .Net Framework 3.0.  Check out Somasegar's blog entry about this:

    Additionally, a bunch of great new .Net Framework 3.0 resources have just come online. 

    And last but not least, check out the Cider Page: http://wpf.netfx3.com/content/cider.aspx

     

  • Cloudy in Seattle

    Your feedback is wanted: Cider Extensibility Requirements

    • 9 Comments

    On Cider, we're rapidly approaching a big planning phase where the outcome of that planning phase will the set of features that are going to be in and out of v1. 

    On my side, that means taking a hard look at the features associated with Control development, design time customization and extensibility in general.

    Here's a list of features stack ranked and listed by priority category (Must Have, High Want, Nice to Have). 

    Is this the right set of features and are these features prioritized correctly?

    Must Have

    1. Licensing
    2. Programmatic differentiation of  "design time" from runtime
    3. Custom Property Editors (think UITypeEditor)
    4. Accessing and editing sub-properties of a type
    5. Custom Object Editor (think ActiveX property sheets that allow object level editing, not just property editing)
    6. Access to standard items (resources, data sources, styles, templates)
    7. Access to other items/controls on the design surface
    8. Leverage Cider UI Components (Property Editors, Property Browser, collection editor and design time UI)
    9. Commands/Verbs/Smart Tags (adding commands/verbs to a context menu)
    10. Metadata discovery (decoupling metadata from the controls themsevles -- can be added later)
    11. Add controls to the toolbox at installation time and add references to the current project when that control is instantiated
    12. Run a configuration wizard when instantiating a control from the toolbox onto the design surface

    High Want

    1. Ability to replace stock editors with a custom editor (replace the string editor)
    2. Better and easier integration with the help system
    3. Able to add a custom control to any UI that presents a list of controls (i.e. data binding picker)

    Nice to Have

    1. In-place design time UI (adorners)
    2. Ability to create custom custom collection editors

    Your feedback is wanted and desired.  Please let me know what's missing and if any of the priorities don't resonate with your priorities.

    Thanks!

     

     

  • Cloudy in Seattle

    Better than the dream...

    • 4 Comments

    We just finished wrapping up the May CTP release of Cider and we're really excited about how far we've come since the Feb CTP!

    Of course, it is important to remember that these are still very early previews of the direction that we are taking Cider.  That said, we haven't added any new features, in fact we've cut some and have really shored up the behaviors and quality.  Check out the release notes.

    For this release, my main area of focus was serialization.  In the Feb CTP, unless you had a relatively simple XAML file, it probably didn't load.  Not true in the May CTP -- the support for XAML Load is greatly improved, have a look at some of these cool UIs loaded in Cider:

    healtcare mediamania welcomescreen HyperBar

    Click on them to see a larger version.

    Of course, there are some caveats -- these have been modified to work around my documented XAML Load Limitations

    The biggest issues you'll run into are:

    1. No in-project references are supported, and only file referenced assemblies are supported.  http://channel9.msdn.com/wiki/default.aspx/Cider.MayCTPProjectLimitations
    2. StaticResource references are not resolved in Application.Resources.  http://channel9.msdn.com/wiki/default.aspx/Cider.MayCTPResourceLimitations
    3. External (i.e. separate XAML file) ResourceDictionaries and MergedDictionaries are not supported.  http://channel9.msdn.com/wiki/default.aspx/Cider.MayCTPResourceLimitations

    Nonetheless, it's a very exciting release that's definetly worth checking out.  The May CTP is tied to the Windows Beta 2.  In order to try this out you'll need:

    Additionally, you may be interested in:

    Have fun and let me know what you think!  If your XAML file doesn't load, please post the project and description to the Cider Forum.

  • Cloudy in Seattle

    MIX06 Sessions Now Online

    • 2 Comments

    This years MIX06 sessions (all 52) are now online.

    Sessions here: http://sessions.mix06.com/

    More here: http://blog.mix06.com/blog/archive/2006/05/03/2367.aspx

    Mike Swanson has the breakdown.

  • Cloudy in Seattle

    Write a caption...

    • 16 Comments

    So jfo and I were talking lately...

    EPSN1142
  • Cloudy in Seattle

    Cider Adorners

    • 2 Comments

    Following up on my previous article on Cider Item Creation, I want to talk a bit about Adorners.  Brian Pepin introduces adorners in his article on User Interaction in Cider:

    Adorners are WPF elements that float on top of the design surface and track the size and location of the elements they are adorning. All of the UI Cider presents to the user, including snap lines, grab handles and grid rulers, is composed of these adorners. Adorners can contain any control, and an adorner can be associated with a task so they can participate in Cider’s commanding mechanism. Adorners can also get focus and be treated just like normal WPF elements, because that’s what they are.

    For example, looking at the following screen shot:

    The grid lines, the anchor lines, the grid information, the grab handles...  all are done with adorners. 

    One of the cool things is that Cider uses the same infrastructure that we expose as extensibility points for control developers.  This allows us to work with and get validation of the extensibility points we ship to customers.

    So what does the code look like to write one of these things? 

    I'll go over the high level details around writing a control that has 2 adorners.  Note that if you want to try this at home, you'll have to wait for the next CTP.  I'll be blogging about getting this sample working with the CTP when it becomes available.

    In the screen shot below, the Slider is an adorner on the button as is the little square in the upper right hand corner.  The slider adjusts the yellow gradient stop to be the position where the slider is and the little square pops a dialog when clicked.

    1) The first step is to decorate your Control such that it knows that there is an attached adorner.

    [Extension(typeof(SliderAdornerProvider))]
    public class SliderAdornedButton : Button
    {
    }

    The Extension attribute is used to attach the SliderAdorner to the SliderAdornedButton class. 

    2) Write the SliderAdornerProvider

    The SliderAdornerProvider is an Extension.  So what's an Extension?  Well, essentially, when Cider loads up, it has an ExtensionManager which loads all of the Extensions found in metadata.  This is an extensibility point for adding new functionality into Cider.  They are lightweight features or add ins which don't require much from Cider and are created and destroyed within a given context

    Each group of Extensions have an ExtensionServer which manages its Extensions and can request and publish services, listen to events etc.  ExtensionServers exist for the lifetime of Cider and are also a Cider extensibility point. 

    In this example, because the SliderAdornerProvider derives from PrimarySelectionAdornerProvider which has an associated ExtensionServer (AdornerProviderExtensionServer), we don't need to write one. 

        class SliderAdornerProvider : PrimarySelectionAdornerProvider
        {
             . . . 
         }

    PrimarySelectionAdornerProvider is a Cider provided class that activates the adorner when the control that the adorner is attached to is the primary selection on the design surface.

    The bulk of the work is in the constructor which creates two adorners and puts them in the adorner layer.  The first is a Rectangle, the second is a Slider.  The first step is to create the AdornerPanel:

            public SliderAdornerProvider()
            {
                // All adorners are placed in an AdornerPanel
                // for sizing and layout support.
                AdornerPanel myPanel = new AdornerPanel();

    A Rectangle is instantiated, its position is set and its added to the AdornerPanel. 

                // 10 pixels Rectangle adorner
                Rectangle rect = new Rectangle();
                rect.Fill = Brushes.Azure;
                rect.Stroke = Brushes.Blue;
                rect.Width = rect.Height = 10.0;
     
                // Inset the slider 5 pixels from the top edge of the
                // control.
                AdornerPanel.SetAdornerOriginOffset(rect, new Vector(5, 5));
                myPanel.Children.Add(rect);

    We now add a Task that is fired when the Rectangle adorner is clicked:

                // Add a task to the Rectangle adorner that is
                // fired when we click on it.
                ToolCommand command = new ToolCommand("OpenPopup");
                Task task = new Task();
                task.InputBindings.Add(new InputBinding(command, new ToolGesture(ToolAction.Click, MouseButton.Left)));
                task.ToolCommandBindings.Add(new ToolCommandBinding(command, OnOpenPopup));
                AdornerPanel.SetTask(rect, task);

    Essentially, the OnOpenPopup method will be called when the Rectangle adorner is left clicked.

    Next up is the Slider Adorner:

               // adding a slider
                Slider slider = new Slider();
                slider.Minimum = 0;
                slider.Maximum = 1;
                AdornerPanel.SetHorizontalStretch(slider, AdornerStretch.Stretch);
                AdornerPanel.SetVerticalStretch(slider, AdornerStretch.None);
                AdornerPanel.SetTargetSizeFactor(slider, new Vector(1.0, 0));
                AdornerPanel.SetAdornerSizeFactor(slider, new Vector(0, 1.0));
                AdornerPanel.SetAdornerOriginFactor(slider, new Vector(0, -1.0));
                AdornerPanel.SetAdornerOriginOffset(slider, new Vector(0, -3));
                myPanel.Children.Add(slider);

    Here you'll notice 6 calls to AdornerPanel methods that are setting up the resizing and repositioning behavior of the adorner relative to the control it adorns.  We're still working on these APIs, however the key things to notice are that the Slider adorner will stretch horizontally to always be the same size as the control it adorns, it will not stretch vertically and its positioned above the control it adorns.

    Now when the slider values change, I also want to update the control that is being adorned:

                // handle the slide
                slider.ValueChanged += new RoutedPropertyChangedEventHandler<double>(slider_ValueChanged);

    Finally, add it to the Adorner layer:

                // Finally, add our panel to the Adorners collection
                Adorners.Add(myPanel);
           }

    The event handlers for the adorner events are pretty straight forward (yes, yes it creates a new LinearGradientBrush every time, that can and should be optimized out) :

            void slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
            {
                UIElement adornedControl = AdornerPanel.GetAdornedElement((UIElement)sender);
                SliderAdornedButton button = adornedControl as SliderAdornedButton;
                if (button != null)
                {
                    LinearGradientBrush gradBrush = new LinearGradientBrush();
                    gradBrush.StartPoint = new Point(0, 0);
                    gradBrush.EndPoint = new Point(1, 1);
                    gradBrush.GradientStops.Add(new GradientStop(Colors.Red, 0.0));
                    gradBrush.GradientStops.Add(new GradientStop(Colors.Yellow, e.NewValue));
                    gradBrush.GradientStops.Add(new GradientStop(Colors.Green, 1));

                    button.Background = gradBrush;
                }
            }

            private void OnOpenPopup(object sender, ExecutedToolEventArgs args)
            {
                MessageBox.Show("Hello Cider");
            }

    That's it!  Your first adorner.

    One of the things this sample highlights is that adorners can be any class that derives from UIElement.  There is no adorner base class.  It also shows the reuse opportunities for adorners as well as the flexibility they provide in terms of providing a customized design time experience for your control

    Obviously there are a lot of details and functionality that was glossed over, I'll be drilling down into various parts of our extensibility mechanisms in the coming weeks.  Stay tuned.

     

  • Cloudy in Seattle

    Collections of Items in Resources

    • 0 Comments
    Recently, I was trying to figure out how to reuse resources within multiple collections all defined in WPF XAML.  For example, suppose I have the following:
     
    <MeshGeometry3D.Positions>
        <Point3D X="0" Y="0" Z="0"/>
        <Point3D X="5" Y="0" Z="0"/>
        <Point3D X="0" Y="0" Z="5"/>
    </MeshGeometry3D.Positions>
     
    What if I wanted to put the 3 Point3D instances in a resource and use them to populate a collection?  Hmmm.... how about this?
     
    <Window.Resources>
        <Point3D x:Key="PointA" X="0" Y="0" Z="0"/>
        <Point3D x:Key="PointB" X="5" Y="0" Z="0"/>
        <Point3D x:Key="PointC" X="0" Y="0" Z="5"/>

    </Window.Resources>
     
    <MeshGeometry3D.Positions>
        {StaticResource PointA}
        {StaticResource PointB}
        {StaticResource PointC}
    </MeshGeometry3D.Positions>
     
    Sure enough, that doesn't work.  When you have something like this:
     
    <SomeElement>
       {Binding ... }
    </SomeElement>
     
    The "{Binding...}" string actually gets sent to the SomeElement type converter instead of being treated like a markup extension. 
     
    So as jfo pointed out to me, I can do this:
     
    <Window.Resources>
        <Point3DCollection x:Key="MyPoints">
            <Point3D X="0" Y="0" Z="0"/>
            <Point3D X="5" Y="0" Z="0"/>
            <Point3D X="0" Y="0" Z="5"/>
        </Point3DCollection>
    </Window.Resources>
     
    <MeshGeometry3D.Positions="{StaticResource MyPoints}">
     
    Ok, not bad, but now, what if I want to share those points on multiple resources (I know this is getting contrived but I can imagine there being a reason to want to do this to avoid duplication in some scenario), I can't go back and do this:
     
    <Window.Resources>
        <Point3D x:Key="PointA" X="0" Y="0" Z="0"/>
        <Point3D x:Key="PointB" X="5" Y="0" Z="0"/>
        <Point3D x:Key="PointC" X="0" Y="0" Z="5"/>

        <Point3DCollection x:Key="MyPoints">
              {StaticResource PointA}
              {StaticResource PointB}
              {StaticResource PointC}
        </Point3DCollection>
        <Point3DCollection x:Key="MyOtherPoints">
              {StaticResource PointA}
              {StaticResource PointC}
        </Point3DCollection>
    </Window.Resources>
     
    So how would I do this in such a way that I can avoid duplicating those Point3D values? 
     
    Well, off to Chuck I go.  His answer?  Element Syntax for Markup Extensions:
     
    <Window.Resources>
        <Point3D x:Key="First" X="0" Y="0" Z="0"/>
        <Point3D x:Key="Second" X="5" Y="0" Z="0"/>
        <Point3D x:Key="Third" X="0" Y="0" Z="5"/>
        <Point3DCollection x:Key="MyPoints">
            <StaticResource ResourceKey="First"/>
            <StaticResource ResourceKey="Second"/>
            <StaticResource ResourceKey="Third"/>
        </Point3DCollection>
        <Point3DCollection x:Key="MyOtherPoints">
            <StaticResource ResourceKey="First"/>
            <StaticResource ResourceKey="Third"/>
        </Point3DCollection>
    </Window.Resources>
     
    I thought that was pretty cool.  I wasn't aware of that syntax.  Very useful.  I've also seen it with Binding where this:
     
    <TextBlock FontSize="12" Foreground="Red" Text="{Binding XPath=@ISBN}"/>
     
    Is replaced by this:

    <TextBlock FontSize="12" Foreground="Red">
        <TextBlock.Text>
            <Binding XPath="@ISBN"/>
        </TextBlock.Text>
    </TextBlock>
    Cool and useful!
  • Cloudy in Seattle

    Cider Item Creation

    • 5 Comments

    In Windows Forms, there were many occasions when you wanted to separate out your design time code from your runtime code.  For design time code you didn't want to execute at runtime, you would use a designer and for runtime code you didn't want to execute at design time (i.e. it did a bunch of database access) in most situations you could use the Control.DesignMode property.

    The problem with Control.DesignMode was that it depended on IComponent.Site being set for your control.  Unfortunately, there were cases when this wasn't true, most noteably in the constructor of that control.

    If you've seen Brian Pepin's articles on the Cider designer (part 1 here and part 2 here) you'll know that in Cider, we've gone away from IComponent.  This means that there isn't a Control.DesignMode property for WPF/Avalon controls.

    Instead, Cider's Extension architecture (again see Brian's articles) is used to add a CustomInstanceFactory to a control.  When Cider goes to instantiate that control, instead of calling "new" or "Activator.CreateInstance" it calls its CustomInstanceFactory instead.

    For example:

    [Extension(typeof(SliderAdornerInstanceFactory))]
    public class SliderAdornedButton : Button
    {
          private bool IsDesignMode = false;

          public bool SetIsDesignMode
          {
                
    get { return this.IsDesignMode; }
                
    set { this.IsDesignMode = value; }
          }
    }

    class SliderAdornerInstanceFactory : CustomInstanceFactory
    {

          public override object CreateInstance(Type type, params object[] arguments)
          
    {
                
    object instance = base.CreateInstance(type, arguments);
                
    SliderAdornedButton sab = instance as SliderAdornedButton;

                if (sab != null) sab.SetIsDesignMode = true;
                
    return instance; 
          
    }
    }

    In other words, at design time, if there is a Custom Instance Factory attached to an object, it is used to instantiate the object instead of the designer itself.  This extensibility point allows you to set a bool indicating that you are in design mode (as the sample code above shows) and do any other design time specific code.

    In fact, you could even return a design time specific instance of your control.  As an example, Cider currently uses this mechanism to provide our own design time version of Window since we can't use a real Window instance as it cannot be parented to another control.

    To summarize: Custom Instance Factories provide the ability to hook into the creation of your control at design time.

Page 7 of 9 (202 items) «56789