プログラムdeタマゴ

世界の端っこ

JavaFXの練習5:Drag&DropでNodeを移動する

 前回は欽ちゃん1号さん、コメントありがとうございました。返信が遅くなったことをお詫びします。
 さて、今回はDrag&Dropの話です。(以下D&Dと略記)


 JavaFX D&DでGoogle検索しても、なんか出てくるのはクリップボードを経由したD&Dの話ばっかり。私が今回やりたいのはそうじゃなくって、あるNodeをドラッグして、別なParentの上でドロップしたら、そのParentにNodeを移動させるってことをしたい。
 とりあえず、JavaFX2.2のMouseEventとMouseDragEventのJavaDocを読みながらモソモソやってみたらとりあえず出来ました。


 いちいちJavaでコンポーネントの配置を書くのは面倒くさいので、JavaFX Scene Builderでちゃちゃっと作って、後はコントローラーに書くことにします。



 この青い円を、左右の四角形の中にD&Dで移動させることを目的とします。
 fx:idはそれぞれ

  • 青い円(Circle):draggable
  • 左側の四角(Pane):left
  • 右側の四角(Pane):right

 としました。


 なので、このFXMLのコントローラーはこんな感じになります。

package ctr;

import java.net.*;
import java.util.*;
import javafx.event.*;
import javafx.fxml.*;
import javafx.scene.*;
import javafx.scene.input.*;
import javafx.scene.layout.*;
import javafx.scene.shape.*;

public class Test implements Initializable{
  public Pane left,right;
  public Circle draggable;

  @Override
  public void initialize(URL location, ResourceBundle resources) {
  }

}

 最終的に必要なimportは先に載せておきました。これ以降は全部initializeメソッドの実装になります。

 まずは、draggableをドラッグできるようにする必要があります。これには、draggable.startFullDrag()というメソッドをDragDetectedイベントが起こった時に呼び出せばいいようです。これを呼び出すと、press-drag-releaseのジェスチャーのソースとすることが出来るようになる模様。

draggable.setOnDragDetected(new EventHandler<MouseEvent>() {
  @Override
  public void handle(MouseEvent event) {
    System.out.println("drag detected");
    //ドラッグ開始
    draggable.startFullDrag();
    event.consume();
  }
});



 で、これで準備は完了とは行かなくて、この状態でD&Dしても、ほかのノードにはそのイベントの情報が伝わらない。そこで、マウスイベントを透過させるようにします。

draggable.setOnMousePressed(new EventHandler<MouseEvent>(){
  @Override
  public void handle(MouseEvent event) {
    System.out.println("mouse pressed");
    //dragイベントをマウスの下のノードにも伝わるようにするために
    //マウスイベントの透過性をtrueにする。
    draggable.setMouseTransparent(true);
    //consume()をしておかないと
    //下のノードにpressedイベントが伝わってしまう。
    event.consume();
  }
});

draggable.setOnMouseReleased(new EventHandler<MouseEvent>(){
  @Override
  public void handle(MouseEvent event) {
    System.out.println("mouse released");
    //処理が終わったので、元に戻しておく。
    draggable.setMouseTransparent(false);
    event.consume();
  }
});




 これで、draggableの準備は完了です。次に、leftとrightがドラッグイベントを受け付けれるようにします。今回は毎回new EventHandler(){………}と書くのが面倒くさかったので、一つのEventHandlerで対応させました。

EventHandler<MouseDragEvent> drageventhandler = 
    new EventHandler<MouseDragEvent>() {  
  @Override
  public void handle(MouseDragEvent event) {
  }
};
//MouseDragEvent全部にdrageventhandlerを適応する場合
right.addEventHandler(MouseDragEvent.ANY, drageventhandler);

//個別に適応する場合
left.setOnMouseDragExited(drageventhandler);
left.setOnMouseDragEntered(drageventhandler);
left.setOnMouseDragReleased(drageventhandler);
left.setOnMouseDragOver(drageventhandler);




 後はhandleメソッドを実装していくだけです。まずは、それぞれのイベントの状態に合わせて文字列を出力させるようにしてみます。

public void handle(MouseDragEvent event) {
  Object type = event.getEventType();
  if(type==MouseDragEvent.MOUSE_DRAG_RELEASED){
    System.out.println("drag release");
  }else if(type == MouseDragEvent.MOUSE_DRAG_ENTERED){
    System.out.println("drag entered");
  }else if(type == MouseDragEvent.MOUSE_DRAG_ENTERED_TARGET){
    System.out.println("drag entered target");
  }else if(type == MouseDragEvent.MOUSE_DRAG_EXITED){
    System.out.println("drag exited");
  }else if(type==MouseDragEvent.MOUSE_DRAG_EXITED_TARGET){
    System.out.println("drag exited target");
  }else if(type == MouseDragEvent.MOUSE_DRAG_OVER){
    System.out.println("drag over");
  }
  event.consume();
}




 enumじゃないので、分岐が面倒くさいです。なお、typeの型がObjectなのは、単純に書くのが面倒くさかっただけです。MOUSE_DRAG_ENTERED_TARGET、MOUSE_DRAG_EXITED_TARGETは何のことかよくわかりません。


 ドロップされた後の処理を書くにはMOUSE_DRAG_RELEASEDで処理を記述していけばいいです。

if(type==MouseDragEvent.MOUSE_DRAG_RELEASED){
  System.out.println("drag release");
      
  //draggableの取得
  Object srcobj = event.getGestureSource();
  //left またはrightの取得
  Object targetobj = event.getTarget();
      
  //一応型チェック
  //Parentだと、getChildren()が見えないので
  //Paneにしてある。
  if((srcobj instanceof Node) &&
     (targetobj instanceof Pane)){
        
    Pane target = (Pane)targetobj;
    Node src = (Node)srcobj;
    //どうやら、元の親からのリムーブは
    //自動で行われるっぽい。
    target.getChildren().add(src);
  }
}




 とりあえず、これで移動させることが出来ました。




 ただ、なんか動きがやたらもっさりしているような気がします。あまり正攻法じゃないのでしょうか?

 あと、ドラッグしている間、せっかくなのでマウスに円を追随させたいのですが、SwingでいうGlassPane的なものはないんですかね?よくわからんです。

 それと、ドロップが完了したことをソース元に通知する簡単な手段も見当たりませんが、どうしたらいいんですかね?(setOnDragDoneは反応しませんでした。)


 今日やったところはこんな感じです。