Вкладка, добавленная в JTabbedPane, не отображается

У меня есть класс VTreePanel, который наследуется от CPanel, который наследуется от JPanel. В классе есть объект JSplitPane, который разделен на две области: левую и правую. В левой части находится объект выбора древовидного меню. Справа он содержит объект JTabbedPane. Класс VTreePanel выглядит следующим образом:

public final class VTreePanel extends CPanel
    implements ActionListener
{
    private JSplitPane centerSplitPane = new JSplitPane();

    private JTabbedPane tabbedPane;

    ...

    // GET method for the tabbedPane    
    public JTabbedPane getTabbedPane() {
        return tabbedPane;
    }

    // Constructor
    public VTreePanel(int WindowNo, boolean hasBar, boolean editable)
    {

        ...

        tabbedPane = new JTabbedPane();
        centerSplitPane.add(treePart, JSplitPane.LEFT);
        centerSplitPane.add(tabbedPane, JSplitPane.RIGHT);  // Look at this

        ...
    }

}

В конструкторе я добавил выбор дерева (treePart) и объект JTabbedPane (tabbedPane) в объект JSplitPane (centerSplitPane). Я еще не добавляю вкладку в tabbedPane. Посмотрите на скриншот ниже:

http://i45.tinypic.com/2v3j0nl.jpg

Тогда как мне добавить вкладку, когда пользователь щелкает одно из меню?

У меня есть класс AMenu, в котором реализован PropertyChangeListener, который запускает метод propertyChange, когда пользователь щелкает меню:

public final class AMenu extends CFrame
    implements ActionListener, PropertyChangeListener, ChangeListener
{

    private VTreePanel treePanel = null;    // this is the VTreePanel object

    ...

    public void propertyChange(PropertyChangeEvent e)
    {
        ...

        // Here I pass the VTreePanel object as parameter to AMenuStartItem thread object
        (new AMenuStartItem(cmd, true, sta, this, treePanel)).start();
    }

}

Вы можете видеть, что у меня есть объект VTreePanel (treePanel), и я передаю объект VTreePanel в качестве параметра потоку AMenuStartItem. AMenuStartItem содержит логику, выполняющую добавление Tab в JTabbedPane (помните, объект JTabbedPane (tabbedPane) находится в VTreePanel).

Вот класс потока AMenuStartItem:

public class AMenuStartItem extends Thread implements ActionListener
{
    private VTreePanel m_vtreePanel;

    public AMenuStartItem (int ID, boolean isMenu, String name, AMenu menu, VTreePanel vtreepanel)
    {
        ...

        m_vtreePanel = vtreepanel;  // save the VTreePanel object
    }

    // The thread method that executed when thread is started
    public void run()
    {
        ...

        startWindow(0, cmd);

        ...
    }

    private void startWindow(int AD_Workbench_ID, int AD_Window_ID)
    {
        ...

        // Here I perform adding new tab
        m_vtreePanel.getTabbedPane().addTab(frame.getTitle(), frame.getAPanel());

        ...
    }

}

Таким образом, getTabbedPane() вернул объект JTabbedPane, и метод addTab() был выполнен, но вкладка вообще не появилась.

Кто-нибудь знает, как решить эту проблему?


person null    schedule 18.10.2012    source источник
comment
Во-первых, вы сломали один из самых важных запусков Swing, вы пытаетесь обновить компоненты пользовательского интерфейса вне Поток отправки событий. Прочтите параллелизм в Swing, чтобы узнать, как обойти Это. Во-вторых, вам нужно вызвать аннулирование и перерисовку на JTabbedPane.   -  person MadProgrammer    schedule 18.10.2012
comment
Вы имели в виду, что я должен использовать этот код в методе propertyChange()? javax.swing.SwingUtilities.invokeLater (новый AMenuStartItem (cmd, true, sta, this, treePanel)); Я пробовал, но вкладка так и не появилась.   -  person null    schedule 18.10.2012
comment
Привет, MadProgrammer, я допустил ошибку при тестировании приложения ^^. Оказалось, что я тестировал не на той главной вкладке (главная вкладка — это горизонтальный ряд вкладок, состоящий из: бухгалтерского учета, финансов, CRM и т. д.). Так получилось, что все работает хорошо. Я удалю свой вопрос в течение следующего часа. Однако мой исходный код хорошо работает без использования SwingUtilities.invokeLater(). Спасибо за совет в любом случае.   -  person null    schedule 18.10.2012
comment
Вплоть до тех пор, пока вы не получите какой-нибудь странный артефакт краски, который время от времени появляется случайным образом. Приятно знать, что вы решили проблему аутсорсинга   -  person MadProgrammer    schedule 18.10.2012
comment
Как мы определяем, должен ли поток вызываться с помощью invokeLater()? Фактически, внутри потока AMenuStartItem он также создал экземпляр объекта AWindow (имя переменной: frame) и вызвал функцию frame.initWindow(), которая выполняет добавление дочернего компонента (панели инструментов, меню, строки состояния и т. д.). Я полагаю, вы знали из моего предыдущего вопроса, что AWindow является подклассом JFrame. Однако автор кода не использует SwingUtilities.invokeLater для запуска потока AMenuStartItem.   -  person null    schedule 18.10.2012
comment
Если EventQueue.isDispatchingThread возвращает false, то вы находитесь за пределами EDT. Вы НИКОГДА не должны создавать или взаимодействовать с компонентом пользовательского интерфейса снаружи, если этот поток   -  person MadProgrammer    schedule 18.10.2012
comment
где я должен поместить EventQueue.isDispatchThread()? Внутри нити?   -  person null    schedule 18.10.2012
comment
Вы должны EventQueue.isDispatchThread() везде, где вы не уверены, вызывается ли метод из контекста EDT, и вы хотите обновить пользовательский интерфейс   -  person MadProgrammer    schedule 18.10.2012
comment
Я обновил свой ответ примером   -  person MadProgrammer    schedule 18.10.2012


Ответы (1)


Все взаимодействия с пользовательским интерфейсом ДОЛЖНЫ выполняться через поток диспетчеризации событий, без исключений...

public class AMenuStartItem extends Thread implements ActionListener
{
    private VTreePanel m_vtreePanel;

    public AMenuStartItem (int ID, boolean isMenu, String name, AMenu menu, VTreePanel vtreepanel)
    {
        ...

        m_vtreePanel = vtreepanel;  // save the VTreePanel object
    }

    // The thread method that executed when thread is started
    public void run()
    {
        ...

        startWindow(0, cmd);

        ...
    }

    private void startWindow(final int AD_Workbench_ID, final int AD_Window_ID)
    {
        ...

        if (EventQueue.isDispatchingThread()) {
            // This is safe, we're in the EDT
            m_vtreePanel.getTabbedPane().addTab(frame.getTitle(), frame.getAPanel());
            m_vtreePanel.getTabbedPane().invalidate();
            m_vtreePanel.getTabbedPane().repaint();
        } else {
            // This is unsafe, we need to resync with the EDT
            SwingUtilities.invokeLater(new Runnable() {
                public void run() {
                    // Here I perform adding new tab
                    m_vtreePanel.getTabbedPane().addTab(frame.getTitle(), frame.getAPanel());
                    m_vtreePanel.getTabbedPane().invalidate();
                    m_vtreePanel.getTabbedPane().repaint();

                }
            });
        }

        ...
    }

}
person MadProgrammer    schedule 18.10.2012
comment
Привет, MadProgrammer, я допустил ошибку при тестировании приложения ^^. Оказалось, что я тестировал не на той главной вкладке (главная вкладка — это горизонтальный ряд вкладок, состоящий из: бухгалтерского учета, финансов, CRM и т. д.). Так получилось, что все работает хорошо. Я удалю свой вопрос в течение следующего часа. Однако мой исходный код хорошо работает без использования SwingUtilities.invokeLater(). Спасибо за совет в любом случае. - person null; 18.10.2012
comment
Нужны ли invalidate() и repaint()? Я оставляю их и не нашел никаких проблем. У меня также были эти строки: m_vtreePanel.getTabbedPane.indexOfComponent() и m_vtreePanel.getTabbedPane.setSelectedIndex(), должен ли я также поместить эти две строки в invokeLater()? - person null; 18.10.2012
comment
недействительным помечает контейнер недействительным, указывая на то, что контейнер должен быть выложен, запрос перерисовки, чтобы менеджер перерисовки добавил наш контейнер в черный список для обновления во время следующего цикла рисования. Нужны ли они, не всегда, но если у вас есть проблемы с не отображаемыми компонентами, это помогает. setSelectedIndex изменяет пользовательский интерфейс, так что да, его нужно вызывать из EDT. - person MadProgrammer; 18.10.2012
comment
Так что многие примеры Java в Интернете не показывают хорошей практики (без вызова из EDT), например: java2s.com/Code/Java/Swing-JFC/CardLayoutDemo.htm по сравнению с этим примером оракула java: docs.oracle.com/javase/tutorial/uiswing/examples/layout/ - person null; 18.10.2012
comment
спасибо за вашу помощь, я решил оставить этот вопрос. Кто знает, кому это пригодится в будущем. - person null; 18.10.2012