首页 > 解决方案 > 在 Racket 中,当函数在另一个文件中时,如何执行按钮的回调函数?

问题描述

在 Racket 中,当函数在另一个文件中时,如何执行按钮的回调函数?

我有一个带有我的 GUI 代码的文件 GUI.rkt:

#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)])

我有一个主文件 proj.rkt:

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

编译器给出一个错误,指出 begin-capture 是一个未绑定的标识符。

我知道这是一个未绑定的标识符,因为我没有在 GUI 文件中定义变量。Racket 文档展示了如何在对象定义中设置回调函数,而不是在定义之外。理想情况下,我想从我的 GUI 访问另一个文件中的函数,以便我所有的 GUI 代码都在 GUI.rkt 文件中。

标签: modulecallbackschemelispracket

解决方案


如果"GUI.rkt"需要标识符,"proj.rkt""proj.rkt"需要provide它们和"GUI.rkt"需要require "proj.rkt",而不是相反。如果这两个模块需要彼此的标识符,那么您几乎肯定会遇到设计问题。

如果您希望程序的 GUI 部分成为require其他部分的东西,那么一个明显的方法是它提供过程来制作带有参数的东西,例如回调:

(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)

但是请注意,我对人们通常如何使用 GUI 组织程序一无所知,更不用说他们如何在 Racket 中执行此操作了,因为我已经很长时间没有编写那种代码了。我认为,对于任何带有模块的程序来说,基本的交易是:

  • 你希望程序的模块结构中没有循环——即使 Racket 的模块系统可能有循环,它们在程序中的存在也会给我敲响警钟;
  • 其中图中的“较低”模块(图中require的某个“较高”模块正在使用的模块)可能需要使用该较高模块的功能,它可能应该通过提供接受较高模块可以的参数的过程来实现提供或与之等效的功能。

以上两点只是我的观点:我可能错了帽子最好的款式是在球拍。


一个可能的例子

这是实现普通 GUI 的一种方法,可以更改回调,但 GUI 代码和实现代码是隔离的。

首先,gui 的生活"gui.rkt"看起来像这样:

#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)

因此,这提供了一个构建 GUI 的函数。在现实生活中,您可能希望提供一些额外的功能来操作返回的对象,而不需要该模块的用户知道它。

该函数将回调函数作为参数,并且以可能对实现有用的方式调用它,而不是 GUI(因此特别是使用选定的字符串调用它)。

"gui.rkt"不提供任何更改回调的方法。但这没关系:模块的用户可以这样做,例如:

#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")

现在参数selection-callback-implementation本质上是回调,并且可以调整以改变它是什么。当然,如果您愿意,您可以不使用参数来执行此操作,但我认为参数是一种非常好的方法(尽管可能是不可靠的)。


推荐阅读