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…)!!!
: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 <code>Color</code>
* @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 <code>Color</code>
*/
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 <code>ColorContainer</code>.
*
* @param component The component to change colors.
* @param color A <code>ColorContainer</code> 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 <code>HTML</code>.
*/
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
* <code>JTextComponent</code>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 <code>Border</code> 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 <code>JComponent</ocde>, 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 <code>JPanel</code> or <code>JLabel</code>,
* it's recommended that you put the component in a <code>JPanel</code>
* and set the border on the <code>JPanel</code>.
* <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
* <code>Component.setVisible</code>.
*
* @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);
}
});
}
}