Skip to main content
RSS feed Subscribe to feed

 

How to Create a Virtual Column Producer

This tutorial describes how to make a virtual column producer that finds images using Google Image Search for each cell value in an input column. The source code is available in the SDK.

Overview

To create a virtual column producer, a virtual value producer is also required. Both are created by inheriting from CustomVirtualColumnProducer and CustomVirtualValueProducer respectively.

Background Information
  • Creating a Virtual Table Column Producer
    Some columns contain data that loads slowly, typically because of slow databases. Loading this kind of data into ordinary columns is impractical. Virtual column producers create columns that render only the cells actually viewed: Cell values are fetched on a need-to-view basis.
Prerequisites
  • Spotfire SDK\Examples\Extensions\SpotfireDeveloper.CustomVirtualColumnExample
    When using this TIBCO Spotfire SDK 2.2 example, be careful not to send too many requests to the Google™ Image Search provider. Excessive amounts of requests may be caused by scrolling and printing, and may result in Google™ blacklisting.

The Custom Virtual Column Example

Custom Column Virtual Column Example dialog

The custom virtual column developer example in the SDK contains a property for the number of images to get from Google Image Search, MaxNumberOfImages and a rotation flag indicating if the image should be rotated 180 degrees, Rotated.

Running the Custom Virtual Column Example

To see the custom virtual column, first launch the SDK example, open some data and create a table. Then open the Table Properties dialog, select the Virtual Columns tab, and the add the Custom Virtual Column Example producer. Finally, define the settings, and close the Table Properties dialog. Images are fetched from Google. If not found, the absence is indicated by a string message.

Custom Column Virtual Column Example result using default settings, but expanding row height in the Table Properties > Appearance tab

The Virtual Column Producer

The code below is a stripped down version of the developer example producer, demonstrating how to set up a virtual column producer to indicate its inputs and providing suitable triggers.

[Serializable]
[PersistenceVersion(4, 0)]
internal class MyProducer : CustomVirtualColumnProducer
{
        private const string credentialsKey = "MyProducer.Credentials";
        private readonly UndoableProperty<int> maxNumberOfImages;
        private readonly UndoableProperty<bool> rotate;
        
        #region Input identifier class

        //A class of this type should be present in each virtual column producer. In it   
        //identifiers for input collections are
        //created. See the more detailed description in the constructor.
        public new abstract class VirtualColumnInputIdentifiers : 
            CustomVirtualColumnProducer.VirtualColumnInputIdentifiers
        {
            public static readonly VirtualColumnInputIdentifier Input = 
                  CreateIdentifier("Input");
        }

        #endregion Input identifier class

        public MyProducer()
            : base()
        {
            CreateProperty(PrivatePropertyNames.MaxNumberOfImages, out 
               this.maxNumberOfImages, 2);
            CreateProperty(PrivatePropertyNames.Rotate, out this.rotate, false);

            //CreateInputs creates a VirtualColumnInputCollection and associates it with the 
            //identifier. The identifier
            //is declared in the nested class VirtualColumnInputIdentifiers that should be 
            //present in each VirtualColumnProducer 
            //(see above). Once created the identifier can be used to get and set inputs in 
            //that collection through the protected
            //GetInputs/SetInputs methods. Each output column is associated with a particular 
            //input collection by giving it
            //the identifier in ConfigureColumnsCore. CreateInputs can be called anytime 
            //before accessing the collection the first time
            //but the constructor is the recommended place.
            CreateInputs(VirtualColumnInputIdentifiers.Input);
        }            
       
        /// <summary>
        /// The selected input column signature.
        /// </summary>
        /// <remarks>
        /// This property represents the single input that is used to get the search string 
        /// in this example. 
        /// This will, in this particular example, map to
        /// a collection of signatures having Count == 1,
        /// describing the fact that this example has the same input column
        /// for producing all images. In the general case, one could represent
        /// several inputs per produced column and they could differ amongst the columns. 
        /// Once the input is set, the VirtualValueRequest.InputValues for a column will give 
        /// the inputs from the
        /// input collection that was associated with that column in configure columns. They 
        /// will be given in the 
        /// order they were set in the collection. If one of the inputs have been deleted an 
        /// invalid value will occur in its place.
        /// </remarks>
        public DataColumnSignature SelectedInput
        {
            get
            {
                // also see the explanation near CreateInputs in the constructor.
                VirtualColumnInputCollection signatures = 
                    GetInputs(VirtualColumnInputIdentifiers.Input);
                //the signature might be asked for before it has been set. 
                if (signatures.Count > 0)
                {
                    return signatures[0];
                }
                return null;
            }

            set
            {
                SetInputs(VirtualColumnInputIdentifiers.Input, new DataColumnSignature[] { 
                          value });
            }
        }

        /// <summary>
        /// Exposure of AvailableColumns for the dialog.
        /// </summary>
        internal VirtualColumnAvailableInputCollection AvailableInputSignatures
        {
            get { return AvailableInputs; }
        }

        // Implement this method to create the columns. It will be called each time the  
        // inputs of the producer changes
        // or the GetConfigureColumnsTriggerCore fires. The purpose is to add the metadata 
        // about each output column
        // that should be created and which inputs it has to the configurator. If this is the 
        // first call to the method those 
        // columns will be created. 
        // If there are already existing output columns those will be matched against those 
        // provided in the configurator, 
        // primarily on external id, and those no longer present will be removed, new ones 
        // added and those still remaining updated 
        // with the new information provided. 
        protected override void ConfigureColumnsCore(VirtualColumnsConfigurator configurator)
        {           
            for (int i = 0; i < this.MaxNumberOfImages; ++i)
            {
                string name = string.Format("Web Image {0}", i + 1);

                // The external ID would typically be a column name in an external data base,
                // but here it is simply the image index as a string. It is used to uniquely
                // recognize the column within the producer for the duration of its lifetime.
                string externalId = i.ToString();

                // Note that in this example all columns have the same input and thus the  
                //same input identifier but they could have separate ones.
                configurator.AddColumn(name, DataType.Binary, "image/bitmap", externalId, VirtualColumnInputIdentifiers.Input);
            }
        }

        protected override bool GetNeedsPromptingCore()
        {
            //determines if we need to prompt if someone calls us with promptmode allowed.
            return !AvailableInputs.Contains(SelectedInput);
        }

        protected override CredentialsModel GetCredentialsModelCore()
        {
            //Used as promptmodel for the credentials. Credentials are used to store 
            //passwords, usernames etc while the application is alive.
            //The corresponding ui should be registered in the
            //ViewRegistry (see the CustomVirtualColumnExampleAddIn) and will receive this 
            //object as input in its constructor. If the implementation does not have any 
            //credentials just ignore overriding this method.
            return 
             CredentialsModel<CustomVirtualColumnExampleProducer>.Create(CredentialsKey);
        }       
        
        /// <summary>
        /// In this example, there are three properties.
        /// Two of these
        /// affect the configuration of the columns.
        /// </summary>
        /// <returns></returns>
        protected override Trigger GetConfigureColumnsTriggerCore()
        {
            return Trigger.CreatePropertyTrigger(this,  
                    PrivatePropertyNames.MaxNumberOfImages);
        }

        /// <summary>
        /// Return a trigger that fires when there is a change in the settings that affect 
        /// the resultvalue delivered
        /// for a particular VirtualValueRequest.
        /// In this example, there are three properties.
        /// Only one of these
        /// affect the cache. The other two properties affects the configuration
        /// of columns.
        /// </summary>        
        protected override Trigger GetCacheKeyTriggerCore()
        {
            return Trigger.CreatePropertyTrigger(this, 
                PrivatePropertyNames.Rotate); 
        }

        //return an object that differs in equality when the settings that affect the value 
        //delivered for a particular VirtualValueRequest differs.
        protected override object GetCacheKeyCore()
        {
            return Rotate;
        }

        //serialization code
}

The Virtual Value Producer

The following code sample shows the key parts of the implementation of the CustomVirtualValueProducer in the developer example.

internal class MyValueProducer : 
    CustomVirtualValueWorker<CustomVirtualColumnExampleProducer>
{
    // Make a predicate that collects all work items with the same
    // input into a batch.
    protected override Predicate<VirtualValueRequest>   
        CreateBatchPredicateCore(VirtualValueRequest firstWorkItem)
    {
        if (!firstWorkItem.InputValues[0].IsValid)
        {
            return delegate(VirtualValueRequest other)
            {
                return false;
            };
        }
        else        
        {
            return delegate(VirtualValueRequest other)
            {
                DataValue otherValue = other.InputValues[0];
                return otherValue.IsValid &&
                    firstWorkItem.InputValues[0].Value.Equals(
                    otherValue.Value);
            };
        }
    }

    protected override void DoBatchWorkCore(IList<VirtualValueRequest> workItems)
    {
        VirtualValueRequest firstWorkItem = workItems[0];

        // If the input is invalid, or credentials check fail, 
        // bail out with an error message.
        if (error)            
        {
            workItem.SetErrorResult(Resources.ErrorMessage);
            workItem.ReturnItem();
            return;
        }

        // Read the rotate flag from the model.
        bool rotate = false;
        firstWorkItem.ReadModel<CustomVirtualColumnExampleProducer>(delegate
            (CustomVirtualColumnExampleProducer model)
            {
                rotate = model.Rotate;
            });

        // Calculate/load the value.
        if (success)
        {
            workItem.SetValidResult(searchString);
        }
        workItem.ReturnItem();
   }
}