Java / AWT / Swing: о валидации и размерах - PullRequest
1 голос
/ 12 января 2011

Я немного озадачен тем, как управлять компоновкой моих компонентов в Java (я хочу сделать это вручную, а не обрабатывать это менеджером компоновки). Есть такие методы в Component:

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

Раньше я пытался перегрузить различные комбинации из вышеперечисленного, но я не был полностью уверен, какую из них перегрузить и что именно я должен делать внутри и какие функции я должен вызывать для дочерних компонентов.


Что я делаю сейчас:

  • Только перегрузка doLayout из вышеперечисленного.
  • В doLayout, для всех дочерних компонентов:
    • Звоните child.doLayout.
    • Позвоните child.setBounds (иногда до child.setBounds, иногда после, иногда до обоих).
  • В doLayout, поскольку я делаю макет, я автоматически рассчитал и его предпочтительный размер.
  • В doLayout, звоните this.setPreferredSize.
  • Во всех конструкторах звоните: this.setLayout(null).
  • В некоторых конструкторах вызывайте: this.doLayout. (И если я этого не сделаю, он не будет отображаться правильно.)
  • Когда я делаю какую-то операцию, где мне нужно переделать макет (например, я динамически добавил некоторое текстовое поле в некоторый контейнер и, таким образом, я хочу изменить размер контейнера и всех его родителей соответственно), я вызываю container.revalidate().

Остальные проблемы:

  • Мне кажется, я до сих пор не понял, что и как вызывает функция, что мне нужно перегрузить и как с ней работать.
  • В doLayout, я звоню this.setPreferredSize. doLayout само по себе часто зависит от this.getSize(). Для родителей часто child.setBounds зависит от child.getPreferredSize(). Поэтому у меня есть дилемма, что в некоторых случаях мне сначала нужно позвонить child.doLayout, а затем child.setBounds, а в некоторых других случаях наоборот. А в некоторых случаях еще сложнее. Так что все кажется, что мне нужно позвонить this.setPreferredSize куда-нибудь еще. Но где? Потому что он всегда должен обновляться при изменении размера (поэтому я перегрузил setBounds ранее, но это было еще страшнее).
  • У меня есть все внутри JScrollPane, который устанавливает полосы прокрутки в соответствии с viewportView.getPreferredSize(). revalidate, который я вызываю в случаях, когда я хочу пересчитать расположение, вызывает doLayout вызовов, которые правильно вызывают setPreferredSize для всех компонентов / контейнеров в иерархии. Хотя, похоже, что JScrollPane устанавливает свою полосу прокрутки до того, как вызовет doLayout, и, следовательно, это всегда неправильно. Как я могу это исправить?

Некоторые дальнейшие мысли (пожалуйста, прокомментируйте их) о том, как, возможно, я мог бы решить проблему JScrollPane (на самом деле не пытался, потому что это потребовало бы некоторых серьезных переписываний, поэтому я сначала хотел спросить):

  • Удалить все setPreferredSize звонки в doLayout.
  • Перегрузите getPreferredSize и позвоните doLayout оттуда (чтобы получить предпочтительный размер).

- ИЛИ -

  • Вместо того, чтобы звонить revalidate, когда я делаю что-то, что требует повторной компоновки, звоните validateTree.

- ИЛИ -

  • Вместо вызова revalidate, когда я делаю что-то, что требует повторного макета, вызовите все doLayout вручную и затем revalidate на JScrollPanel.

И, наконец, как мне перейти к круговой зависимости размера и предпочтительного размера? У меня довольно часто бывает такой случай:

  • comp.width исправлено в корне. Я могу установить ширину корня и рекурсивно перейти ко всем дочерним элементам и установить его ширину.
  • comp.height фиксируется на самом внутреннем дочернем элементе и зависит от его ширины. Поэтому после того, как я установил все ширины, я могу вычислить и установить высоту снизу вверх.

Я не могу позвонить setPreferredSize, пока я не позвонил setSize. И я не могу позвонить setSize, пока я не позвонил setPreferredSize.

Ответы [ 3 ]

3 голосов
/ 12 января 2011

Все ваши проблемы и вопросы могут быть решены, если вы используете LayoutManager. Похоже, вы очень стараетесь избегать их, но на самом деле они являются вашим лучшим союзником, когда речь идет о компоновке элементов графического интерфейса.

Многие люди сначала стараются избегать LayoutManager из-за кривой обучения. Что я нашел очень полезным, когда изучал их, так это наглядное руководство для менеджеров по макету: http://download.oracle.com/javase/tutorial/uiswing/layout/visual.html.

Мое предложение - найти способ заставить ваш макет работать с LayoutManager. Даже если вы решите некоторые из этих проблем, внесение изменений или кому-то еще, придется читать ваш код, будет очень много времени. Плюс ко всему, что вам нужно сделать, чтобы заставить это работать, есть много места для ошибок из-за того, что что-то не хватает на одном компоненте.

1 голос
/ 16 февраля 2016

Боюсь, вы ошиблись.

Используйте ответ Стюарта и установите нулевой менеджер раскладки. Это позволяет вам устанавливать положение всех компонентов, вызывая setBounds ().

Затем добавьте слушателя в окно, которое вызывается при каждом изменении размера окна, и пересчитайте позиции и вызовите setBounds ().

Это все может быть автоматизировано, если вы реализуете свой код позиционирования как менеджер раскладки. (В конце концов, располагая компоненты в соответствии с изменением размера == управлением макетом, поверьте или нет, вы разрабатываете менеджер макетов, это так просто!)

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
    }

}

В методе layoutContainer вы можете вызывать setBounds всех компонентов. Этот метод вызывается при первоначальном размещении окна, а также каждый раз, когда происходит изменение размера.

Тогда, когда вы, например, положить вещи в окно или в JPanel, просто setLayoutManager (новый MyLayoutManager ()), и вы золотой.

Однако очень грубый вопрос все еще остается. Ваш менеджер компоновки - это отдельный класс, но он все же должен иметь доступ к компонентам, которые вы создали в другом месте кода вашего окна. Решение проблемы грубой силы состоит в том, чтобы просто получить ссылку на все компоненты в конструкторе, например ::

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);
   }
}

Конечно, это наивный подход, но может сработать. Правильный способ решения этой проблемы заключается в реализации addLayoutComponent в вашем менеджере компоновки, который вызывается всякий раз, когда вы добавляете что-либо в JFrame (например, при вызове add (label)). Таким образом, менеджер макета узнает о компонентах в макете.

0 голосов
/ 13 января 2011

После прочтения ваших комментариев кажется, что вы просто хотите сделать абсолютное позиционирование с использованием нулевого макета.В нем есть учебник , но суть в том, что вы задаете для диспетчера макета значение null для своего контейнера, а затем вызываете setBounds () для каждого компонента в этом контейнере, чтобы установить его размер и положение, например:

JPanel p = new JPanel();
p.setLayout(null);

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