4

JTextField を含むいくつかの子要素を持つカスタム Swing コンポーネントがあります。コンテナに JTextField と同じベースラインを設定したいので、コンテナの getBaseline メソッドをオーバーライドしました。ただし、コンテナーに対して getBaseline が呼び出されたときに、JTextField の位置が常に設定されるとは限りません。コンテナーの getBaseline メソッドに doLayout への呼び出しを追加しようとしましたが、役に立ちませんでした。私が欲しいのは次のとおりです。

public int getBaseline(int w, int h) {
    Dimension size = textField.getPreferredSize();
    int textBaseline = textField.getBaseline(size.width, size.height);
    int textY = textField.getY();
    return textY + textBaseline;
}

上記のコードでは、textY は 0 です。

問題を説明するためにいくつかのコードを書きました。初めて「追加」をクリックすると、ベースラインが間違っています。2回目は正解です。

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

class CustomComponent extends JPanel {

    public CustomComponent() {
        setLayout(new GridBagLayout());
        textField.setColumns(8);
        layoutElements();
    }

    public void addComponent() {
        JPanel comp = new JPanel();
        comp.setPreferredSize(new Dimension(50, 200));
        comp.setBackground(Color.red);
        otherComponents.add(comp);
        layoutElements();
    }

    public int getBaseline(int w, int h) {
        Dimension size = textField.getPreferredSize();
        return textField.getY() + textField.getBaseline(size.width, size.height);
    }

    public Component.BaselineResizeBehavior getBaselineResizeBehavior() {
        return Component.BaselineResizeBehavior.CONSTANT_DESCENT;
    }

    private void layoutElements() {
        removeAll();
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.anchor = GridBagConstraints.SOUTH;
        add(textField, constraints);

        for (JPanel comp : otherComponents)
            add(comp, new GridBagConstraints());
        if (getParent() != null)
            getParent().validate();
    }

    private JTextField textField = new JTextField();
    private ArrayList<JPanel> otherComponents = new ArrayList<JPanel>();
}

public class Main {

    public static void main(String[] args) {
        JFrame frame = new JFrame();
        JPanel panel = new JPanel();
        panel.setLayout(new GridBagLayout());
        frame.getContentPane().add(panel);

        JButton addComponent = new JButton("Add");
        GridBagConstraints constraints = new GridBagConstraints();
        constraints.anchor = GridBagConstraints.BASELINE;
        panel.add(addComponent, constraints);

        final CustomComponent customComp = new CustomComponent();
        panel.add(customComp, constraints);

        addComponent.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                customComp.addComponent();
            }
        });

        frame.setSize(400, 300);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }
}
4

1 に答える 1

1

最初のコメント、単なる確認:

GridBag(正確な例)またはMigLayoutのいずれかの外部レイアウトの問題を確認できます。外側のレイアウトがFlowLayoutの場合、ハックする可能性があります。その場合、ベースラインを計算する前にレイアウトを強制するだけで十分です。

@Override
public int getBaseline(int w, int h) {
    // helps with simple managers like FlowLayout
    // detoriates with powerful managers like GridBag or Mig
    doLayout();
    Dimension size = textField.getPreferredSize();
    return textField.getY() + textField.getBaseline(size.width, size.height);
}

メインのアウターパネル:

FlowLayout flow = new FlowLayout();
flow.setAlignOnBaseline(true);
panel.setLayout(flow);

それほど多くはありませんが、ここからもう少し掘り下げることができるかもしれません...

考えられる理由について、暫定的な解決策(?またはハック)と考察を編集します

強制的な2パスレイアウトプロセスが必要なようです。

private void layoutElements() {
    removeAll();
    GridBagConstraints constraints = new GridBagConstraints();
    constraints.anchor = GridBagConstraints.SOUTH;
    add(textField, constraints);

    for (JPanel comp : otherComponents)
        add(comp, new GridBagConstraints());
    if (getParent() != null) {
        getParent().validate();
        getParent().revalidate();
    }
}

検証と再検証の順序に注意してください。いくつかの理由で、両方が必要です。正確なメカニズムについてはわかりません。ランダムに推測するだけです。

  • レイアウトはトップダウンで行われるため、親が子供に依存してレイアウトした場合、すべての孫が2パスの必要性を説明します。孫に関連するベースラインはそのような条件になります
  • 最初のパスは、より広いコンテキストで発生します。つまり、最初の検証の最後に、すべての状態が完全に完了しているわけではありません。次に、再検証により、進行中のすべての変更が完了したに2番目のパスが発生することが保証されます。
于 2012-07-31T11:24:29.100 に答える