1
votes

In Racket, how do I execute a button's callback function, when the function is in another file?

I have a file GUI.rkt with my GUI code:

#lang racket/gui
(provide (all-defined-out))
(define main (new frame% [label "App"]))
(new button% [parent main] [label "Click"]
    [callback (lambda (button event) (begin-capture)])

I have a main file, proj.rkt:

#lang racket
(require "GUI.rkt")
(define (begin-capture)
    ;do stuff
    ;...
    )

The compiler gives an error saying that begin-capture is an unbound identifier.

I know it is an unbound identifier because I didn't define the variable in the GUI file. The Racket documentation shows how to set the callback function in the object definition, but not outside of the definition. Ideally, I would like to access functions in the other file from my GUI, so that all my GUI code is in the GUI.rkt file.

1

1 Answers

1
votes

If "GUI.rkt" needs identifiers from "proj.rkt" then "proj.rkt" needs to provide them and "GUI.rkt" needs to require "proj.rkt", not the other way around. If the two modules need identifiers from each other then you almost certainly have a design problem.

If you want the GUI part of the program to be something that is required by other parts, then an obvious approach is for it to provide procedures to make things which take arguments which are things like callbacks:

(provide
 ...
 make-main-frame
 ...)

(define (make-main-frame ... capture-callback ...)
  (define main (new frame% [label "App"]))
  (new button% [parent main] [label "Click"]
       [callback (lambda (button event) (capture-callback))])
  ...
  main)

Note, however that I don't know anything about how people conventionally organize programs with GUIs, let alone how they do it in Racket, since I haven't written that sort of code for a very long time. The basic deal, I think, for any program with modules is:

  • you want the module structure of programs to not have loops in it – even if it's possible for Racket's module system to have loops, their presence in a program would ring alarm bells for me;
  • where a 'lower' module in the graph (a module which is being required by some 'higher' module in the graph) may need to use functionality from that higher module it should probably do by providing procedures which take arguments which the higher module can provide, or equivalent functionality to that.

The above two points are my opinion only: I may be wrong about hat the best style is in Racket.


A possible example

Here's one way of implementing a trivial GUI in such a way that the callback can be changed, but the GUI code and the implementation code are isolated.

First of all the gui lives in "gui.rkt" which looks like this:

#lang racket/gui

(provide (contract-out
          (selection-window (->* (string?
                                  (listof string?)
                                  (-> string? any))
                                 (#:initial-choice string?)
                                (object/c)))))

(define (selection-window name choices selection-callback
                          #:initial-choice (initial-choice (first choices)))
  ;; make & show a selection window for a number of choices.
  ;; selection-callback gets called with the choice, a string.
  (define frame (new frame% [label name]))
  (new choice%
       [parent frame]
       [label "state"]
       [choices choices]
       [selection (index-of choices initial-choice)]
       [callback (λ (self event)
                   (selection-callback (send self get-string-selection)))])
  (send frame show #t)
  frame)

So this provides a single function which constructs the GUI. In real life you'd probably want to provide some additional functionality to manipulate the returned object without users of this module needing to know about it.

The function takes a callback function as an argument, and this is called in a way which might be useful to the implementation, not the GUI (so in particular it's called with the selected string).

"gui.rkt" doesn't provide any way to change the callback. But that's OK: users of the module can do that, for instance like this:

#lang racket

(require "gui.rkt")

(define selection-callback-implementation
  (make-parameter (λ (s)
                    (printf "selected ~A~%" s))))

(selection-window "foo" '("red" "amber" "green")
                  (λ (s) ((selection-callback-implementation) s))
                  #:initial-choice "green")

Now the parameter selection-callback-implementation is essentially the callback, and can be adjusted to change what it is. Of course you can do this without parameters if you want, but parameters are quite a nice approach I think (although, perhaps, unrackety).