ブログトップ 記事一覧 ログイン 無料ブログ開設

プログラムdeタマゴ

2013/02/19

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でちゃちゃっと作って、後はコントローラーに書くことにします。

f:id:nodamushi:20130219181825p:image:w320

 この青い円を、左右の四角形の中に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<MouseDragEvent>(){………}と書くのが面倒くさかったので、一つの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);
  }
}



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

f:id:nodamushi:20130219181826p:image:w320



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

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

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

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

欽ちゃん1号欽ちゃん1号 2013/02/28 17:37 >前回は欽ちゃん1号さん、コメントありがとうございました。返信が遅くなったことをお詫びします。

どうぞお気になさらずに。

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

書類アイコンみたいなのになっちゃうんですよね。
探したんですがわかりませんでした。

>ドロップが完了したことをソース元に通知する簡単な手段も見当たりません

MouseDragEventだと「event.setDropCompleted(success)」が使えないんですよね。
という事で平凡で詰まらないコードですが考えてみました。
ClipboardContentは使わなくても良いんですが無理に使ってみました(笑)

コントローラーがController.javaというファイル名だったと仮定しまして。

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

public class Controller implements Initializable{
@FXML
private Pane left;
@FXML
private Pane right;
@FXML
private Circle circle;

private ClipboardContent content = new ClipboardContent();
private String saveStyle;

public void setupGestureSource(final Circle source){

source.setOnDragDetected(new EventHandler <MouseEvent>() {
@Override
public void handle(MouseEvent event) {

System.out.println("onDragDetected");

Dragboard db = source.startDragAndDrop(TransferMode.MOVE);

if(content.getString() == null || content.getString().equals("")){
content.putString(event.toString());
}

db.setContent(content);

event.consume();
}
});

source.setOnDragDone(new EventHandler <DragEvent>() {
@Override
public void handle(DragEvent event) {

System.out.println("onDragDone");

if (event.getTransferMode() == TransferMode.MOVE) {

System.out.println("left = " + left);
System.out.println("right = " + right);

}

event.consume();
}
});

}

public void setupGestureTarget(final Pane target){

target.setOnDragOver(new EventHandler <DragEvent>() {
@Override
public void handle(DragEvent event) {

System.out.println("onDragOver");

if (event.getDragboard().hasString()) {

event.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}

event.consume();
}
});

target.setOnDragEntered(new EventHandler <DragEvent>() {
@Override
public void handle(DragEvent event) {

System.out.println("onDragEntered");
saveStyle = ((Pane)event.getSource()).getStyle();

if (event.getDragboard().hasString()) {
((Pane)event.getSource()).setStyle("-fx-background-color: blue;");
}

event.consume();
}
});

target.setOnDragExited(new EventHandler <DragEvent>() {
@Override
public void handle(DragEvent event) {

((Pane)event.getSource()).setStyle(saveStyle);

event.consume();
}
});

target.setOnDragDropped(new EventHandler <DragEvent>() {
@Override
public void handle(DragEvent event) {

System.out.println("onDragDropped");

Dragboard db = event.getDragboard();

Pane node = (Pane)event.getSource();

boolean success = false;
if (db.hasString()) {
System.out.println("content.getString()= " + content.getString());

if(node.getChildren().size() == 0){
node.getChildren().add((Node)event.getGestureSource());
}
success = true;
content.putString("");
}

event.setDropCompleted(success);

event.consume();
}
});

}

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

setupGestureSource(circle);
setupGestureTarget(left);
setupGestureTarget(right);

}

}

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/nodamushi/20130219/1361268082
リンク元