We had a scenerio where an 800+ line CFL routine was used to determine the optimal machine and materials. It looped through over 4000 options. Even though it was optimized and took only a second, that was still enough that we did not want it running in between every calculation. The user was agreeable to having a button that said “Click to Calculate” that would then run the optimization. We further wanted the button to disappear when it was up to date. This WIKI article describes the way we accomplished this.

I've created a simple product and pricing form for illustrative purposes. The product adds two numbers to produce a result. However, the result is only computed when the button is pressed. The pricing form looks like this:

The product consists of the following variables:

  • Quantity1 - The first number -
  • Quantity2 - The second number
  • Quantity3 - The result. (The formula for this is the key.)
  • ActionAdd - A yes/no variable that appears to be the “button” to track when it is clicked.

There are three other psuedo-variables, which are stored as properties on Quantity3. A variable can have as many properties as you want, and you don't declare them in advance. However, they can't have a formula. Unlike regular variables, though, you can write them using CFL. These psuedo-variables are:

  • LastResult - This stores the previous value of Quantity3
  • LastActionAdd - This stores the previous value of the ActionAdd.
  • IsDirty - This stores if changes have occurred since the last calculations.

All of the action to pull this off occurs in the formula for Quantity3 (the result). The CFL code for our example looks like this:

code_formatpascal

This routine checks if the value of ActionAdd has changed from the last calculation. If it has not changed, we return the current value but set that we might be dirty.

If it has changed, or was never defined, we compute the new one and store that for next time. declare CurrentActionAdd := ActionAdd; declare LastActionAdd := GetVariablePropertyValue(Quantity3, “LastActionAdd”, -1); if (LastActionAdd CurrentActionAdd) then we must have a change or undefined value

// Make sure we have a value stored
declare NewValue := Quantity1 + Quantity2;
// Save this value for next time
SetVariableProperty(Quantity3, "CurrentValue", NewValue);
// And save the setting for ActionAdd for next time
SetVariableProperty(Quantity3, "LastActionAdd", CurrentActionAdd);
// And clear that we are dirty
SetVariableProperty(Quantity3, "IsDirty", False);
// And return it as the current value;
NewValue;

else

// Store that we might be dirty
SetVariableProperty(Quantity3, "IsDirty", True);
// retrieve the stored value -
// make sure you do this last
GetVariablePropertyValue(Quantity3, "CurrentValue", -1);

endif;

The actual work (where you would put the real work) is the line
[[code_formatpascal|]]

  declare NewValue := Quantity1 + Quantity2;

<code>
Everything else is just managing the tracking.  The code is fairly well documented, but here is how it works:
  - Determine if the ActionAdd has changed by comparing it to the saved property.
  * If ActionAdd has changed, recompute the new value but also update all the tracking property variables.
  * If ActionAdd has not changed, return the last value but also set that you may be dirty (since a refresh was triggered by something).
The code for the pricing form looks like this.  I deleted some of the default properties, but Control will recreate those values anyway.

object DesignerLabel1: TDesignerLabel

  Left = 260
  Top = 73
  Width = 84
  Height = 25
  Alignment = taCenter
  AutoSize = False
  Caption = 'Result'
  Layout = tlCenter
  BorderInner = fsBump
  DisplayedInfo = pmdValue
  ParameterName = 'Quantity3'
  VariableName = 'Quantity3'
end
object DesignerLabel2: TDesignerLabel
  Left = 144
  Top = 69
  Width = 11
  Height = 24
  Caption = '+'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -19
  ParentFont = False
  DisplayedInfo = pmdText
end
object DesignerLabel3: TDesignerLabel
  Left = 232
  Top = 74
  Width = 11
  Height = 24
  Caption = '='
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -19
  ParentFont = False
  DisplayedInfo = pmdText
end
object DesignerSpinEdit1: TDesignerSpinEdit
  Left = 88
  Top = 70
  Width = 47
  Height = 21
  Max = 999999.000000000000000000
  Value = 1.000000000000000000
  TabOrder = 0
  DisplayedInfo = pmdValue
  ParameterName = 'Quantity1'
  VariableName = 'Quantity1'
end
object DesignerSpinEdit2: TDesignerSpinEdit
  Left = 169
  Top = 72
  Width = 47
  Height = 21
  Max = 999999.000000000000000000
  Value = 1.000000000000000000
  TabOrder = 1
  DisplayedInfo = pmdValue
  ParameterName = 'Quantity2'
  VariableName = 'Quantity2'
end
object DesignerCheckBox1: TDesignerCheckBox
  Left = 117
  Top = 114
  Width = 179
  Height = 42
  AlignmentVertical = avCenter
  Caption = '   Click to Calculate'
  Color = clHighlightText
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -16
  Font.Name = 'MS Sans Serif'
  Font.Style = [fsBold]
  ParentColor = False
  ParentFont = False
  TabOrder = 2
  UseCustomGlyphs = True
  DisplayedInfo = pmdValue
  ParameterName = 'ActionAdd'
  VariableName = 'ActionAdd'
  VisibleFormula = 'GetVariablePropertyValue(Quantity3, "IsDirty", False);'
end

end

One trick we did was to use make a checkbox look like a button. A button does not have a variable, so we couldn't use it to track when something changed. We had a checkbox, but it looked … inappropriate for the purpose. To make it look like a button, we did a few things:

  • Set the background color (Color)
  • Centered the text (Alignment and AlignmentVertical)
  • Hid the checkbox itself but setting the UseCustomGlyphs to true but not providing any image (glyph) to show.

Presto .. fake button! Finesse There are two more things we did to finesse this further. Added a Warning/Error. What happens if the user changes a value and then closes the form without recomputing. To avoid this, or at least notify the user, we added a check for this in the products warning message. If you are not familiar with this, it allows you to provide warning messages to the user when they click OK to close the form. The screen looks like this:

And the code we used was this: code_formatpascal

if GetVariablePropertyValue(Quantity3, “IsDirty”, False) then

''"The value was not calculated with the most recent inputs!"

''

endif;

Note, you could have also put this in the Error Message, in which case it would not allow the user to close the form until they recalculate first. It's your call on the right place for you. Hiding the Button. When the value was current, we wanted to hide the recalc button (which is really a checkbox). To do this we used the visibility formula of the checkbox and set it to only show when the result is “Dirty”, which is a software term for not-up-to-date. The code for this is: code_formatpascal

GetVariablePropertyValue(Quantity3, “IsDirty”, False);

That's it! Simple, right? Ok, perhaps not the simplest thing but once you get the technique down it's pretty easy to replicate it.

Contributor: Cyrious Software Date: 8/2/2013 Version: Control 4.5+

You could leave a comment if you were logged in.