I'm running into a challenge with the GUI, I have a solution that I am considering putting into a module and posting on the package server. I'd like some feedback on whether this seems useful or if there's a better way.
tl;dr: Widgets get created in a specific order, which means that the ones created earlier cannot easily talk to the ones created later. I'm considering a mixin that allows asking a container for a specified child.
Long version:
I'm building a dialog with multiple controls on it, and I find myself wanting to let the various controls interact -- for example, the validator for the text field should be able to enable/disable the OK button and the OK button should be able to retrieve the value of the text field. This produces dependencies based on the order in which the various controls are instantiated, for example:
#lang racket/gui
(define diag (new dialog% ...))
(define text-fld
(new text-field%
[parent diag]
[callback (lambda (fld e)
(send ok-btn enable #t))] ; NOPE!
...)
(define ok-btn
(new button%
[parent diag]
[callback (lambda (btn e)
(displayln (send text-fld get-value)))] ; Ok
...)
The 'NOPE!' line is a compile time error because ok-btn
is still an undefined identifier at that point. Reversing the order of the declarations doesn't help, it simply means that the ok button's callback is invalid instead of the text-field's.
I have come up with two ways to solve this. The first is an ugly hack:
#lang racket/gui
(define ok-btn-forward-reference #f)
(define diag (new dialog% [parent #f] [label "ok"]))
(define text-fld
(new text-field%
[parent diag]
[label "text field"]
[callback (lambda (fld e)
(display "before setting, ok enabled? ")
(displayln (send ok-btn-forward-reference is-enabled?))
(send ok-btn-forward-reference enable #t)
(display "after setting, ok enabled? ")
(displayln (send ok-btn-forward-reference is-enabled?)))]))
(define ok-btn
(new button%
[parent diag]
[enabled #f]
[label "Ok"]
[callback (lambda (btn e)
(display "text field value is: ")
(displayln (send text-fld get-value))
(send diag show #f))]))
(set! ok-btn-forward-reference ok-btn)
(send diag show #t)
Here, I create an invalid forward reference that the text field's callback can use, then later set!
the reference to be valid. It works, but it's ugly.
Another solution would be to create a subclass of dialog%
that can locate and return the needed child:
#lang racket/gui
(define child-finder-dialog%
(class dialog%
(super-new)
(define/public (get-ok-button)
(define kids (send this get-children))
(list-ref kids 1))))
(define diag
(new child-finder-dialog%
[parent #f]
[label "ok"]))
(define text-fld
(new text-field%
[parent diag]
[label "text field"]
[callback (lambda (fld e)
(define ok-btn (send diag get-ok-button))
(display "before setting, ok enabled? ")
(displayln (send ok-btn is-enabled?))
(send ok-btn enable #t)
(display "after setting, ok enabled? ")
(displayln (send ok-btn is-enabled?)))]))
(define ok-btn
(new button%
[parent diag]
[enabled #f]
[label "Ok"]
[callback (lambda (btn e)
(display "text field value is: ")
(displayln (send text-fld get-value))
(send diag show #f))]))
(send diag show #t)
Obviously, if we were going to do this then it would be better to use a generic get-specific-child
method that can return any desired child instead of only the second one. Maybe its argument could be a predicate and it returns the first child that matches the predicate, the same way findf
works on lists. Or maybe there should be a mixin that can be applied to any class in order to add a "name-tag" field which the get-specific-child
method can search for. (Calling it 'name-tag' instead of 'id' in order to make it less likely to clash with existing fields.)
Also, it would be better if this was a mixin so as to make it easier to use. Perhaps with an interface such as child-finder<%>
that makes it easy to tell if the method is available.
What do people think? Is this a sensible thing to do?
EDIT: Another issue would be whether the search for the relevant child should be single-level, depth-first, or breadth-first.