プログラムdeタマゴ

nodamushiの著作物は、文章、画像、プログラムにかかわらず全てUnlicenseです

入力候補が出るTextField作ってみた

 夏風邪ひいて熱が出たり咳が出たり鼻水出たりでしんどいですが、皆さん体調いかがでしょうか。


 さて、風邪ひいていようと暇なものは暇なので、タイトル通りの物を作っていました。
 イメージとしてはこんなのね。(Firefoxの検索窓)
f:id:nodamushi:20150813230113p:plain:w240

 ソースコードはこちら。NodamushiFXControls
 クラスは CompletionTextFieldです。
 候補や挿入方法はCandidateインターフェースで定義してあるだけなので、よく言えば自由がある、悪く言えばユーザーが用意しないといけない。

 デモ用に作ったCandidateインターフェースの実装(ForwardMatchCandidate)ではこんな感じに動作します。
f:id:nodamushi:20150813230150p:plain:w240
f:id:nodamushi:20150813230152p:plain:w240
f:id:nodamushi:20150813230154p:plain:w240





自作PopupControlの作り方

 意外とここの部分でつまった。
 最も単純な方法では、TooltipのGraphicに表示させたいNodeを突っ込んで表示するってのが楽そうだけど、やっぱそれだと微妙だよね。
 Tooltipの元となっているPopupWindowないし、PopupControlから派生させたい物です。


 今回はPopupControlにNodeを表示させる方法を調べたところ、単純にSkinを登録するだけでした。
 私が作ったコードではこんな感じ。ComboBoxSkinのコードを参考にしています。(ていうか、ほとんどここはまんまか)

    final PopupControl p = new PopupControl(){
      @Override
      public Styleable getStyleableParent(){
        return getSkinnable();//ポップアップ元のノードを返しています
      }
      {
        setSkin(new Skin<Skinnable>(){
          @Override public Skinnable getSkinnable(){return CompletionTextFieldSkin.this.getSkinnable();}
          @Override public Node getNode(){return getPopupContent();}
          @Override public void dispose(){}
          });
      }
    };





JavaFX8ではListViewの行の高さを取得できなかった

 ListViewを表示するときに、ある一定の行までは表示して、それ以上はスクロールバーで対応させたい。
 つまり、n行を表示するための高さを取得して、それをPrefHeightとすれば良いのだが、この1行の高さというのが実は取得できない。

 でも、ComboBoxとかではそれを実現しているように見える。ではどうやっているのかとソースコードを調べ、最終的に次のコードに落ち着いた。

  private static final Class<?> VIRTUALCONTAINERBASE;
  private static final Method
  GET_VIRTUAL_FLOW_PREFERRED_HEIGHT,//n行分の高さを取得するメソッド
  UPDATE_ROW_COUNT;//今何行分のアイテムがあるのかSkinに強制的に更新させる
  static{
    Class<?> c;
    Method m,m2;
    try {
      c=Class.forName("com.sun.javafx.scene.control.skin.VirtualContainerBase");
      m = c.getDeclaredMethod("getVirtualFlowPreferredHeight", int.class);
      m2 =c.getDeclaredMethod("updateRowCount");
      m.setAccessible(true);
      m2.setAccessible(true);
    } catch (final Exception e) {
      c = null;m=null;m2=null;
    }
    VIRTUALCONTAINERBASE=c;
    GET_VIRTUAL_FLOW_PREFERRED_HEIGHT=m;
    UPDATE_ROW_COUNT=m2;
  }


  private double getListViewPrefHeight() {
    double ph;
    final int maxRows = min(textField.getVisibleRowCount(),getCandidateSize());
    if (VIRTUALCONTAINERBASE!=null &&listView.getSkin()!=null &&
        VIRTUALCONTAINERBASE.isAssignableFrom(listView.getSkin().getClass())) {
        try{
          //これ↓を挟まないと次のgetVirtualFlowPrefreredHeightでちゃんと高さが出なかった。
          UPDATE_ROW_COUNT.invoke(listView.getSkin());
          ph =(double) GET_VIRTUAL_FLOW_PREFERRED_HEIGHT.invoke(listView.getSkin(), maxRows);
        }catch(final Exception e){
          final double ch = maxRows * 25;
          ph = Math.min(ch, 200);
        }
    } else {
      final double ch = maxRows * 25;
      ph = Math.min(ch, 200);
    }
    return ph;
  }


 う~ん、リフレクションかぁ。com.sunパッケージかぁ(´Д⊂ヽ

 とりあえず、これに期待するしかないっすね。。。いったいどの程度JavaFX9でPublic APIが増えるんでしょうかね。
 
JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization



 特に落ちもまとめもありませんが、以上。