2011-01-04 10 views
2

사용자가 키를 눌러 작업을 수행 할 수있는 프로그램이 있습니다. 그 하나의 사건은 약간의 시간이 걸립니다. 사용자는 해당 키를 누른 상태에서 여러 번 연속으로 작업을 수행 할 수도 있습니다. 문제는 keyPress() 이벤트가 이벤트 처리보다 빠르게 대기하고 있다는 것입니다. 즉, 사용자가 키를 놓으면 이전에 키를 누른 상태에서 대기중인 이벤트가 계속 처리됩니다. 또한 keyRelease 이벤트는 키가 실제로 해제되었을 때와 관계없이 최종 keyPress 이벤트가 처리 될 때까지 발생하지 않는 것으로 나타났습니다. 1. 키 릴리스 이벤트를 감지하고 사용자가 실제로 키를 다시 누를 때까지 향후 keyPress 이벤트를 무시하십시오. 2. 첫 번째 키가 완료 될 때까지 후속 keyPress 이벤트를 수행하지 않고 키가 눌러지지 않은 경우이를 감지하고 중지합니다.Java KeyEvents 건너 뛰기

누구든지이 작업을 수행하는 방법을 알고 있습니까?

답변

2

면책 사항 : 나는이 느낌이 좋지 않아서이 코드가 끔찍한 것처럼 보입니다.

내가 원하는 것 : DirectInput에 액세스하여 이벤트 대신 키보드 상태를 얻습니다. 그것은이 질문의 범위를 훨씬 벗어납니다. 그래서 우리는 우리 자신의 행동 상태를 유지할 것입니다.

발생하는 문제는 UI 스레드 내에서 작업을 실행하고 있다는 것입니다. 작업자 스레드를 생성하고 작업이 완료 될 때까지 후속 이벤트를 무시해야합니다.

예를 들어, 문자 'a'를 누르거나 누르고있을 때 새로운 작업을 시작합니다. 첫 번째 작업이 완료 될 때까지 다른 작업을 생성하지 않습니다. 작업은 양식의 레이블을 업데이트하여 완료되기 전에 남아있는 '주기'수를 표시합니다.

지금까지 얼마나 많은 작업이 발생했는지 표시하는 또 다른 레이블이 있습니다. 중요한 부분은 모든 UI 키 이벤트가 그들을 대기열 원인이 UI 스레드에서 차단하지, 발생하도록하는 것입니다 새로운 액션

을 산란

.

public void keyPressed(KeyEvent e) { 
    char keyChar = e.getKeyChar(); 
    System.out.println("KeyChar: " + keyChar); 
    // Press a to start an Action 
    if (keyChar == 'a') { 
     if (!mAction.isRunning()) { 
      mTotalActions.setText("Ran " + (++mTotalActionsRan) + " actions."); 
      System.out.println("Starting new Action"); 
      Thread thread = new Thread(new Runnable() { 
       public void run() { 
        mAction.run(); 
       } 
      }); 
      thread.start(); 
     } 
    } 
} 

업데이트 UI 스레드 액션은 사용자 인터페이스 업데이트의 모든 종류를 수행하는 경우

에, 그것은 SwingUtilities.invokeLater 방법을 사용해야합니다. 이 메서드는 UI 스레드에서 실행되도록 코드를 대기열에 추가합니다. UI 스레드가 아닌 스레드에서 사용자 인터페이스를 수정할 수 없습니다. 또한 SwingUtilities를 사용하여 UI 구성 요소를 업데이트하십시오. Component에서 메소드를 호출하지 않는 계산, 처리 등은 SwingUtilities.invokeLater의 범위를 벗어나 수행 할 수 있습니다.

전체 코드 목록은

/* 
* To change this template, choose Tools | Templates 
* and open the template in the editor. 
*/ 
package stackoverflow_4589538; 

import java.awt.event.KeyAdapter; 
import java.awt.event.KeyEvent; 
import java.util.Random; 
import javax.swing.JFrame; 
import javax.swing.JLabel; 
import javax.swing.SwingUtilities; 

public class Main extends JFrame { 

    private JLabel mActionLabel; 
    private JLabel mTotalActions; 
    private int mTotalActionsRan; 

    private class MyAction { 

     private boolean mIsRunning = false; 

     public void run() { 
      // Make up a random wait cycle time 
      final int cycles = new Random().nextInt(100); 
      for (int i = 0; i < cycles; ++i) { 
       final int currentCycle = i; 
       try { 
        Thread.sleep(100); 
       } catch (InterruptedException ex) { 
       } 
       SwingUtilities.invokeLater(new Runnable() { 
        public void run() { 
          mActionLabel.setText("Cycle " + currentCycle + " of " + cycles); 
        } 
       }); 
      } 
      completed(); 
     } 

     public synchronized void start() { 
      mIsRunning = true; 
     } 

     public synchronized void completed() { 
      mIsRunning = false; 
     } 

     public synchronized boolean isRunning() { 
      return mIsRunning; 
    } 
} 
    private MyAction mAction = new MyAction(); 

    public Main() { 
     setLayout(null); 
     setBounds(40, 40, 800, 600); 
     setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); 
     addKeyListener(new KeyAdapter() { 

      @Override 
      public void keyPressed(KeyEvent e) { 
       char keyChar = e.getKeyChar(); 
       System.out.println("KeyChar: " + keyChar); 
       // Press A to start an Action 
       if (keyChar == 'a') { 
       if (!mAction.isRunning()) { 
         mTotalActions.setText("Ran " + (++mTotalActionsRan) + " actions."); 
         System.out.println("Starting new Action"); 
         Thread thread = new Thread(new Runnable() { 

          public void run() { 
           mAction.run(); 
          } 
         }); 
         // I had this within the run() method before 
         // but realized that it is possible for another UI event 
         // to occur and spawn another Action before, start() had 
         // occured within the thread 
         mAction.start(); 
         thread.start(); 
        } 
       } 
      } 

     @Override 
      public void keyReleased(KeyEvent e) { 
      } 
     }); 

     mActionLabel = new JLabel(); 
     mActionLabel.setBounds(10, 10, 150, 40); 

     mTotalActions = new JLabel(); 
     mTotalActions.setBounds(10, 50, 150, 40); 

     add(mActionLabel); 
     add(mTotalActions); 
    }  

    public static void main(String[] args) { 
     new Main().setVisible(true); 
    } 
} 
+0

감사합니다. 이것은 완벽하게 작동했습니다. – Jon

0

옵션 1로 가야합니다. 더 긴 프로세스를 시작한 후에는 작업 시간을 나타내는 부울을 설정하고 다른 들어오는 동일한 요청을 버리십시오. 일단 프로세스를 완료하면 부울을 다시 설정하고 추가 이벤트를 허용하십시오.

+0

나는 당신이 말한 것을 해보고 부울을 설정하려고했지만 작동하지 않았습니다. 후속 이벤트가 어딘가에 보관되어 (Java 내에서, 어디에서 알지 못함), 이전 이벤트가 완료되면 나에게만 전달되므로 이벤트는 절대로 건너 뛰지 않습니다. – Jon

+0

나는 당신이 별도의 스레드에서 긴 이벤트를하고 있다고 가정했다. (당신이해야 할 수도 있습니다.) – jzd

+0

나는 아니었지만, 지금 나는 있습니다. – Jon

1

나는 또한 keyRelease 이벤트가 발생하지 않는 것으로 나타났습니다까지 최종 키 누르기 이벤트에 관계없이 키가 실제로 출시되었을 때의 처리 된 후

이것은 사용중인 OS에 따라 다릅니다. 이것은 Windows에서의 동작입니다 (나에게 의미가 있음). Unix 나 Mac에서는 여러 개의 keyPressed, keyReleased 이벤트가 발생합니다. 따라서 솔루션은 keyReleased 이벤트를 기반으로해서는 안됩니다.

사용자가 작업을 수행하기 위해 키를 누를 수있는 프로그램이 있습니다.

그런 다음 KeyListener가 아니라 Key Binding을 사용해야합니다. 자세한 내용은 How to Use Key Bindings에있는 스윙 튜토리얼의 섹션을 읽어보십시오.

동작이 호출되면 비활성화 할 수 있습니다. 이것이 KeyStroke가 다시 동작하는 것을 막을 수 있는지, 또는 당신이 Action의 enabled 상태를 점검 할 필요가있을 지 확신 할 수 없다. 그런 다음 작업 코드가 실행되면 작업을 다시 활성화 할 수 있습니다.

또한이 장기 실행 코드는 EDT에서 실행하지 않아야합니다. 이 문제와 솔루션에 대한 자세한 내용은 동시성에 대한 스윙 튜토리얼의 섹션을 읽어보십시오.

+0

우리는 단순한 비디오 게임을 개발할 수 있고 캐릭터를 일정한 거리만큼 움직이기를 원한다는 것을 알고 있습니다. 내 이해에서 그는 열쇠를 누르고있는 동안 행동이 일어나길 원한다. 나는 그가 말한 것을 잘못 읽을 수는 있었지만. –

+0

네 말이 맞아, 실제로 이미징 앱을 만들고있어, 키를 누르고 사용자가보고있는 이미지 평면을 바꿀 수 있고, 사용자가 키를 놓으면 비행기 움직이기를 멈추고 싶다. . – Jon