一、窗口编程——Racket图形界面工具包

1 窗口

窗口工具箱提供了GUI程序的基本构建块,包括框架(顶层窗口)、模式对话框、菜单、按钮、复选框、文本字段和单选按钮,这些都是类。

有关Racket中类和接口的介绍,请参见《类和对象(Classes and Objects)》。

1.1 创建窗口

要创建新的顶级窗口,请实例化frame%类:

; Make a frame by instantiating the frame% class
(define frame (new frame% [label "Example"]))
 
; Show the frame by calling its show method
(send frame show #t)

内置类为处理GUI事件提供了各种机制。例如,在实例化button%类时,提供一个事件回调过程,当用户单击该按钮时调用该过程。下面的示例程序创建一个带有文本消息和按钮的框架;当用户单击按钮时,消息将更改:

; Make a frame by instantiating the frame% class
(define frame (new frame% [label "Example"]))
 
; Make a static text message in the frame
(define msg (new message% [parent frame]
                          [label "No events so far..."]))
 
; Make a button in the frame
(new button% [parent frame]
             [label "Click Me"]
             ; Callback procedure for a button click:
             [callback (lambda (button event)
                         (send msg set-label "Button click"))])
 
; Show the frame by calling its show method
(send frame show #t)

程序员从不直接实现GUI事件循环。相反,窗口系统会自动将每个事件从内部队列中拉出来,并将事件分派到适当的窗口中。调度调用窗口的回调过程或调用窗口的某个方法。在上面的程序中,每当用户单击【Click Me】时,窗口系统自动调用按钮的回调过程。

如果一个窗口接收到多种类型的事件,那么这些事件将被调度到该窗口类的方法,而不是回调过程。例如,绘图画布接收更新事件、鼠标事件、键盘事件和大小调整事件;要处理这些事件,请从内置的canvas%类派生一个新类,并重写事件处理方法。以下表达式扩展了上面创建的框架,该框架具有处理鼠标和键盘事件的画布:

; Derive a new canvas (a drawing window) class to handle events
(define my-canvas%
  (class canvas% ; The base class is canvas%
    ; Define overriding method to handle mouse events
    (define/override (on-event event)
      (send msg set-label "Canvas mouse"))
    ; Define overriding method to handle keyboard events
    (define/override (on-char event)
      (send msg set-label "Canvas keyboard"))
    ; Call the superclass init, passing on all init args
    (super-new)))
 
; Make a canvas that handles events in the frame
(new my-canvas% [parent frame])

运行上述代码后,手动调整框架大小以查看新画布。将光标移动到画布上会调用画布的 on-event方法,其中对象表示运动事件。单击画布调用on-event事件。当画布具有键盘焦点时,在键盘上键入将调用画布的on-char方法。

窗口化系统按顺序发送GUI事件;也就是说,在调用事件处理回调或方法之后,窗口化系统等待处理程序返回,然后再调度下一个事件。要说明事件的顺序性,请再次扩展帧,添加一个【Pause】按钮:

(new button% [parent frame]
             [label "Pause"]
             [callback (lambda (button event) (sleep 5))])

用户单击【Pause】后,整个帧将停止响应5秒钟;窗口系统无法发送更多事件,直到调用sleep返回。有关事件调度的详细信息,请参阅《事件调度和事件空间(Event Dispatching and Eventspaces)》。

除了调度事件之外,GUI类还处理窗口的图形布局。我们的示例框架演示了一个简单的布局;框架的元素从上到下排列。通常,程序员通过将每个GUI元素分配给父容器来指定窗口的布局。垂直容器(如框架)将其子容器排列在一列中,水平容器将其子容器排列在一行中。容器可以是另一个容器的子容器;例如,要在框架中并排放置两个按钮,请为新按钮创建一个水平面板:

(define panel (new horizontal-panel% [parent frame]))
(new button% [parent panel]
             [label "Left"]
             [callback (lambda (button event)
                         (send msg set-label "Left click"))])
(new button% [parent panel]
             [label "Right"]
             [callback (lambda (button event)
                         (send msg set-label "Right click"))])

有关窗口布局和容器的详细信息,请参见《几何图形管理( Geometry Management)》。

1.2 在画布中画图

画布的内容由其on-paint方法确定,其中默认的on-paint调用在创建画布时提供的绘图回调函数。on-paint方法不接收任何参数,并使用画布的get-dc方法获取绘图上下文(DC);默认的on-paint方法将画布和此DC传递给paint-callback函数。DC上的racket/draw工具箱的绘制操作反映在屏幕上画布的内容中。

例如,下面的程序创建一个画布,显示大而友好的字母:

(define frame (new frame%
                   [label "Example"]
                   [width 300]
                   [height 300]))
(new canvas% [parent frame]
             [paint-callback
              (lambda (canvas dc)
                (send dc set-scale 3 3)
                (send dc set-text-foreground "blue")
                (send dc draw-text "Don't Panic!" 0 0))])
(send frame show #t)

画布的背景色可以通过set-canvas-background方法进行设置。要使画布透明(以便它以其父级的颜色和纹理作为初始内容),请在创建画布时在style参数中提供'transparent。

有关使用racket/draw库绘制的概述,请参见《Racket绘图工具包(The Racket Drawing Toolkit)》中的概述。有关画布绘制的更多高级信息,请参见《画布中的动画(Animation in Canvases)》。

1.3 核心窗口类

窗口工具箱中的基本图形元素是一个area。以下类实现了窗口工具箱中不同类型的区域:

  • 容器(Containers)——可以包含其他区域的区域:
  • > frame%——一个框架(frame)是一个顶级窗口,用户可以移动和调整其大小。
  • > dialog%——对话框(dialog)是模式顶级窗口;显示对话框时,其他顶级窗口将被禁用,直到对话框被取消。
  • > panel%——容器(panel)是容器中的子容器。工具箱提供了panel%的三个子类:vertical-panel%、horizontal-panel%和tab-panel%。
  • > pane%——窗格(pane)是轻量级的面板。它没有图形表示或事件处理功能。pane%类有三个子类:vertical-pane%、horizontal-pane%和grow-box-spacer-pane%。
  • 窗格(Containees)——必须包含在其他区域中的区域:

  • > panel%——panel既是窗格也是容器。

  • > pane%——pane既是窗格也是容器。

  • > canvas%——画布(canvas)是在屏幕上绘制的子窗口。

  • > editor-canvas%——编辑器画布(editor-canvas)是用于显示文本编辑器或粘贴板编辑器的子窗口。editor-canvas%类与《编辑器( Editors)》中的编辑器类一起记录。

  • 控件(Controls)——包含用户可以操作的内容:

  • > message%——消息是一个静态文本字段或位图,没有用户交互。

  • > button%——按钮是可单击的控件。

  • > check-box%——复选框是可单击的控件,用户单击该控件以设置或删除其复选标记。

  • > radio-box%——单选框是互相排斥的单选按钮的集合;当用户单击某个单选按钮时,将选中该单选框,并取消选中该单选框以前选定的单选按钮。

  • > choice%——选择项是文本选项的弹出菜单,用户在控件中选择一项。

  • > list-box%——列表框是文本选项的可滚动列表,用户选择列表中的一个或多个项目(取决于列表框的样式)。

  • > text-field%——文本字段是用于简单文本输入的框。

  • > combo-field%——组合字段将文本字段与弹出的选项菜单组合在一起。

  • > slider%——滑块是一个可拖动的控件,用于选择固定范围内的整数值。

  • > gauge%——计数器是一个仅输出的控件(用户不能更改该值),用于报告固定范围内的整数值。

正如上面的列表所建议的,某些区域(area),称为容器(container),管理某些其他区域,称为窗格(containee)。有些区域,如面板(panel),既是窗格又是容器。

大多数区域是窗户,但有些是非窗户。窗口(如面板)具有图形表示,接收键盘和鼠标事件,可以禁用或隐藏。相反,非窗口(如窗格)仅对几何图形管理有用;非窗口不接收鼠标事件,并且不能禁用或隐藏。

每个区域都是area<%>接口的实例。每个容器也是 area-container<%>接口的实例,而每个窗格是subarea<%>的实例。窗口是 window<%>的实例。area-container<%>、subarea<%>和window<%>接口是area<%>的子接口。

下图显示了在area<%>下的更多类型层次结构:

                           area<%>

       ______________________|_______________

       |                  |                 |

  subarea<%>          window<%>      area-container<%>      

       |____       _______|__________       |

            |      |                |       |

           subwindow<%>          area-container-window<%>

        ________|________                |

        |               |                |

     control<%>       canvas<%>   top-level-window<%>

下面的图扩展了上面的图,以显示area<%>下的完整类型层次结构。(有些类型由接口表示,有些类型由类表示。原则上,每种区域类型都应该用一个接口来表示,但是只要窗口工具箱提供了具体的实现,相应的接口就会从工具箱中省略。)为了避免交叉线,将为柱面绘制层次结构;subarea<%> 和subwindow<%>的线从图表的左边缘到右边缘换行。

                           area<%>

        _____________________|_______________

        |               |                   |

      subarea<%>     window<%>       area-container<%>      

<<<____|____       _____|__________       __|___  ___________________<<<

            |      |              |       |    |  |                  

           subwindow<%>           |       |    |  |                  

<<<______________|___________     |       |    |  |                 _<<<

            |               |     |       |    pane%                |

       control<%>           |     |       |     |- horizontal-pane% |

        |- message%         |     |       |     |- vertical-pane%   |

        |- button%          |     |       |                         |

        |- check-box%       |  area-container-window<%>             |

        |- slider%          |        |                              |

        |- gauge%           |        |            __________________|

        |- text-field%      |        |            |   

            |- combo-field% |        |-------- panel%       

        |- radio-box%       |        |          |- horizontal-panel%

        |- list-control<%>  |        |          |- vertical-panel%

            |- choice%      |        |              |- tab-panel%

            |- list-box%    |        |              |- group-box-panel%

                            |        |

                            |        |- top-level-window<%>

                            |            |- frame% 

                         canvas<%>       |- dialog%

                          |- canvas%

                          |- editor-canvas%

菜单栏、菜单和菜单项是图形元素,但不是区域(即,它们没有区域通用的所有属性,如可调图形大小)。相反,菜单类形成了一个单独的容器—窗格层次结构:

  • 菜单项容器
  • > menu-bar%——菜单栏是与框架关联的顶级菜单集合。
  • > menu%——一个菜单包含一组菜单项。菜单可以出现在菜单栏、弹出菜单或其他菜单的子菜单中。
  • > popup-menu%——弹出菜单是动态显示在画布或编辑器画布中的顶级菜单。
  • 菜单项
  • > separator-menu-item%——分隔符是菜单或弹出菜单中不可选择的行。
  • > menu-item%——纯菜单项是菜单中可选择的文本项。当选择该项时,将调用其回调过程。
  • > checkable-menu-item%——可选中菜单项是菜单中的文本项,用户选择可选中菜单项以切换该项旁边的复选标记。
  • > menu%——菜单是菜单项和菜单项容器。

下图显示了菜单系统的完整类型层次结构:

    menu-item<%>                menu-item-container<%> 

        |                              | 

        |- separator-menu-item%   _____|___ 

        |- labelled-menu-item<%>  |       |- menu-bar% 

            _________|_________   |       |- popup-menu% 

            |                 |   | 

            |                 menu%

            |                          

            |- selectable-menu-item<%>               

                |- menu-item%                        

                |- checkable-menu-item%

1.4 几何管理

窗口工具箱的几何图形管理使得设计在所有平台上都看起来正确的窗口变得容易,尽管图形用户界面元素的图形表示方式不同。几何管理基于容器;每个容器根据简单的约束(如框架的当前大小和按钮的自然大小)排列其子容器。

内置容器类包括水平面板(和窗格)和垂直面板(和窗格),水平面板(和窗格)将它们的子级排成一行,垂直面板(和窗格)将它们的子级排成一列。通过嵌套水平和垂直容器,程序员可以实现大多数布局。例如,要用形状构造一个对话框

   ------------------------------------------------------

  |              -------------------------------------   |

  |  Your name: |                                     |  |

  |              -------------------------------------   |

  |                    --------     ----                 |

  |                   ( Cancel )   ( OK )                |

  |                    --------     ----                 |

   ------------------------------------------------------

 

使用以下程序:

; Create a dialog
(define dialog (instantiate dialog% ("Example")))
 
; Add a text field to the dialog
(new text-field% [parent dialog] [label "Your name"])
 
; Add a horizontal panel to the dialog, with centering for buttons
(define panel (new horizontal-panel% [parent dialog]
                                     [alignment '(center center)]))
 
; Add Cancel and Ok buttons to the horizontal panel
(new button% [parent panel] [label "Cancel"])
(new button% [parent panel] [label "Ok"])
(when (system-position-ok-before-cancel?)
  (send panel change-children reverse))
 
; Show the dialog
(send dialog show #t)

每个容器使用每个子容器的自然大小排列其子容器,通常取决于子容器的实例化参数,例如按钮上的标签或单选框中的选项数。在上面的示例中,对话框水平延伸以匹配文本字段的最小宽度,垂直延伸以匹配字段和按钮的总高度。然后,对话框拉伸水平面板以填充对话框的下半部分。最后,水平面板使用按钮的最小宽度之和使它们水平居中。

如示例所示,一个可伸缩容器增长以填充其环境,并在其可伸缩子容器之间分配额外的空间。默认情况下,面板在两个方向都可以拉伸,而按钮在两个方向都不能拉伸。程序员可以更改单个GUI元素是否可伸缩。

下面的小节将详细描述容器系统,首先讨论容器中容器的属性,然后描述容器中容器的属性。除了内置的垂直和水平容器外,程序员还可以定义新类型的容器,如最后一小节中讨论的那样,《定义新类型的容器( Defining New Types of Containers)》。

1.4.1窗格

每个containee或子级具有以下属性:

  • 图形最小宽度和图形最小高度;
  • 要求的最小宽度和最小高度;
  • 水平和垂直伸缩性(开或关),以及
  • 水平和垂直边距。

容器根据每个窗格的这四个属性排列其子容器。创建窗格时指定窗格的父容器。当窗格被创建时一个窗口窗格的父级容器被指定,并且可以通过重新命名来更改其父级。

作为get-graphical-min-size报告的,一个特定窗格的图形最小尺寸(graphical minimum size)取决于创建窗格时所指定的平台、窗格的标签(对于控件)以及样式属性。例如,按钮的最小图形尺寸确保标签的整个文本可见。控件(如按钮)的最小图形大小无法更改;它在创建时被固定。(控件的最小尺寸在其标签更改时不会重新计算。)面板或窗格的图形最小尺寸取决于其子级的总最小尺寸和排列方式。

要为窗格选择尺寸,其父容器将考虑窗格的请求最小尺寸(requested minimum size),而不是其图形最小尺寸(假定请求的最小尺寸大于图形最小尺寸)。与图形最小尺寸不同,程序员可以随时使用min-width和min-height方法更改容器的请求最小尺寸。

边距(margin)是围绕窗格的空间。每个窗格的边距与其最小尺寸无关,但从视图的容器角度来看,边距有效地增加了容器的最小尺寸。例如,如果按钮的垂直边距为2,则容器必须分配足够的空间,以便在按钮的上方和下方保留两个像素的空间,以及为按钮的最小高度分配的空间。程序员可以用horiz-margin和vert-margin调整窗格的边距。控件的默认边距为2,任何其他类型的窗格的默认边距为0。

实际上,控件的请求最小尺寸和边距很少更改,尽管它们经常更改为画布。根据编程人员所需的视觉效果,可伸缩性通常适用于任何类型的窗格。

1.4.2 容器

容器具有以下属性:

  • 包含(未删除)子窗格的列表;
  • 要求的最小宽度和最小高度;
  • 子窗格之间的间隔;
  • 边框边距用于围绕在整个子集合周围;
  • 水平和垂直伸缩性(开或关);以及
  • 对齐设置以用于定位剩余空间。

这些属性被分解到容器本身的尺寸计算和子容器的排列中。对于也是窗格的容器(例如,面板),容器需求最小尺寸和可伸展性与其窗格方面相同。

创建窗格时指定窗格的父容器。窗格窗口可以在其父容器中隐藏(hidden)或删除(deleted),并且可以通过重新设置来更改其父容器(但非窗口窗格不能隐藏、删除或重新设置):

  • 隐藏的子级对用户不可见,但仍为容器中的每个隐藏子级分配空间。要隐藏或显示子级,请调用子级的show方法。
  • 已删除的子级在排列其其他子级时被容器隐藏并忽略,因此容器中没有为已删除的子级保留空间。若要使子级被删除或未被删除,请调用容器的delete-child或add-child方法(该方法调用子级的show方法)。
  • 要重新分析窗口窗格,请使用reparent方法。窗口将其隐藏或删除状态保留在新父级中。

创建子级时,它最初会显示并不会被删除。如果不存在对子级的外部引用,则删除的子级将受到垃圾收集的影响。通过容器的get-children方法,可以从容器中获得未删除子项(隐藏或未删除)的列表。

容器的未删除列表中子级的顺序非常重要。例如,垂直面板将第一个子项放在面板顶部的列表中,依此类推。创建新的子级时,它将放在其容器的子级列表的末尾。容器列表的顺序可以通过change-children方法动态更改。(change-children方法也可用于激活或停用子项。)

根据 get-graphical-min-size报告,容器的图形最小尺寸是通过结合其子级的最小尺寸(求和或取最大值,根据容器的布局策略)以及容器的间距和边界边距来计算的。程序员可以使用min-width和min-height方法指定较大的最小值;当容器的计算最小值大于程序员指定的最小值时,则忽略程序员指定的最小值。

容器的间距决定了容器中相邻子容器之间的剩余空间量,以及子容器边距所需的任何空间。容器的边界边距决定了在子集合周围添加的空间量;它有效地减少了容器中可以放置子集合的区域。程序员可以通过边界border和spacing方法动态调整容器的边界和间距。所有容器类型的默认边框和间距均为0。

因为面板或窗格既是集装箱又是容器,所以除了边界边距外,它还有一个集装箱边界。对于面板,这些边距不是多余的,因为面板可以有图形边框;边框绘制在面板的包含边距内,但不在面板的边框边距外。

对于顶级窗口容器(如框架或对话框),容器的可伸缩性决定了用户是否可以将窗口调整为大于其最小大小的大小。因此,用户无法调整不可拉伸的框架的尺寸。对于其他类型的容器(即面板和窗格),容器的可伸缩性是其作为其他容器中集装箱的可伸缩性。所有类型的容器最初都可以在两个方向上进行拉伸,但grow-box-spacer-pane%的实例除外,后者旨在作为轻型间隔类而不是有用的容器类,但是程序员可以通过stretchable-width和stretchable-height方法随时更改区域的可拉伸性。

容器的对齐规范决定了当容器有剩余空间时如何定位其子容器。(当容器的子级都不可拉伸时,容器只能在特定方向上具有剩余空间。)例如,当容器的水平对齐为'left时,子级在容器中左对齐,剩余空间累积到右侧。当容器的水平对齐方式为'center时,每个子容器在容器中水平居中。容器的对齐方式使用set-alignment方法更改。

1.4.3 定义新类型的容器

尽管嵌套的水平和垂直容器可以表示大多数布局模式,但是程序员可以使用显式布局过程定义一种新类型的容器。程序员通过从panel%或pane%派生类并重写container-size和place-children方法来定义新类型的容器。container-size方法获取每个子级的大小规范列表,并返回两个值:容器的最小宽度和高度。place-children方法获取容器的大小和每个子容器的大小规范列表,并返回大小和位置列表(与原始列表并行)。

输入大小规格是由四个值组成的列表:

  • 子级的最小宽度;
  • 子级的最小高度;
  • 子级的水平可伸展性(#t表示可伸展,#f表示不可伸展);以及
  • 子级的垂直可伸展性。

对于place-children,输出位置和尺寸规格是由四个值组成的列表:

  • 子级的新水平位置(相对于父级);
  • 子级新的垂直位置;
  • 子级的新实际宽度;
  • 子级的实际高度。

输入和输出的宽度和高度包括子级的边距。每个子控件的返回位置将自动递增,以说明放置控件时子控件的边距。

1.5 鼠标和键盘事件

每当用户移动鼠标、单击或释放鼠标按钮或按下键盘上的键时,就会为某些窗口生成一个事件。接收事件的窗口取决于图形显示的当前状态:

  • .鼠标事件的接收窗口通常是鼠标移动或单击时光标下的窗口。如果鼠标位于子窗口上,则子窗口将接收事件,而不是其父窗口。

当用户在窗口中单击时,窗口“抓取”鼠标,以便所有鼠标事件都转到该窗口,直到释放鼠标按钮(无论光标的位置如何)。因此,用户可以单击滚动条拇指并拖动它,而不必将光标严格保留在滚动条控件内。

通常会为每个鼠标按钮按下事件生成鼠标按钮释放事件,但按钮释放事件可能会被丢弃。例如,可能会出现模式对话框并接管鼠标。更一般地说,任何类型的鼠标事件原则上都可以被丢弃,因此避免使用依赖于精确鼠标事件序列的算法。例如,当鼠标跟踪处理程序接收到拖动事件以外的事件时,它应该重置跟踪状态。

  • .键盘事件的接收窗口是事件发生时拥有键盘焦点的窗口。任何时候只有一个窗口拥有焦点,焦点所有权通常由窗口以某种方式显示。例如,文本字段控件通过显示闪烁的插入符号来显示焦点所有权。

在顶级窗口中,根据平台的约定,只有某些类型的子窗口可以具有焦点。此外,最初拥有焦点的子窗口是特定于平台的。用户可以通过多种方式移动焦点,通常是通过单击目标窗口。程序可以使用Focus方法将焦点移动到子窗口或设置初始焦点。
根据操作系统处理车轮事件的方式,可以将'wheel-up或'wheel-down事件发送到键盘焦点窗口以外的其他窗口。

按键事件可能对应于实际按键或自动按键重复。不干预按键释放事件的多个按键事件通常表示自动按键。但是,与任何输入事件一样,密钥发布事件有时会被删除(例如,由于模式对话框的出现)。

控件(如按钮和列表框)自动处理键盘和鼠标事件,最终调用创建控件时提供的回调过程。画布将鼠标和键盘事件分别传播到其on-event和on-char方法。

鼠标和键盘事件以特殊方式传递到其窗口。接收窗口的每个祖先都有机会通过 on-subwindow-event和on-subwindow-char方法截获事件。有关详细信息,请参见方法说明。

顶级窗口的默认on-subwindow-char方法截取键盘事件以检测菜单快捷方式事件和焦点导航事件。有关详细信息,请参见《frame%》中的on-subwindow-char和dialog%中的on-subwindow-char。某些特定于操作系统的密钥组合是在低级别捕获的,不能重写。例如,在Windows和Unix上,按下并释放Alt总是将键盘焦点移动到菜单栏。同样,Alt-Tab在Windows上切换到不同的应用程序。(Alt-Space调用Windows上的系统菜单,但此快捷方式是由on-system-menu-char实现的,该命令在frame%中的on-subwindow-char和dialog%中的on-subwindow-char上调用。)

1.6 事件调度和事件空间

图形用户界面是一个固有的多线程系统:一个线程是屏幕上管理窗口的程序,另一个线程是用户移动鼠标并在键盘上键入。GUI程序通常使用一个事件队列将这个多线程系统转换成一个连续的系统,至少从程序员的角度来看是这样的。每个用户操作一次处理一个,忽略其他用户操作,直到前一个操作被完全处理。从多线程进程到单线程进程的转换大大简化了GUI程序的实现。

尽管纯顺序事件队列提供了编程便利性,但某些情况下需要与用户进行较不严格的对话:

  • .嵌套事件处理:在处理事件的过程中,可能需要从用户那里获得进一步的信息。通常,这些信息是通过模式对话框获得的;无论以何种方式获得输入,在完全处理原始事件之前,必须接收和处理更多的用户事件。为了进一步处理事件,原始事件的处理程序必须显式地向系统屈服。让步导致事件以嵌套的方式处理,而不是以纯粹的顺序方式处理。

  • .异步事件处理:应用程序可能由表示与用户独立对话的窗口组成。例如,绘图程序可能支持多个绘图窗口,并且一个窗口中特别耗时的任务(例如,对图像的特殊过滤效果)不应阻止用户在不同的窗口中工作。这样的应用程序需要对每个单独的窗口进行连续的事件处理,但需要跨窗口进行异步(可能是并行的)事件处理。换句话说,应用程序需要为每个窗口分别设置一个事件队列,为每个事件队列分别设置一个事件处理线程。

事件空间是用于处理GUI事件的上下文。每个事件空间维护自己的事件队列,单个事件空间中的事件由指定的处理程序线程按顺序调度。在此处理程序线程中运行的事件处理过程可以通过调用yield来向系统屈服,在这种情况下,可以在同一处理程序线程内以嵌套(但单线程)方式调用其他事件处理过程。来自不同事件空间的事件由不同的处理程序线程异步调度。

创建没有父级的框架或对话框时,它将与当前事件空间关联,如《创建和设置事件空间(Creating and Setting the Eventspace)》中所述。顶级窗口及其子窗口的事件始终在窗口的事件空间中调度。每个对话框都是模态的;对话框的show方法在显示对话框时隐式调用yield来处理事件。(有关线程和模式对话框的信息,请参见《事件空间和线程(Eventspaces and Threads)》。)此外,当显示模式对话框时,系统将禁用键和鼠标按下/释放事件到对话框事件空间中的其他顶级窗口,但其他事件空间中的窗口不受模式对话框的影响。(显示模式对话框时,鼠标移动、输入和离开事件仍会传递到所有窗口。)

1.6.1事件类型和优先级

除了与用户和窗口操作(如按钮单击、按键和更新)对应的事件外,系统还发送两种内部事件:计时器事件和显式排队事件。

计时器事件由timer%的实例创建。当计时器启动然后到期时,计时器将事件排队以调用计时器的notify方法。与顶级窗口一样,每个计时器在创建时都与特定的事件空间(《创建和设置事件空间(Creating and Setting the Eventspace)》中描述的当前事件空间)相关联,并且计时器在其事件空间中对事件进行排队。

显式排队事件是通过队列回调创建的,队列回调接受回调过程来处理事件。调用队列回调时,该事件已在当前事件空间中排队,优先级高或低,由队列回调的第二个参数(可选)指定。

事件空间的事件队列实际上是一个优先级队列,其中事件按其类型排序,从最高优先级(先调度)到最低优先级(后调度):

  • .使用queue-callback安装的高优先级事件具有最高优先级。

  • .通过timer%的计时器事件具有第二高优先级。

  • .窗口刷新事件具有第三高优先级。

  • .输入事件(如鼠标单击或按键)的优先级第二低。

  • .使用queue-callback安装的低优先级事件具有最低优先级。

尽管程序员不能直接控制事件的调度顺序,但是程序员可以通过event-dispatch-handler参数设置事件调度处理程序来控制调度的时间。这个参数和其他事件空间过程在《事件空间(Eventspaces)》中有更详细的描述。

1.6.2事件空间和线程

创建新的事件空间时,将为该事件空间创建相应的处理程序线程。当系统为一个事件空间分派一个事件时,它总是在事件空间的处理程序线程中这样做。处理程序过程可以创建无限期运行的新线程,但只要处理程序线程运行处理程序过程,就不能为相应的事件空间调度任何新事件。

当一个处理程序线程显示一个对话框时,只要显示该对话框,对话框的show方法就会隐式调用yield。当一个非处理程序线程显示一个对话框时,该非处理程序线程会一直阻塞,直到该对话框被取消。在没有来自非处理程序线程的参数的情况下调用yield无效。从非处理程序线程使用信号量调用yield等同于调用semaphore-wait。

1.6.3创建和设置事件空间

无论何时创建框架、对话框或计时器,它都与 current-eventspace参数确定的当前事件空间关联(请参见《参数( Parameters)》)。

make-eventspace过程创建新的事件空间。下面的示例在事件空间中创建一个新的事件空间和一个新的框架(参数化语法表暂时地设置一个参数值):

(let ([new-es (make-eventspace)])
  (parameterize ([current-eventspace new-es])
    (new frame% [label "Example"])))

创建事件空间时,它将置于当前管理员的管理之下。当管理员关闭事件空间时,与事件空间相关联的所有框架和对话框都将被销毁(在top-level-window<%>中不调用can-close?或者on-close,事件空间中的所有计时器都将停止,并且所有排队的回调都将被删除。尝试在关闭事件空间中创建新窗口、计时器或显式排队事件会引发exn:misc异常。

事件空间是一个可同步的事件(不要与GUI事件混淆),因此它可以与sync一起使用。作为可同步事件,当框架可见、计时器处于活动状态、回调排队或使用'root父级创建menu-bar%时,事件空间处于阻塞状态。(请注意,事件空间的阻塞状态与事件是否准备好进行调度无关。)

1.6.4延续与事件调度

每当系统发送一个事件时,对处理程序的调用将被一个延续提示(continuation prompt)(请参阅《 call-with-continuation-prompt》)包装,该提示限定延续中止(例如引发异常时)并处理程序捕获的连续。分隔的延续提示安装在对事件调度处理程序的调用之外,因此任何捕获的延续都包括对事件调度处理程序的调用。

例如,如果按钮回调引发异常,则默认异常处理程序执行的中止将返回到事件调度点,而不是终止程序或从封闭(yield)中转义。但是,如果with-handlers包装了导致按钮回调引发异常的(yield),则 with-handlers可以捕获该异常。

沿着类似的线路,如果按钮回调捕获了一个延续(使用默认的延续提示标记),那么应用延续只会重新安装处理程序要完成的工作,直到它返回为止;调用按钮回调的调度机制不包括在延续中。因此,在按钮回调期间捕获的延续在同一回调之外可能有用。

1.6.5记录

GUI系统记录事件处理的时间和处理时间。涉及回调到racket代码的每个事件都记录了两个事件,这两个事件都使用gui-event结构:

(struct gui-event (start end name) #:prefab)

start字段是事件处理开始时(current-inexact-milliseconds)的结果。事件处理开始时日志消息的end字段为#f,事件结束时日志消息的结束结果(current-inexact-milliseconds) 。name字段是处理事件的函数的名称;对于基于queue-callback的事件,它是底部(thunk)传递给queue-callback的名称。

1.7 画布动画

画布的内容是缓冲的,因此,如果必须重新绘制画布,则通常不需要再次调用on-paint方法或paint-callback函数。为了进一步减少闪烁,当调用了on-paint方法或paint-callback函数时,窗口系统避免将画布内容缓冲区刷新到屏幕上。

画布内容可以随时通过使用画布的get-dc方法的结果进行绘图来更新,并且绘图是线程安全的。对画布内容的更改会定期刷新到屏幕(不一定是在事件处理边界上),但只要刷新没有挂起,flush方法就会立即刷新到屏幕。suspend-flush和resume-flush方法挂起并恢复自动及显式刷新,尽管在某些平台上,在很少情况下强制执行自动刷新。

对于大多数动画目的,可以使用suspend-flush、resume-flush和flush来避免闪烁以及需要为动画提供额外的绘图缓冲区。在动画期间,用suspend-flush和resume-flush将每个动画帧的构造括起来,以确保部分绘制的帧不会刷新到屏幕上。如果即将进行挂起刷新,请使用flush确保画布内容在准备就绪时被刷新,因为如果经常挂起刷新,则刷新到屏幕的过程可能会处于饥饿状态。方法refresh-now在canvas%中方便地封装了这个序列。

1.8屏幕分辨率和文本缩放

在Mac OS上,屏幕尺寸是以绘图单位向用户描述的。视网膜显示器为每个绘图单元提供两个像素,而绘图单元始终用于窗口大小、子窗口位置和画布绘图。字体大小调整的“点”相当于绘图单位。

在Windows和Unix上,屏幕大小是以像素为单位描述给用户的,而用户可以独立选择比例来应用于文本和其他项目。典型的文本比例为125%、150%和200%。racket/gui库将此比例用于所有GUI元素,包括屏幕、窗口、按钮和画布绘图。例如,如果比例为200%,则get-display-size报告的屏幕大小将是每个维度中像素数的一半。注意,舍入效应会导致报告的窗口大小与刚设置的窗口大小不同。字体大小调整的“点”相当于(/ 96 72)绘图单位。

在Unix上,如果PLT_DISPLAY_BACKING_SCALE环境变量设置为实数,则它将覆盖racket/gui缩放的某些系统设置。使用GTK+ 3 (请参见《平台依赖项(Platform Dependencies)》),环境变量将覆盖系统范围的文本缩放;使用GTK+ 2,环境变量将覆盖文本和控件缩放。但是,使用默认标签字体的菜单、控制标签和非标签控制部件将不会使用通过PLT_DISPLAY_BACKING_SCALE指定的比例。

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值