Java / AWT / Swing: su validazione e dimensioni

Sono un po ‘confuso su come gestire il layout dei miei componenti in Java (voglio farlo manualmente e non gestirlo da un gestore di layout). Ci sono questi metodi in un Component :

  • layout , doLayout
  • validate , invalidate , revalidate
  • validateTree , invalidateTree
  • setSize , setBounds , setPreferredSize
  • getSize , getBounds , getPreferredSize
  • paint , repaint , update
  • updateUI

In precedenza, ho provato a sovraccaricare varie combinazioni di quanto sopra, ma non ero sicuro di quale sovraccaricare e cosa esattamente dovessi fare all’interno e quali funzioni devo chiamare sui componenti del bambino.


Quello che sto facendo ora è:

  • Solo sovraccaricamento di quanto sopra.
  • In doLayout , per tutti i componenti figlio:
    • Chiama child.doLayout .
    • Chiama child.setBounds (a volte prima di child.setBounds , a volte dopo, a volte entrambi).
  • In doLayout , perché sto facendo il layout, ho calcolato automaticamente anche la sua dimensione preferita.
  • In doLayout , chiama this.setPreferredSize .
  • In tutti i costruttori, chiama: this.setLayout(null) .
  • In alcuni costruttori, chiama: this.doLayout . (E se non lo faccio, non viene visualizzato correttamente.)
  • Quando eseguo un’operazione in cui devo rifare il layout (ad esempio, ho aggiunto dynamicmente un campo di testo in un contenitore e quindi voglio ridimensionare il contenitore e anche tutti i genitori di conseguenza), chiamo container.revalidate() .

Problemi rimanenti:

  • Penso di non aver ancora capito veramente quale funzione sta chiamando cosa, cosa devo sovraccaricare e come gestire.
  • In doLayout , chiamo this.setPreferredSize . doLayout stesso spesso dipende da this.getSize() . Per il genitore, spesso child.setBounds dipende da child.getPreferredSize() . Quindi ho il dilemma che in alcuni casi, prima devo chiamare child.doLayout e poi child.setBounds e in alcuni altri casi il contrario. E in alcuni casi ancora più complicato. Quindi sembra che dovrei chiamare this.setPreferredSize da qualche altra parte. Ma dove? Perché deve sempre essere aggiornato quando le dimensioni cambiano (è per questo che ho sovraccaricato setBounds precedenza ma era ancora più brutto).
  • Ho tutto all’interno di un JScrollPane che imposta le barre di scorrimento in base a viewportView.getPreferredSize() . Il revalidate che sto chiamando nei casi in cui voglio ricalcolare il layout causa chiamate doLayout che chiamano correttamente setPreferredSize per tutti i componenti / contenitori nella gerarchia. JScrollPane , sembra che JScrollPane impostato la barra di scorrimento prima che doLayout chiamato e quindi è sempre sbagliato. Come posso ripararlo?

Alcune ulteriori riflessioni (per favore commentarle) su come potrei risolvere il problema di JScrollPane (non ho davvero provato perché richiederebbe alcune importanti riscritture, quindi volevo prima chiedere):

  • Rimuovi tutte setPreferredSize chiamate doLayout in doLayout .
  • Sovraccarico getPreferredSize e chiama doLayout da lì (per ottenere la dimensione preferita).

— O —

  • Invece di richiamare la revalidate quando faccio qualcosa che richiede di ripetere il layout, chiama validateTree .

— O —

  • Invece di richiamare la revalidate quando faccio qualcosa che richiede di rifare il layout, richiama tutto manualmente doLayout e poi una JScrollPanel revalidate su JScrollPanel .

E infine, come faccio per la dipendenza circolare delle dimensioni e delle dimensioni preferite? Cioè, ho abbastanza spesso questo caso:

  • comp.width è corretto nella radice. Posso impostare la larghezza sulla radice e ricorrere in modo ricorsivo a tutti i childs e impostarne la larghezza.
  • comp.height è fissato al bambino più interno e dipende dalla sua larghezza. Quindi, dopo aver impostato tutte le larghezze, posso calcolare e impostare le altezze dal basso verso l’alto.

Non riesco a chiamare setPreferredSize prima di non aver chiamato setSize . E non posso chiamare setSize prima di non aver chiamato setPreferredSize .

Ho paura che tu stia sbagliando.

Usa la risposta di Stewart e imposta un gestore di layout nullo. Ciò consente di impostare la posizione di tutti i componenti chiamando setBounds ().

Quindi, aggiungi un listener nella finestra che viene richiamata ogni volta che la finestra viene ridimensionata e ricalcola le posizioni e chiama setBounds ().

Tutto questo può essere automatizzato se si implementa il codice di posizionamento come gestore layout. (Dopo tutto, posizionando i componenti a ridimensionamento == gestione del layout, così credi o no, stai sviluppando un gestore di layout, è così semplice!)

 public class MyLayout implements LayoutManager,LayoutManager2 { @Override public void addLayoutComponent(Component comp, Object constraints) { // TODO Auto-generated method stub } @Override public Dimension maximumLayoutSize(Container target) { // TODO Auto-generated method stub return null; } @Override public float getLayoutAlignmentX(Container target) { // TODO Auto-generated method stub return 0; } @Override public float getLayoutAlignmentY(Container target) { // TODO Auto-generated method stub return 0; } @Override public void invalidateLayout(Container target) { // TODO Auto-generated method stub } @Override public void addLayoutComponent(String name, Component comp) { // TODO Auto-generated method stub } @Override public void removeLayoutComponent(Component comp) { // TODO Auto-generated method stub } @Override public Dimension preferredLayoutSize(Container parent) { // TODO Auto-generated method stub return null; } @Override public Dimension minimumLayoutSize(Container parent) { // TODO Auto-generated method stub return null; } @Override public void layoutContainer(Container parent) { // Now call setBounds of your components here } } 

Nel metodo layoutContainer, è ansible chiamare setBounds di tutti i componenti. Questo metodo viene chiamato quando la finestra è disposta inizialmente, così come ogni volta che c’è un ridimensionamento.

Quindi, quando metti le cose ad una finestra o ad un JPanel, semplicemente impostaLayoutManager (nuovo MyLayoutManager ()) e sei d’oro.

Tuttavia, rimane ancora una domanda molto cruda. Il tuo gestore di layout è una class separata, ma deve comunque accedere ai componenti che sono stati creati altrove nel codice della finestra. La soluzione brute-force è semplicemente ottenere un riferimento a tutti i componenti nel costruttore, ad esempio:

 class MyWindow extends JFrame { public MyWindow() { JLabel label=new JLabel("Hello"); JButton button=new JButton("Ok"); setLayoutManager(new MyLayoutManager(label,button)); // PASS THEM add(label); add(button); pack(); setVisible(true); } } 

Certo, è un approccio ingenuo, ma potrebbe funzionare. Il modo corretto per gestirlo è implementare addLayoutComponent nel proprio gestore di layout, che viene chiamato ogni volta che si aggiunge qualcosa a un JFrame (come quando si chiama add (etichetta)). In questo modo il gestore di layout è a conoscenza dei componenti nel layout.

Tutti i tuoi problemi e domande potrebbero essere risolti se hai usato LayoutManagers. Sembra che tu stia cercando davvero di evitarli, ma in realtà sono il tuo miglior alleato quando si tratta di strutturare gli elementi della GUI.

Un sacco di persone cercano di evitare LayoutManager all’inizio a causa della curva di apprendimento. Quello che ho trovato molto utile quando stavo imparandoli era la guida visiva per i gestori di layout: http://download.oracle.com/javase/tutorial/uiswing/layout/visual.html .

Il mio suggerimento sarebbe quello di trovare un modo per far funzionare il layout con LayoutManagers. Anche tu risolvi alcuni di questi problemi, apportare modifiche o qualcun altro che debba leggere il tuo codice richiederà molto tempo. Inoltre, con tutto ciò che devi fare per far funzionare tutto questo c’è un sacco di spazio per i bug dovuti a lasciare qualcosa su un componente.

Dopo aver letto i tuoi commenti, sembra che tu voglia solo fare il posizionamento assoluto usando un layout nullo. C’è un tutorial su di esso, ma l’essenza è che tu imposti il ​​gestore di layout a null per il tuo contenitore, e poi chiama setBounds () su ciascun componente in quel contenitore per impostarne le dimensioni e la posizione, come questo:

 JPanel p = new JPanel(); p.setLayout(null); JButton b = new JButton ("Hit It"); p.add(b); b.setBounds(new Rectangle(10, 20, 100, 50));