まったり技術ブログ

Technology is power.

【JavaFX】レイアウトを使ってみる

f:id:motikan2010:20180107061741j:plain
以下のレイアウトを紹介します。

  • VBox クラス:垂直にUIコントロールを配置
  • HBox クラス:平行にUIコントロールを配置
  • FlowPane クラス:平行にUIコントロールを配置(折り返し有り)
  • BorderPane クラス:上下・左右・中心の位置にUIコントロールを配置
  • GridPane クラス:行と列を指定してUIコントロールを配置
  • TilePane クラス:クリッド状にUIコントロールを配置
  • StackPane クラス:重ねてUIコントロールを配置

VBox クラス

垂直にUIコントロールを配置します。
上部から順に配置されていきます。

f:id:motikan2010:20180107044324p:plain:w100
VBox (JavaFX 8)

import com.google.common.collect.Lists;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.List;

public class VBoxExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("HBox Example");

        List<Button> buttonList = Lists.newArrayList();
        for (int i=0; i<5; i++) {
            buttonList.add(new Button(Integer.toString(i)));
            buttonList.get(i).setPrefWidth(80);
        }

        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER);
        vBox.setPadding(new Insets(10, 10, 10, 10));
        vBox.getChildren().addAll(buttonList);

        stage.setScene(new Scene(vBox));
        stage.show();
    }
}

UIコントロール間に間隔を空けることも可能です。

VBox vBox = new VBox();
vBox.setAlignment(Pos.CENTER);
vBox.setPadding(new Insets(10, 10, 10, 10));
vBox.setSpacing(5.0); // 追加
vBox.getChildren().addAll(buttonList);

f:id:motikan2010:20180107044413p:plain:w100

HBox クラス

平行にUIコントロールを配置します。
f:id:motikan2010:20180107044758p:plain:w450

HBox (JavaFX 8)

import com.google.common.collect.Lists;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

import java.util.List;

public class HBoxExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("HBox Example");

        List<Button> buttonList = Lists.newArrayList();
        for (int i=0; i<5; i++) {
            buttonList.add(new Button(Integer.toString(i)));
            buttonList.get(i).setPrefWidth(80);
        }

        HBox hBox = new HBox();
        hBox.setAlignment(Pos.CENTER);
        hBox.setPadding(new Insets(10, 10, 10, 10));
        hBox.getChildren().addAll(buttonList);

        stage.setScene(new Scene(hBox));
        stage.show();
    }
}

HBox同様に、UIコントロール間に間隔を空けることも可能です。

HBox hBox = new HBox();
hBox.setAlignment(Pos.CENTER);
hBox.setPadding(new Insets(10, 10, 10, 10));
hBox.setSpacing(5.0); // 追加
hBox.getChildren().addAll(buttonList);

f:id:motikan2010:20180107044840p:plain:w450

FlowPane クラス

平行にUIコントロールを配置します。
入りきらないUIコントロールは折り返しが行われます。

f:id:motikan2010:20180107045018p:plain:w450
FlowPane (JavaFX 8)

import com.google.common.collect.Lists;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

import java.util.List;

public class FlowPaneExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("Flow Pane Example");

        List<Button> buttonList = Lists.newArrayList();
        for (int i=0; i<5; i++) {
            buttonList.add(new Button(Integer.toString(i)));
            buttonList.get(i).setPrefWidth(80);
        }

        FlowPane flowPane = new FlowPane();
        flowPane.setPadding(new Insets(10, 10, 10, 10));
        flowPane.getChildren().addAll(buttonList);

        stage.setScene(new Scene(flowPane));
        stage.show();
    }

}

HBoxクラスとの違い

ウィンドウの幅を狭めてみると、2つの違いが分かります。

  • HBox   :ウィンドウの幅に合わせてボタンのサイズが縮小
  • FlowPane:ウィンドウの幅に合わせてボタンが次の行へ折り返し
HBoxクラス

f:id:motikan2010:20180107045119p:plain:w300

FlowPane クラス

f:id:motikan2010:20180107045121p:plain:w300

BorderPaneクラス

上下・左右・中心の位置にUIコントロールを配置します。

f:id:motikan2010:20180107051907p:plain:w250
GridPane (JavaFX 8)

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class BorderPaneExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("Border");
        stage.setWidth(200);
        stage.setHeight(110);

        Button topButton = new Button("Top");
        topButton.setPrefWidth(210);
        topButton.setPrefHeight(30);

        Button leftButton = new Button("Left");
        leftButton.setPrefHeight(30);

        Button centerButton = new Button("Center");
        centerButton.setPrefWidth(160);
        centerButton.setPrefHeight(30);

        Button rightButton = new Button("Right");
        rightButton.setPrefHeight(30);

        Button bottomButton = new Button("Bottom");
        bottomButton.setPrefWidth(210);
        bottomButton.setPrefHeight(30);

        BorderPane borderPane = new BorderPane();
        borderPane.setTop(topButton);
        borderPane.setLeft(leftButton);
        borderPane.setCenter(centerButton);
        borderPane.setRight(rightButton);
        borderPane.setBottom(bottomButton);

        VBox vBox = new VBox();
        vBox.getChildren().addAll(borderPane);

        stage.setScene(new Scene(vBox));
        stage.show();
    }

}

GridPane クラス

行と列を指定してUIコントロールを配置します。

f:id:motikan2010:20180107051647p:plain:w250
GridPane (JavaFX 8)

import com.google.common.collect.Lists;
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

import java.util.List;

public class VBoxExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("HBox Example");

        List<Button> buttonList = Lists.newArrayList();
        for (int i=0; i<5; i++) {
            buttonList.add(new Button(Integer.toString(i)));
            buttonList.get(i).setPrefWidth(80);
        }

        VBox vBox = new VBox();
        vBox.setAlignment(Pos.CENTER);
        vBox.setPadding(new Insets(10, 10, 10, 10));
        vBox.setSpacing(5.0);
        vBox.getChildren().addAll(buttonList);

        stage.setScene(new Scene(vBox));
        stage.show();
    }
}

TilePane クラス

クリッド状にUIコントロールを配置します。

f:id:motikan2010:20180107051732p:plain:w350
TilePane (JavaFX 8)

import com.google.common.collect.Lists;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.TilePane;
import javafx.stage.Stage;

import java.util.List;

public class TilePaneExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("Tile Pane Example");
        stage.setWidth(320);
        stage.setHeight(130);

        List<Button> buttonList = Lists.newArrayList();
        for (int i=0; i<16; i++) {
            buttonList.add(new Button("Button:" + Integer.toString(i)));
            buttonList.get(i).setPrefWidth(80);
            buttonList.get(i).setPrefHeight(20);
        }

        TilePane tilePane = new TilePane();
        tilePane.getChildren().addAll(buttonList);

        stage.setScene(new Scene(tilePane));
        stage.show();
    }
}

ウィンドウの縮尺に合わせて配置が変わります。
f:id:motikan2010:20180107062347p:plain

StackPane クラス

重ねてUIコントロールを配置ができます。

f:id:motikan2010:20180107051814p:plain:w300
StackPane (JavaFX 8)

import javafx.application.Application;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;


public class StackPaneExample extends Application {

    @Override
    public void start(Stage stage) throws Exception{
        stage.setTitle("Stack Pane Example");
        stage.setWidth(260);
        stage.setHeight(130);

        Button smButton = new Button("Small Button");
        smButton.setPrefWidth(80);
        smButton.setPrefHeight(30);
        smButton.setAlignment(Pos.TOP_CENTER);

        Button mdButton = new Button("Medium Button");
        mdButton.setPrefWidth(160);
        mdButton.setPrefHeight(60);
        mdButton.setAlignment(Pos.TOP_CENTER);

        Button lgButton = new Button("Large Button");
        lgButton.setPrefWidth(240);
        lgButton.setPrefHeight(90);
        lgButton.setAlignment(Pos.TOP_CENTER);

        StackPane stackPane = new StackPane();
        stackPane.setAlignment(Pos.BOTTOM_RIGHT);
        stackPane.getChildren().addAll(lgButton, mdButton, smButton);

        stage.setScene(new Scene(stackPane));
        stage.show();
    }
}

Specificationインタフェースを利用した副問合せ

Specificationインタフェースを利用した副問合せに関して、あまり情報が見当たらなかったので、メモ程度にφ(・ω・ )。

サンプルテーブル

よく見かける 1対多 の関係で説明していきます。
f:id:motikan2010:20171013000832p:plain:w400

ユーザ テーブル

サンプルコード

下記のサンプルコードでは、「同じ内容のツイートを3回以上しているユーザ」を選択という少し面倒くさいSQL文を発行してみます。
f:id:motikan2010:20171014134003j:plain

ファイル構成

 ── dbapp
    ├── DbappApplication.java
    ├── model
    │   ├── Tweet.java
    │   ├── Tweet_.java
    │   ├── User.java
    │   └── User_.java
    ├── repository
    │   ├── TweetRepository.java
    │   └── UserRepository.java
    └── spec
        └── BadUserSpec.java

Specification

今回の肝となる部分です。

BadUserSpec.java
package com.motikan2010.dbapp.spec;

import com.motikan2010.dbapp.model.Tweet;
import com.motikan2010.dbapp.model.Tweet_;
import com.motikan2010.dbapp.model.User;
import com.motikan2010.dbapp.model.User_;
import org.springframework.data.jpa.domain.Specification;

import javax.persistence.criteria.*;
import java.util.ArrayList;
import java.util.List;

public class BadUserSpec implements Specification<User> {

    @Override
    public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        final List<Predicate> predicates = new ArrayList<>();

        // サブクエリ
        Subquery<Tweet> subquery = query.subquery(Tweet.class);
        Root<Tweet> subRoot = subquery.from(Tweet.class);
        subquery.select(subRoot.get(Tweet_.user.getName()));
        subquery.where(cb.equal(root.get(User_.id.getName()), subRoot.get(Tweet_.user.getName())));
        // ツイート内容でグループ化
        subquery.groupBy(subRoot.get(Tweet_.body.getName()));
        // 条件 
        subquery.having(cb.and(
                cb.greaterThanOrEqualTo(cb.count(subRoot), 3L)
        ));

        predicates.add(cb.exists(subquery));

        return cb.and(predicates.toArray(new Predicate[predicates.size()]));
    }

}

エンティティ(モデル)

User.java
package com.motikan2010.dbapp.model;

import lombok.Data;

import javax.persistence.*;
import java.util.List;

@Entity
@Data
@Table(name = "user")
public class User {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    
    @Column(name = "nickname")
    private String nickname;

    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    private List<Tweet> tweetList;

}
Tweet.java
package com.motikan2010.dbapp.model;

import com.sun.istack.internal.NotNull;
import lombok.Data;

import javax.persistence.*;

@Entity
@Data
@Table(name = "tweet")
public class Tweet {

    @Id
    @GeneratedValue
    @Column(name = "id")
    private int id;
    
    @Column(name = "body")
    private String body;

    @NotNull
    @Column(name = "user_id")
    private int userId;
    
    @ManyToOne(targetEntity=User.class)
    @JoinColumn(name = "user_id", referencedColumnName = "id", insertable=false, updatable=false)
    private User user;

}

メタモデル

恥ずかしながらメタモデルの存在を今まで知らなかった。。
JPAを深掘りする〜Criteria APIで型安全な検索を追求しよう!【基本編】 - 技術ブログ | 株式会社クラウディア
ちなみにエンティティと同じパケッケージに所属させないといけないらしい。
別のパッケージに配置し、ヌルポと格闘したのはいい思い出・・・。

stackoverflow.com

User_.java
package com.motikan2010.dbapp.model;

import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@StaticMetamodel(User.class)
public class User_ {
    public static volatile SingularAttribute<User, Integer> id;
    public static volatile SingularAttribute<User, String> nickname;
    public static volatile ListAttribute<User, Tweet> tweetList;
}
Tweet_.java
package com.motikan2010.dbapp.model;

import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.StaticMetamodel;

@StaticMetamodel(Tweet.class)
public class Tweet_ {
    public static volatile SingularAttribute<Tweet, Integer> id;
    public static volatile SingularAttribute<Tweet, String> body;
    public static volatile SingularAttribute<Tweet, User> user;
}

リポジトリ

今回は空っぽ。

package com.motikan2010.dbapp.repository;

import com.motikan2010.dbapp.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;

public interface UserRepository extends JpaRepository<User, Integer>, JpaSpecificationExecutor<User>{
}

呼び出し側

DbappApplication.java
package com.motikan2010.dbapp;

import com.motikan2010.dbapp.model.User;
import com.motikan2010.dbapp.repository.UserRepository;
import com.motikan2010.dbapp.spec.BadUserSpec;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

import java.util.List;

@SpringBootApplication
public class DbappApplication implements CommandLineRunner {

    @Autowired
    UserRepository userRepo;

    public static void main(String[] args) {
        SpringApplication.run(DbappApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {

        BadUserSpec spec = new BadUserSpec();
        List<User> userList = userRepo.findAll(spec);

        for(User user : userList){
            System.out.println(user.getId() + " : " + user.getNickname());
        }

    }

}

発行SQL

select user0_.id as id1_1_, user0_.nickname as nickname2_1_ from user user0_ where exists (select tweet1_.user_id from tweet tweet1_, user user2_ where tweet1_.user_id=user2_.id and user0_.id=tweet1_.user_id group by tweet1_.body having count(tweet1_.id)>=3)

分かりやすくするとこんな感じ。

select id , nickname 
from user user1 
where exists (
    select user_id 
    from tweet, user user2 
    where tweet.user_id = user2.id and user1.id = tweet.user_id 
    group by body 
    having count(tweet.id)>=3
)

いい感じに副問い合わせが行えている。
countの部分をsumやavgに変えるなどして様々なパターンに応用できそう。

PHPBrewに"intl"拡張をインストール

2017/11/07 ※追記 こちらのやり方が簡単!!

qiita.com
できなかったよ。という方が本記事の方法も試してみてもいいかと。

動作環境

バージョン
macOS 10.12.6
PHPBrew 1.22.6
PHP 7.1.0

ことの始まり

CakePHP3を入れたのだが、起動ができない。
intlを有効にしないといけないらしい。

$ bin/cake server -p 8765

PHP Fatal error:  You must enable the intl extension to use CakePHP.
 in /Users/admin/PhpstormProjects/cake3app/config/requirements.php on line 31

Fatal error: You must enable the intl extension to use CakePHP.
 in /Users/admin/PhpstormProjects/cake3app/config/requirements.php on line 31

問題発生

// インストール有無の確認
$ phpbrew ext | grep intl
 [ ] intl

// 拡張intlをインストール
$ phpbrew ext install intl

//・・・

Error: Command failed: /usr/bin/make -C '/Users/admin/.phpbrew/build/php-7.1.0/ext/intl' 'all'  >> '/Users/admin/.phpbrew/build/php-7.1.0/ext/intl/build.log' 2>&1 returns:
The last 5 lines in the log file:
/usr/local/Cellar/icu4c/59.1/include/unicode/unistr.h:3180:7: error: delegating constructors are permitted only in C++11

      UnicodeString(Char16Ptr(buffer), buffLength, buffCapacity) {}

      ^~~~~~~~~~~~~

2 warnings and 3 errors generated.

make: *** [intl_convertcpp.lo] Error 1

エラー_(:3 」∠ )_

解決方法

下記の記事を参考にした。
Install the PHP INTL extension on a Mac

$ cd /Users/admin/.phpbrew/build/php-7.1.0/ext/intl/
$ vim Makefile
CXXFLAGS = -g -O2
↓に変更(33行目付近:"-std=c++11"を追記)
CXXFLAGS = -g -O2 -std=c++11

// ビルド
$ make
$ make install
Installing shared extensions:     /Users/admin/.phpbrew/php/php-7.1.0/lib/php/extensions/no-debug-non-zts-20160303/

// .soファイル生成の確認
$ ls -l /Users/admin/.phpbrew/php/php-7.1.0/lib/php/extensions/no-debug-non-zts-20160303/ | grep intl
-rwxr-xr-x  1 admin  staff   512764  9 19 01:16 intl.so

// "intl.ini"ファイルの作成と、1行記述
$ vim ~/.phpbrew/php/php-7.1.0/var/db/intl.ini
extension=intl.so

intlインストールの確認

intlが有効になっていることを確認

$ phpbrew ext | grep intl
 [*] intl         1.1.0

動作確認

CakePHP3も動作していることを確認できた。

$ bin/cake server -p 8765
Welcome to CakePHP v3.5.0 Console