在JavaFX中重新触发已消耗的事件

时间:2012-08-09 18:57:18

标签: java event-handling javafx-2 barcode-scanner

我正在开发一个允许用户扫描条形码的系统。条形码扫描仪有效地表现得像键盘,以超人的速度“键入”条形码的每个数字。为了这个例子,让我们说连续“击键”之间的大部分时间是10毫秒。

我首先实现了一个EventHandler,用于侦听应用程序KeyEvent上的数字Window。当KeyEvent到达时,处理程序还不知道它是由人还是条形码扫描器输入的( 知道从现在起10毫秒)。不幸的是,我必须做出决定现在或冒险锁定JavaFX的主线程,因此我会自动调用keyEvent.consume()以防止它被处理。

经过10毫秒后,计时器会唤醒并判断KeyEvent是否是条形码的一部分。如果是,则KeyEvent被连接在一起并由条形码处理逻辑处理。否则,我想让应用程序正常处理KeyEvent

如果应用程序在我已经调用KeyEvent之后强制该应用程序处理keyEvent.consume()

1 个答案:

答案 0 :(得分:3)

以下是我对如何做到这一点的看法。

该解决方案的工作原理是过滤应用程序的关键事件,克隆它们并将克隆的事件放入队列中,然后使用过滤器中的原始事件。克隆的事件队列稍后处理。来自条形码阅读器的事件不会被激活。不会来自条形码阅读器的事件被激活,以便系统可以处理它们。数据结构跟踪事件是否已经处理,以便系统可以在事件过滤器中知道它是否真的必须拦截和使用事件,或者让它们传递给标准的JavaFX事件处理程序。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import javafx.util.Duration;

// delays event key press handling so that some events can be intercepted
// and routed to a bar code a reader and others can be processed by the app.
public class EventRefire extends Application {
  public static void main(String[] args) { launch(args); }
  @Override public void start(final Stage stage) throws Exception {
    // create the scene.
    final VBox layout = new VBox();
    final Scene scene = new Scene(layout);

    // create a queue to hold delayed events which have not yet been processed.
    final List<KeyEvent> unprocessedEventQueue = new ArrayList();
    // create a queue to hold delayed events which have already been processed.
    final List<KeyEvent> processedEventQueue = new ArrayList();

    // create some controls for the app.
    final TextField splitterField1 = new TextField(); splitterField1.setId("f1");
    final TextField splitterField2 = new TextField(); splitterField2.setId("f2");
    final Label forBarCode   = new Label();
    final Label forTextField = new Label();

    // filter key events on the textfield and don't process them straight away.
    stage.addEventFilter(KeyEvent.ANY, new EventHandler<KeyEvent>() {
      @Override public void handle(KeyEvent event) {
        if (event.getTarget() instanceof Node) {
          if (!processedEventQueue.contains(event)) {
            unprocessedEventQueue.add((KeyEvent) event.clone());
            event.consume();
          } else {
            processedEventQueue.remove(event);
          }  
        }  
      }
    });

    // set up a timeline to simulate handling delayed event processing from 
    // the barcode scanner.
    Timeline timeline = new Timeline(
      new KeyFrame(
        Duration.seconds(1), 
        new EventHandler() {
          @Override public void handle(Event timeEvent) {
            // process the unprocessed events, routing them to the barcode reader
            // or scheduling the for refiring as approriate.
            final Iterator<KeyEvent> uei = unprocessedEventQueue.iterator();
            final List<KeyEvent> refireEvents = new ArrayList();
            while (uei.hasNext()) {
              KeyEvent event = uei.next();
              String keychar = event.getCharacter();
              if ("barcode".contains(keychar)) {
                forBarCode.setText(forBarCode.getText() + keychar);
              } else {
                forTextField.setText(forTextField.getText() + keychar);
                refireEvents.add(event);
              }
            }

            // all events have now been processed - clear the unprocessed event queue.
            unprocessedEventQueue.clear();

            // refire all of the events scheduled to refire.
            final Iterator<KeyEvent> rei = refireEvents.iterator();
            while (rei.hasNext()) {
              KeyEvent event = rei.next();
              processedEventQueue.add(event);
              if (event.getTarget() instanceof Node) {
                ((Node) event.getTarget()).fireEvent(event);
              }
            }
          }
        }
      )
    );
    timeline.setCycleCount(Timeline.INDEFINITE);
    timeline.play();

    // layout the scene.
    final GridPane grid = new GridPane();
    grid.addRow(0, new Label("Input Field 1:"), splitterField1);
    grid.addRow(1, new Label("Input Field 2:"), splitterField2);
    grid.addRow(2, new Label("For App:"),       forTextField);
    grid.addRow(3, new Label("For BarCode:"),   forBarCode);
    grid.setStyle("-fx-padding: 10; -fx-vgap: 10; -fx-hgap: 10; -fx-background-color: cornsilk;");

    Label instructions = new Label("Type letters - key events which generate the lowercase letters b, a, r, c, o, d, e will be routed to the barcode input processor, other key events will be routed back to the app and processed normally.");
    instructions.setWrapText(true);
    layout.getChildren().addAll(grid, instructions);
    layout.setStyle("-fx-padding: 10; -fx-vgap: 10; -fx-background-color: cornsilk;");
    layout.setPrefWidth(300);
    stage.setScene(scene);
    stage.show();
  }
}

示例程序输出:

Sample program output

因为我使用时间轴我的代码中的所有内容都在FXApplicationThread上运行,所以我不必担心我的实现中的并发性。在使用真正的条形码阅读器和条形码事件处理器实现时,您可能需要一些额外的并发保护,因为可能涉及多个线程。此外,您可能不需要我的代码中使用的时间轴来模拟条形码系统的延迟处理。