You can create a derived settable attribute when you want users to edit a convenient display value while MDriven stores and updates one or more underlying attributes; this is for modelers building input fields in MDriven Designer.
A derived settable attribute has two expressions:
- An OCL derivation expression that returns the value to display.
- An EAL OclSet expression that handles a value entered by the user and writes it back to the source attributes.
For example, you can show a time as 09:30 in a String attribute while storing it as a DateTime value. When the user changes the text to 10, the OclSet expression can convert that input to 10:00 and update the stored time.
When to use a derived settable attribute
Use a derived settable attribute when the value that is convenient to enter is not the same as the value you persist. It lets you keep conversion and update rules in the model instead of duplicating them in each UI.
Typical uses include:
- Formatting a persisted value for entry, such as a
DateTimeshown as a time String. - Accepting more than one input format, such as decimal values written with either
,or.. - Editing a calculated companion value and updating the persisted values from it.
- Splitting a composite user value into separate stored values. This pattern is also called reverse derivation.
A normal derived attribute is read-only: its OCL expression calculates a value. A derived settable attribute adds an OclSet expression, so writing to the derived attribute updates its source values.
How it works
The OCL expression defines the read direction:
source attributes â displayed derived value
The OclSet expression defines the write direction:
entered value (vInputParameter) â source attributes
vInputParameter is the value supplied when the derived attribute is set. In a String-based input attribute, it is the text entered by the user.
The OclSet expression must assign the result to the persisted attributes that are the source of the derivation. Do not attempt to assign the derived attribute to itself.
Configure the attribute
- Create an attribute with the type required by the UI. For a formatted time field, create a nullable String attribute such as
FromTimeText: String?. - In the attribute's Property Inspector, set AttributeMode to the mode for a derived settable attribute.
- Enter the expression used to display the value in the OCL or derivation expression setting.
- Enter the expression used when a value is assigned in OclSet. The entered value is available as
vInputParameter. - Keep the actual stored values as separate attributes. In the example below,
FromTimeandHoursare the stored source attributes; the text attributes are input and display helpers. - Test each permitted input path. If users can edit three related fields, verify that editing each one updates the other two fields as intended.
For the broader behavior and invalidation of derived values, see Training:Derived attributes & associations. For overriding derivations in subclasses, see Documentation:Derivation expressions.
Example: enter a time and duration in different ways
Consider an object that stores:
| Attribute | Type | Purpose |
|---|---|---|
FromTime
|
DateTime
|
Persisted start time. |
Hours
|
Decimal
|
Persisted duration in hours. |
Date
|
Derived DateTime
|
Easy access to a date held by a neighboring class. |
EffectiveToTime
|
Derived DateTime
|
A companion value to FromTime, used for symmetric access to the time information.
|
Expose these derived settable String attributes for input:
| Attribute | What the user can enter | What is updated |
|---|---|---|
FromTimeText
|
A start time, such as 09:30 or 9
|
FromTime
|
HoursText
|
A duration, such as 1,50 or 1.50
|
Hours
|
ToTimeText
|
An end time, such as 11:00
|
Hours when a start time exists; otherwise FromTime
|
With FromTime = 09:30 and Hours = 1.50:
FromTimeTextdisplays09:30.HoursTextdisplays1,50.ToTimeTextdisplays11:00.
If the user edits ToTimeText to 12:00, the OclSet expression recalculates Hours as 2.50. If no start time exists, entering an end time instead calculates FromTime from the existing duration.
Support function: convert entered text to a time
Add a helper function named StringToTime() to convert the entered String. It accepts a short hour-only value such as 9 by adding minutes, and it uses a fixed date so that the current date is not carried into a value that represents a time of day.
if aText.length <= 2 then
DateTime.Parse('1900-01-01 ' + aText + ':00')
else
DateTime.Parse('1900-01-01 ' + aText)
endif
For example, StringToTime('9') produces a value representing 09:00, and StringToTime('09:30') produces a value representing 09:30. The date 1900-01-01 is a deliberate fixed-date workaround; it prevents the current date from becoming part of the entered time.
FromTimeText
Define FromTimeText as String?.
OCL
if self.FromTime.notNull then
self.FromTime.formatDateTime('HH:mm')
else
String.nullValue
endif
This expression displays FromTime in 24-hour HH:mm format. If FromTime is null, it returns a null String instead of formatting a missing value.
EAL (OclSet)
self.FromTime := self.StringToTime(vInputParameter)
This expression converts the entered text and stores the result in FromTime.
HoursText
Define HoursText as String?.
OCL
self.Hours.ToString('N2').Replace('.', ',')
This displays the duration with two decimal places and replaces a dot with a comma. For example, a value of 1.5 is displayed as 1,50.
EAL (OclSet)
self.Hours := Decimal.Parse(vInputParameter.Replace(',', '.')).Round(2)
This accepts either decimal separator. For example, both 1,5 and 1.5 are parsed and stored rounded to two decimal places.
ToTimeText
Define ToTimeText as String?. This value is calculated from the start time and duration, but it can also be entered to update the underlying values.
OCL
if self.FromTime.notNull then
self.FromTime.AddHours(self.Hours.Value.ToDouble).formatDateTime('HH:mm')
else
String.nullValue
endif
When FromTime exists, this adds Hours and displays the resulting end time. For example, 09:30 plus 1.50 hours displays as 11:00.
EAL (OclSet)
if self.FromTime.notNull then
self.Hours := Decimal.Create(self.StringToTime(vInputParameter).Subtract(self.FromTime).TotalHours)
else
self.FromTime := self.StringToTime(vInputParameter).AddHours(-self.Hours.ToDouble);
0
endif
If FromTime exists, entering an end time recalculates Hours. If it does not exist, the expression works backwards from the entered end time and the existing duration to calculate FromTime. The final 0 completes that branch of the expression.
Design and testing checklist
- Keep one clear source of truth. In this example, persist
FromTimeandHours; derive the text fields from them. - Make the OCL and OclSet expressions agree. If OCL formats hours to two decimals, OclSet should store the value at the same precision.
- Handle missing source values explicitly. The time expressions return
String.nullValuewhen no start time is available. - Decide which input formats you support, then test each format. This example accepts both comma and dot decimal separators and accepts an hour-only time value.
- Test all edit paths. Enter a start time, duration, and end time independently, then confirm that the resulting persisted values and displayed companion fields are correct.
- Treat parsing failures as an input case that needs an explicit model or UI decision. The expressions shown use
DateTime.ParseandDecimal.Parse, so entered text must be parseable by those calls.
Related patterns
Use Documentation:Derived settable associations when the value being read and written is an association end rather than an attribute. See Documentation:Attribute for the distinction between persisted, transient, derived, and derived settable attributes. If your goal is to change a stored attribute's data type, use the approach described in Documentation:Attribute or Data Type Conversion rather than treating a derived helper attribute as a permanent conversion.
