Two example extended widgets are provided in the next two sections. The first, below, is a subclass of CwExtendedPrimitive, and the second, on page Example: a composite extended widget, is a subclass of CwExtendedComposite. The prefix 'Cew' is used to differentiate these new widgets from basic widgets. For simplicity, the examples do not include robust error-checking, nor does each widget provide a complete set of resources.
The CewEntryField widget, shown below, has a label on the left and a text box on the right. It allows a user to enter text into its text box, and it invokes a valueChanged callback if a new value is present when the user either hits the tab key or clicks on a different widget.
The extended widget is implemented using a CwForm as the primary
widget with CwLabel and CwText children. A
losingFocus callback on the CwText enables the widget to
test entered data, and possibly call any registered valueChanged
callbacks.
CwExtendedPrimitive subclass: #CewEntryField instanceVariableNames: 'label value valueChangedCallback labelWidget textWidget ' classVariableNames: '' poolDictionaries: '' createPrimaryWidget: theName parent: parent argBlock: argBlock "Private - Create and answer the basic widget that is the root of the widget hierarchy for the receiver's widget system." ^self parent createForm: theName, 'Form' argBlock: argBlock
createWidgetSystem "Private - Create the children of the receiver's primary widget which form the widget hierarchy." | pw | pw := self primaryWidget. self labelWidget: (pw createLabel: pw name , 'Label' argBlock: [:w | w labelString: self label]) manageChild.
self textWidget: (pw createText: pw name , 'Text' argBlock: [:w | w borderWidth: 1; value: self value]) manageChild. self textWidget addCallback: XmNlosingFocusCallback receiver: self selector: #losingFocus:clientData:callData: clientData: nil.
"Add form attachments for the two children." self labelWidget setValuesBlock: [:w | w topAttachment: XmATTACHFORM; leftAttachment: XmATTACHFORM; rightAttachment: XmATTACHNONE; bottomAttachment: XmATTACHFORM].
self textWidget setValuesBlock: [:w | w topAttachment: XmATTACHFORM; leftAttachment: XmATTACHWIDGET; leftWidget: self labelWidget; rightAttachment: XmATTACHFORM; bottomAttachment: XmATTACHFORM].
initializeResources "Private - Set the default extended widget resource values. This is sent during create with isCreated set to false. All extended resource variables should be initialized to default values here." label := String new. value := String new.
initializeAfterCreate "Private - Perform any widget-specific post-create initialization." self primaryWidget horizontalSpacing: 4; verticalSpacing: 4; borderWidth: 1. "Resources that involve the children of the primary widget have to be set after the children are created." self labelWidget labelString: label. self textWidget value: value.
label "Answer the value of the label resource. Resource type: String Default setting: '' Resource access: CSG Description: Specifies the string for the CewEntryField's label. This label is to the left of the CewEntryField's text box." ^label
label: resourceValue "Set the value of the label resource to resourceValue. Resource type: String Default setting: '' Resource access: CSG Description: Specifies the string for the CewEntryField's label. This label is to the left of the CewEntryField's text box." label := resourceValue. self isCreated ifTrue: [labelWidget labelString: resourceValue]
value "Answer the value of the value resource. Resource type: String Default setting: ' Resource access: CSG Description: Specifies the string for the CewEntryField's value. This value is displayed in the CewEntryField's text box if set using the #value: message, or if a valid string followed by a tab key is entered by the user. Note that while the user is typing, the string displayed in the text box might not be the same as the CewEntryField's value." ^value
value: resourceValue "Set the value of the value resource to resourceValue. Resource type: String Default setting: ' Resource access: CSG Description: Specifies the string for the CewEntryField's value. This value is displayed in the CewEntryField's text box if set using the #value: message, or if a valid string followed by a tab key is entered by the user. Note that while the user is typing, the string displayed in the text box might not be the same as the CewEntryField's value." value := resourceValue. self isCreated ifTrue: [textWidget value: resourceValue]
valueChangedCallback "Private - Answer the value of valueChangedCallback." valueChangedCallback isNil ifTrue: [self valueChangedCallback: OrderedCollection new]. ^valueChangedCallback
valueChangedCallback: resourceValue "Set the value of the XmNvalueChangedCallback resource to resourceValue. Resource type: OrderedCollection of CwCallbackRec Default setting: OrderedCollection new Resource access: C Callback reason: XmCRVALUECHANGED Calldata structure: CwValueCallbackData Description: Specifies the list of callbacks that is called when the value of the CewEntryField has changed. The reason sent by the callback is XmCRVALUECHANGED. The structure returned by this callback is CwValueCallbackData." valueChangedCallback := resourceValue.
labelWidget "Private - Answer the value of labelWidget." ^labelWidget labelWidget: aCwLabel "Private - Set the value of labelWidget to aCwLabel." labelWidget := aCwLabel.
textWidget "Private - Answer the value of textWidget." ^textWidget textWidget: aCwText "Private - Set the value of textWidget to aCwText." textWidget := aCwText.
losingFocus: widget clientData: clientData callData: callData "Private - Process a losing focus callback for the primary widget." | newValue | newValue := self textWidget value. "If the new value is different, invoke the entryField widget's valueChanged callback." self value = newValue ifTrue: [ self value: newValue; callCallbacks: XmNvalueChangedCallback callData: (CwValueCallbackData new value: newValue)].
The following code creates a CewEntryField instance, sets its name and label resources inside the create argBlock, and hooks a valueChanged callback to it. The new widget is shown in the diagram at the beginning of this section, on page Example: a primitive extended widget.
| shell entryField | shell := CwTopLevelShell createApplicationShell: 'CewEntryField Test' argBlock: nil.
entryField := CewEntryField createManagedWidget: 'entryField' parent: shell argBlock: [:w |
w label: 'Name :'; value: 'Your name here'].
entryField addCallback: XmNvalueChangedCallback receiver: self selector: #valueChanged:clientData:callData: clientData: nil.
shell realizeWidget. valueChanged: widget clientData: clientData callData: callData "Display the new value on the transcript." Transcript cr; show: 'Value changed to: ' , callData value printString.
The CewEntryField class can be subclassed to provide a slightly different extended widget by simply overriding one method, as in the following class definition for CewNumericEntryField:
CewEntryField subclass: #CewNumericEntryField instanceVariableNames: '' classVariableNames: '' poolDictionaries: '' losingFocus: widget clientData: clientData callData: callData "Private - Process a losing focus callback for the primary widget." | newValue | newValue := self textWidget value.
"Verify that the new value string represents a number. If it doesn't, reset the text widget and return." (newValue notEmpty and: [newValue conform: [:c | c isDigit]]) ifFalse: [ ^self value: value ]. "If the new value is different, invoke the entryField widget's valueChanged callback." self value = newValue ifTrue: [ self value: newValue; callCallbacks: XmNvalueChangedCallback callData: (CwValueCallbackData new value: newValue)]