Gavin Stevens's Blog

the ramblings of another developer....

  Home  |   Contact  |   Syndication    |   Login
  34 Posts | 0 Stories | 57 Comments | 212 Trackbacks

News

Archives

Post Categories

Tuesday, June 5, 2007 #

Well, I just finished up some work in our CAB app to dynamically associate help with specific CAB smartparts.  I thought it was pretty cool in the end to be able to do this with a Builder Strategy and not have to hardcode all of the help references in each component.

So, there are a few parts to make this work.

The HelpComponent is a serializable type we defined to store help info.

The Help Provider Service will be used to load our stored HelpConfiguration file.

The Help Provider Strategy hooks up the help to the smartparts as they are created.

We also need an interface for the HelpProvider Service:

public interface IHelpProviderService
    {
        Dictionary<string, HelpComponent> HelpComponents { get;}
        void InsertItem(string key, HelpComponent item);
        bool Contains(HelpComponent item);
        bool Contains(string key);
    }
Now we define the serializable type:
    public enum HelpNavigator
    {
        AssociateIndex, Find, Index, KeywordIndex, TableOfContents, Topic, TopicId
    }
    [Serializable]
    public class HelpComponent
    {
        private HelpNavigator _navigationType;
        [XmlAttribute]
        public HelpNavigator NavigationType
        {
            get { return _navigationType; }
            set { _navigationType = value; }
        }
        private String _helpNamespace;
        [XmlAttribute]
        public String HelpNamespace
        {
            get { return _helpNamespace; }
            set { _helpNamespace = value; }
        }
        private String _helpKey;
        [XmlAttribute]
        public String HelpKey
        {
            get { return _helpKey; }
            set { _helpKey = value; }
        }
        private String _componentName;
        [XmlAttribute]
        public String ComponentName
        {
            get { return _componentName; }
            set { _componentName = value; }           
        }
      
        public override string ToString()
        {
            if (String.IsNullOrEmpty(_componentName))
            {
                if (base.ParseType() == null)
                    return this.GetType().ToString();
                else
                    return base.ParseType().AssemblyQualifiedName;
            }
            else
            {
                if (this.ParseType() != null)
                    return _componentName + "::" + this.ParseType().AssemblyQualifiedName;
                else
                    return _componentName;
            }
        }
    }
Ok, so this defines the Serializable type, so we can make an XML file like this:
<HelpConfiguration>
    <HelpComponent TypeName="AdminModule.Editors.NewRelationship, AdminModule"
                 NavigationType="TopicId"
                 HelpKey="722412"
                 HelpNamespace="Help\EntityExplorer.chm"/>
    <HelpComponent TypeName="AdminModule.Editors.NewBusinessRuleStep, AdminModule"
                 NavigationType="TopicId"
                 HelpKey="72248"
                 HelpNamespace="Help\EntityExplorer.chm"/>
</HelpConfiguration>
So, we build the Help Provider Service to Load the saved HelpConfiguration Data into a Dictionary
using System;
using System.Collections;
using System.Collections.Generic;
using System.Xml.Serialization;
    public class HelpProviderService : IHelpProviderService
    {
        private Dictionary<string, HelpComponent> helpComponents;
        public Dictionary<string, HelpComponent> HelpComponents
        {
            get { return helpComponents; }
            set {  }
        }
        public void InsertItem(string key, HelpComponent item)
        {
            HelpComponents.Add(key, item);
        }
        public bool Contains(HelpComponent item)
        {
            foreach (KeyValuePair<string, HelpComponent> comp in HelpComponents)
            {
                if (comp.Value == item)
                    return true;
            }
            return false;
        }
        public bool Contains(string key)
        {
            foreach (KeyValuePair<string, HelpComponent> comp in HelpComponents)
            {
                if (comp.Key == key)
                    return true;
            }
            return false;
        }
        public HelpProviderService()
        {
            helpComponents = new Dictionary<string, HelpComponent>();
        }
    }

 Now, when smartparts are created in the normal CAB manner:

 _propertyGrid = ModuleWorkItem.SmartParts.AddNew<PropertyGridToolWindow>();

The Builder Strategy will pick up the creation and dynamically hook-up the help

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using Microsoft.Practices.ObjectBuilder;

    public class HelpProviderStrategy:BuilderStrategy
    {
        private bool _canConfig;
       
        public HelpProviderStrategy()
        {
        }
        public override object BuildUp(IBuilderContext context, Type typeToBuild, object existing, string idToBuild)
        {
            WorkItem wi = context.Locator.Get<WorkItem>(new DependencyResolutionLocatorKey(typeof(WorkItem), null));
            IHelpProviderService _helpProvider = wi.Services.Get<IHelpProviderService>(false);
            if (_helpProvider != null)
            {
                if (_helpProvider.HelpComponents.Count > _helpcomponents.Count)
                { LoadHelpConfiguration(_helpProvider); }
                Control control = existing as Control;
                if (control != null)
                {
                    AddHelpSupport(control);
                }
            }
            return base.BuildUp(context, typeToBuild, existing, idToBuild);
        }
        private void AddHelpSupport(Control c)
        {
            HelpComponent help = _helpcomponents.Find(
                delegate(HelpComponent searchComponent)
                {
                    return searchComponent.ComponentName == c.Name &&
                        c.GetType().Equals(searchComponent.ParseType());
                });
          
            if (help == null)
            {
                help = _helpcomponents.Find(
                    delegate(HelpComponent searchComponent)
                    {
                        return c.GetType().Equals(searchComponent.ParseType());
                    });
            }
            //Assign Help Provider
            if (help != null)
            {
                HelpProvider provider = _providers[help.HelpNamespace];
                provider.SetHelpKeyword(c, help.HelpKey);
                provider.SetHelpNavigator(c, (System.Windows.Forms.HelpNavigator)Enum.Parse(typeof(System.Windows.Forms.HelpNavigator), help.NavigationType.ToString()));
            }
        }
}
}
Don't forget to add the Builder Strategy to you root workitem: 
        protected override void InitializeCommonStrategies(Builder builder)
         {
             base.InitializeCommonStrategies(builder);
......
             builder.Strategies.AddNew<HelpProviderStrategy>(BuilderStage.Initialization);
        }

You may not be able to just copy/paste all the code here and make it work, but if you can follow the example you should get a pretty good idea of how to implement this dynamic help configuration strategy in CAB.

Gavin