JLabel com indicação de Status não atualiza - SWING [Resolvido]

Estou com um problema que creio ser comum entre os programadores que utilizam o SWING e um dos piores…
Eu quero que o usuário ao clicar em um botão, faça o aplicativo realizar uma busca no sistema e que um jlabel indique ao mesmo que essa operação está sendo feita (não quero usar jprogressbar porquê é para uma busca pequena, não sendo necessário usar esse componente).
O problema é que ele não atualiza o jlabel. A tela fica “paralisada” e só quando termina de executar a busca, é que tudo atualiza…
Quando tento usar paint ele sobrescreve o texto que já estava no jlabel, repaint não surte efeito algum… Já tentei usar Thread e também não obtive sucesso. Tampouco com SwingUtilities ou SwingWorker (para dizer a verdade, não consegui compreender o SwingWorker…).

Eu queria fazer isso…algo simples mas que há muito tempo me perturba, porquê eu não consigo fazer funcionar…

Estou tentando usar os seguintes métodos:


    private void jButtonBuscaStatusActionPerformed(java.awt.event.ActionEvent evt) {                                                   

        //"tenta" alterar o status do jlabel para indicar ao usuário que a busca está sendo realizada
        try {
            alterarStatusLoading();
        } catch (Exception e) {
        }

        ...
        ...  //código que executa a busca
        ...
        ...

        //ao terminar a busca, altera o texto de status no jlabel
        jLabelStatus.setText("Busca concluída.");
        jLabelStatus.setIcon( null );        //retira a exbibição do gif de loading no jlabel

        } 

    /* método que configura o jlabel para exibir um texto indicando a busca
     * e insere uma animação gif junto com o texto, no jlabel
     */
    private final void alterarStatusParaLoading()
                                                                         throws Exception {
        jLabelStatus.setText("Realizando busca... aguarde!");
        jLabelStatus.setIconTextGap(8);
        jLabelStatus.setIcon(new ImageIcon(getClass().getResource("/br/com/empresa/ferramentas/multa/imgs/searching.gif")));
    }  

         

Problema comum? Sim. Um dos piores? Nem de longe.
Você precisa mesmo usar threads ou o SwingWorker.

Vai ficar mais ou menos assim:

[code]private void jButtonBuscaStatusActionPerformed(java.awt.event.ActionEvent evt) {

 //"tenta" alterar o status do jlabel para indicar ao usuário que a busca está sendo realizada  
 try {  
     alterarStatusLoading();  
 } catch (Exception e) {  
 }  
 new Thread(new Runnable() {
      public void run() {
         ...  
         ...  //código que executa a busca  
         ...  
         ...  

           //ao terminar a busca, altera o texto de status no jlabel  
           jLabelStatus.setText("Busca concluída.");  
           jLabelStatus.setIcon( null );        //retira a exbibição do gif de loading no jlabel  
      }}).start();   
 }   

}[/code]

O que acontece é o seguinte. O swing tem uma fila de mensagens.
Toda vez que você atualiza um JLabel, ele muda suas propriedades e chama o método repaint(), indicando ao Swing que ele deve redesenhar a tela. Esse método, enfilera uma requisição, que será processada assim que a thread do Swing estiver desocupada.

Entretanto, ações de botão também são tratadas na thread do Swing. Portanto, se seu código que executa a busca foi disparado pelo método do botão, ele se torna um dos itens da fila do swing a ser processado. Cada troca de JLabel lá, só empilhará um comando de pintura para ser executado depois que seu método terminar. Por isso, outros comandos como invalidate() repaint() e updateUI() serão igualmente inúteis.

A solução para isso é fazer a execução da busca em outra thread. Seja disparando a thread diretamente (como eu fiz), ou indiretamente através do SwingWorker.

1 curtida

VALEU! DEU CERTO!
MUITO OBRIGADO! MEUS PROBLEMAS DE JLABEL CONGELADOS ACABARAM (e faz tempo que tinha esses problemas…)!!!

:smiley: :lol:

Só um detalhe. Quando estiver rodando de outra thread, o ideal é trocar as linhas que alteram os componentes do swing, como essa:

jLabelStatus.setText("Busca concluída."); jLabelStatus.setIcon( null ); //retira a exbibição do gif de loading no jlabel

Por isso aqui:

EventQueue.invokeLater(new Runnable() { public void run() { jLabelStatus.setText("Busca concluída."); jLabelStatus.setIcon( null ); //retira a exbibição do gif de loading no jlabel }} );

Isso porque os comandos de pintura devem, necessariamente, serem executados na thread do swing. O resto do processamento, sim, pode ir para fora. Eu criei uma classezinha utilitária para reduzir um pouco a verbozidade disso. Com ela você pode chamar:

Acho que não tem uma função para o setIcon, mas não deve ser difícil inserir.

import java.awt.Color;
import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;

import javax.swing.AbstractButton;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.border.Border;
import javax.swing.text.JTextComponent;

/**
 * This class was created to support swing updates threads other than Swing
 * EventQueue thread. It automatically wrapps most common operations into a
 * runnable and submit it to the EventQueue. <br>
 * Most part of the operations done after all events are processed in the
 * EventQueue, by using the invokeAndLater method.
 */
public class GuiSync
{

    /**
     * Sets whether or not the given component is enabled. A component that is
     * enabled may respond to user input, while a component that is not enabled
     * cannot respond to user input. Some components may alter their visual
     * representation when they are disabled in order to provide feedback to the
     * user that they cannot take input.
     * <p>
     * <b>This method uses the invokeAndWait call.</b>
     * 
     * @param component The component to set.
     * @param enabled True if this component should be enabled, false otherwise
     * @return True if the setEnabled method changed the component state for
     *         sure, false if not.
     */
    public static boolean setEnabled(final JComponent component,
            final boolean enabled)
    {
        if (EventQueue.isDispatchThread())
        {
            component.setEnabled(enabled);
            return true;
        }
        
        class Enabler implements Runnable
        {
            public void run()
            {
                component.setEnabled(enabled);
            }
        }

        Enabler enabler = new Enabler();
        try
        {
            EventQueue.invokeAndWait(enabler);
            return true;
        }
        catch (InterruptedException e)
        {
            EventQueue.invokeLater(enabler);
        }
        catch (InvocationTargetException e)
        {
            if (e.getCause() instanceof RuntimeException)
                throw (RuntimeException) e.getCause();
        }
        return false;
    }

    /**
     * Sets the background color of the given component.
     * 
     * @param component The component to set color.
     * @param color the desired background &lt;code&gt;Color&lt;/code&gt;
     * @see GUISync#setForeground(JComponent, Color)
     * @see GUISync#setColor(JComponent, ColorContainer)
     */
    public static void setBackground(final JComponent component,
            final Color color)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                component.setBackground(color);
            }
        });
    }

    /**
     * Sets the background color of the given component.
     * 
     * @param component the component to set color.
     * @param color the desired background &lt;code&gt;Color&lt;/code&gt;
     */
    public static void setForeground(final JComponent component,
            final Color color)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                component.setForeground(color);
            }
        });
    }

    /**
     * Sets the foreground and background color of the given component, based in
     * the &lt;code&gt;ColorContainer&lt;/code&gt;.
     * 
     * @param component The component to change colors.
     * @param color A &lt;code&gt;ColorContainer&lt;/code&gt; with the color.
     */
    public static void setColor(final JComponent component,
            final ColorContainer color)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                component.setForeground(color.getForeground());
                component.setBackground(color.getBackground());
            }
        });
    }

    /**
     * Defines the single line of text this component will display. If the value
     * of text is null or empty string, nothing is displayed. The default value
     * of this property is null.
     * 
     * @param label The label to change.
     * @param text A text or an &lt;code&gt;HTML&lt;/code&gt;.
     */
    public static void setText(final JLabel label, final String text)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                label.setText(text);
            }
        });
    }

    /**
     * <b>There's no need to use this method.</b> All
     * &lt;code&gt;JTextComponent&lt;/code&gt;s should be thread safe.
     * 
     * @param textComponent The text component.
     * @param text The new text.
     */
    public static void setText(final JTextComponent textComponent,
            final String text)
    {
        textComponent.setText(text);
    }

    /**
     * Sets the button's text.
     * 
     * @param button The button to change the text.
     * @param text the string used to set the text
     */
    public static void setText(final AbstractButton button, final String text)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                button.setText(text);
            }
        });
    }

    /**
     * Sets the border of the given component. The &lt;code&gt;Border&lt;/code&gt; object
     * is responsible for defining the insets for the component (overriding any
     * insets set directly on the component) and for optionally rendering any
     * border decorations within the bounds of those insets. Borders should be
     * used (rather than insets) for creating both decorative and non-decorative
     * (such as margins and padding) regions for a swing component. Compound
     * borders can be used to nest multiple borders within a single component.
     * <p>
     * Although technically you can set the border on any object that inherits
     * from &lt;code&gt;JComponent&lt;/ocde&gt;, the look and feel implementation of many 
     * standard Swing components doesn't work well with user-set borders.  
     * In general, when you want to set a border on a standard Swing
     * component other than &lt;code&gt;JPanel&lt;/code&gt; or &lt;code&gt;JLabel&lt;/code&gt;,
     * it's recommended that you put the component in a &lt;code&gt;JPanel&lt;/code&gt;
     * and set the border on the &lt;code&gt;JPanel&lt;/code&gt;.
     * <p>
     * 
     * @param component The component that will receive the new border.
     * @param border the border to be rendered for this component
     */
    public static void setBorder(final JComponent component, final Border border)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                component.setBorder(border);
            }
        });
    }

    /**
     * Makes the given component visible or invisible. Overrides
     * &lt;code&gt;Component.setVisible&lt;/code&gt;.
     * 
     * @param component The component that will be made visible or invisible
     * @param visible true to make the component visible; false to make it
     *        invisible
     */
    public static void setVisible(final JComponent component,
            final boolean visible)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                component.setVisible(visible);
            }
        });
    }
}