0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaFX の TableView の選択範囲をクリップボードにコピーする。

Posted at

この記事について

将来的に書く予定の「JavaFX で DynamoDB Viewer作ってみた」記事の1ステップ。
結構大きな話になると思うので、少しずつ技術ポイント毎に記事を書いて、ある一定程度の要件を満たせた段階で前述まとめ記事書く予定。

第一回記事:DynamoDBの情報を読み込んでJavaFXで表示してみる
第二回記事:JavaFXで動的にテーブル列を設定する
第三回記事:AWS java SDKでDynamoDBテーブル情報を取得してみる

※これまでの記事が基本になってます。メソッドなど細かい部分で再説明していない部分があります。不明点などありましたらコメントなど頂けたら対応しようと思います。

今回の追加機能

  • テーブル選択エリアの内容をクリップボードにコピー可能にした
  • 選択形式を行選択モードとセル選択モードの切り替えが出来る様にした
  • コンテキストメニュー及びCtrl+Cでのコピーに対応した
  • データ読み込み時に読み込み中ダイアログ表示

現在の進捗

画面だと進捗解りにくいけど、内容をメモ帳などにペースト可能になってます。

image.png

この時点でのソース(github)

実装内容確認(クリップボードにコピー)

JavaFX TableView copy to clipboard を拝見したところによると、メニューの要素のアクションで、テーブルの中身をクリップボードにコピーしている。
TableView自体にコピペが実装されているわけではないという事が解る。以下の処理を実装する必要がある。

  1. 何かの操作にアクションを割り当て
  2. アクションの中でクリップボードに登録したい内容をセット

コピー部分実装

今回は、表計算ソフトに貼り付けできる様、Tab+改行で構成したテキストをクリップボードにコピーする事にする。
※記載ソース中tableResultList がTableView。

イベント設定

Scene Builderで TableView:onKeyPressed に onTableResultListKeyPressed を指定

イベントハンドラ実装

javaのController側で実装

DynamoDbToolController.java

	// Ctrl+Cキーイベント
	@FXML
	protected void onTableResultListKeyPressed(KeyEvent ev) {
		if (ev.isControlDown() && ev.getEventType() == KeyEvent.KEY_PRESSED && ev.getCode() == KeyCode.C) {
			actTableLineCopyToClipBoard(null);
		}
	}

	// コンテキストメニューからのイベント
	@FXML
	protected void actTableLineCopyToClipBoard(ActionEvent ev) {
		final ClipboardContent content = new ClipboardContent();
		TableViewSelectionModel<ObservableList<String>> selectedModel = tableResultList.getSelectionModel();
		if (selectedModel.getSelectedItems().size() == 0) {
			return;
		}
		String targetStr = null;
		// セル選択モードか行選択モードで動作切り替え
		if (tableResultList.getSelectionModel().isCellSelectionEnabled()) {
			targetStr = getWholeTableSelectedCellString(selectedModel);
		} else {
			targetStr = getWholeTableSelectedRowString(selectedModel);
		}
		if (targetStr != null) {
			content.putString(targetStr);
			Clipboard.getSystemClipboard().setContent(content);
		}
	}

テーブルの中身をクリップボードにセット

DynamoDbToolController.java

	// セル選択モードでのコピペ用文字列作成
	private String getWholeTableSelectedCellString(TableViewSelectionModel<ObservableList<String>> selectedModel) {
		List<Pair<Integer, Integer>> posList = getPositionList(selectedModel);

		StringBuilder selectedRowStrSb = new StringBuilder();
		int oldRow = posList.get(0).getValue();
		for (Pair<Integer, Integer> position : posList) {
			int newRow = position.getValue();
			String cellStr = tableResultList.getItems().get(newRow).get(position.getKey());
			cellStr = cellStr == null ? "" : cellStr;
			if (oldRow != newRow) {
				selectedRowStrSb.append("\n").append(cellStr);
			} else if (selectedRowStrSb.length() == 0) {
				selectedRowStrSb.append(cellStr);
			} else {
				selectedRowStrSb.append("\t").append(cellStr);
			}
			oldRow = position.getValue();
		}
		return selectedRowStrSb.toString();
	}

	@SuppressWarnings("unchecked")
	// セル選択モードでの位置情報処理。Pair形式に変更+ソート
	// TablePositionがソート対応してないというエラー出たので。
	private List<Pair<Integer, Integer>> getPositionList(
			TableViewSelectionModel<ObservableList<String>> selectedModel) {
		@SuppressWarnings("rawtypes")
		ObservableList<TablePosition> selPosList = selectedModel.getSelectedCells();

		List<Pair<Integer, Integer>> posList = new ArrayList<>();
		for (TablePosition<ObservableList<String>, String> position : selPosList) {
			posList.add(new Pair<Integer, Integer>(position.getColumn(), position.getRow()));
		}
		posList.sort(new Comparator<Pair<Integer, Integer>>() {
			@Override
			public int compare(Pair<Integer, Integer> o1, Pair<Integer, Integer> o2) {
				int rowdiff = o1.getValue() - o2.getValue();
				if (rowdiff != 0) {
					return rowdiff;
				}
				return o1.getKey() - o2.getKey();
			}
		});
		return posList;
	}

	// 行選択モードでのコピペ用文字列作成
	private String getWholeTableSelectedRowString(TableViewSelectionModel<ObservableList<String>> selectedModel) {
		ObservableList<ObservableList<String>> selectedRecords = selectedModel.getSelectedItems();
		if (selectedRecords.size() == 0) {
			return null;
		}

		StringBuilder selectedRowStrSb = new StringBuilder();
		for (ObservableList<String> record : selectedRecords) {
			if (selectedRowStrSb.length() > 0) {
				selectedRowStrSb.append("\n");
			}
			selectedRowStrSb.append(getOneRowString(record));
		}
		return selectedRowStrSb.toString();
	}

	private String getOneRowString(ObservableList<String> record) {
		StringBuilder oneRowStrSb = new StringBuilder();
		for (String item : record) {
			if (oneRowStrSb.length() > 0) {
				oneRowStrSb.append("\t");
			}
			oneRowStrSb.append(item == null ? "" : item);
		}
		return oneRowStrSb.toString();
	}

追加:TableViewを複数行選択可能にする

上の動作確認の為に設定。自分が使ってるScene Builderだと設定できないのでControllerで直接。

	tableResultList.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);

追加:TableViewをセル選択モードにする

同上。デフォルトは行選択。

	tableResultList.getSelectionModel().setCellSelectionEnabled(true);

ContextMenu で、行選択モード/セル選択モード選択

Scene Builder 対応

  1. TableViewの配下にContextMenu追加。
  2. ContextMenuの fxId に contextMenuTable を指定
  3. ContextMenuにMenuItemを追加。
  4. MenuItemの fxId に menuItemTableResultListCellSelectMode を指定
  5. 同、On ActionにactToggleCellSelectModeを指定

java側対応

isCellSelectMode はクラス変数に指定済み。
切り替え時に、MenuItemの表示文字列も切り替え

DynamoDbToolController.java
	@FXML
	protected void actToggleCellSelectMode(ActionEvent ev) {
		isCellSelectMode = !isCellSelectMode;
		if (isCellSelectMode) {
			menuItemTableResultListCellSelectMode.setText("Switch to row select mode");
		} else {
			menuItemTableResultListCellSelectMode.setText("Switch to cell select mode");
		}
		tableResultList.getSelectionModel().setCellSelectionEnabled(isCellSelectMode);
	}

データ読み込み時に読み込み中ダイアログ表示部分実装

データ読み込み時に、いわゆる砂時計やぐるぐるアイコンにしたい。対応は以下の処理

  • テーブルリスト表示
  • データ読み込み時

しかし、JavaFXの仕様か何かで、処理スレッドが終わった時にしか再描画されないらしく、カーソルを変更する事で色々検討してみたが、駄目だった。カーソル変更での対応は諦め、Alertを使用してそれを表現する事にした。

読み込み用ダイアログ初期化

シンプルにするため、AlertType.NONE を使用。ただそのままだと、ダイアログが閉じれなくなる。その為、OKボタンを追加。そして、それを不可視にしている。

初期化関数作成
	private void initLoadDialog() {
		dialog = new Alert(AlertType.NONE);
		dialog.setHeaderText(null);
		dialog.setContentText("Now Loading...");
		dialog.getDialogPane().getButtonTypes().add(ButtonType.OK);
		Pane pane = dialog.getDialogPane();
		ObservableList<Node> nodes = pane.getChildren();
		for (Node node : nodes) {
			if (node instanceof ButtonBar) {
				node.setVisible(false);
			}
		}
	}

読み込み時処理

後にもっと良い読み込み中表現が見つかった時の為に、ダイアログ表示/非表示は関数化。代表してテーブルリスト表示のロジックを紹介。

	@FXML
	protected void actTableListLoad(ActionEvent ev) throws URISyntaxException {
		startWaiting();
		try {
			TableListDao dao = new TableListDao(getConnectInfo());
			TableNameCondType conditionType = TableNameCondType.getByName(cmbTableNameCond.getValue());
			lvTableList.getItems().clear();
			lvTableList.getItems().addAll(dao.getTableList(txtFldTableNameCond.getText(), conditionType));
		} finally {
			finishWaiting();
		}
	}

	private void startWaiting() {
		dialog.show();
	}

	private void finishWaiting() {
		dialog.close();
	}

次回予定

テーブル中身表示部を動的タブで追加して、複数テーブルの中身を効率的に見れるようにしたい。

参考にさせて頂いたページ

公式ページ

Clipboard クラス
JavaFX CSSリファレンス・ガイド
Groundbreakers Developer Community

皆さんの良記事

JavaFX の Clipboard API を使う
JavaFX / TableViewを編集可能にする
JavaFX TableView copy to clipboard
JavaFX TreeTableViewの選択色を変更する
JavaFX 8 の TableView で行を選択不可にするための設定方法
java - アラートタイプなしjavafxを閉じる

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?