I'm making a touch screen keyboard. I want the alphabetic keys to change text when I press shift (from lower case to upper case). Here's a snippet of my current implementation:
public void updatedButtons()
{
switch( m_state )
{
case QWERTY:
for( KeyboardButton button : m_keyboardButtons )
{
button.setText( button.getQwertyText().toLowerCase() );
}
break;
case QWERTY_SHIFT:
for( KeyboardButton button : m_keyboardButtons )
{
button.setText( button.getQwertyText().toUpperCase() );
}
break;
}
}
Where KeyboardButton is a simple extension of JButton with the qwertyText String field.
The issue here is that the buttons update in a chaotic manner. I understand why this is happening; when I call setText(), it's calling repaint() for the individual component, and this is happening for each button. My question is, am I able to "batch repaint" these buttons, without undermining Swing's design (I'd prefer not override AbstractButton's setText() method). Thanks.
UPDATE
Turns out it is an issue with FormLayout. Below is simple code that illustrates the problem (note that you will need a JGoodies Form Jar, and might need to modify the breakpoint value).
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import com.jgoodies.forms.layout.CellConstraints;
import com.jgoodies.forms.layout.FormLayout;
public class JButtonTest
{
public static List<JButton> buttons = new ArrayList<JButton>();
public static boolean isCaps = false;
public static JFrame frame;
public static void main(String[] args)
{
frame = new JFrame();
frame.setSize( 1000, 100 );
JPanel panel = new JPanel();
// Form with 11 83x83 pixel squares, with 5x83 pixel spaces.
panel.setLayout( new FormLayout("83px, 5px, 83px, 5px, 83px, 5px, 83px, 5px, "
+ "83px, 5px, 83px, 5px, 83px, 5px, 83px, 5px, 83px, 5px, 83px, 5px, 83px, 5px, 86px",
"83px"));
int i = 1;
for (char c = 'a'; c < 'l'; ++c)
{
JButton button = new ComplexButton( Character.toString(c) );
button.addActionListener( new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
updateButtons();
}
});
panel.add( button, new CellConstraints().xywh( (i*2)-1, 1, 1, 1, CellConstraints.FILL, CellConstraints.FILL) );
buttons.add( button );
++i;
}
frame.setContentPane( panel );
frame.setVisible( true );
}
// Enable the commented-out lines in this method to allow concurrent updating.
public static void updateButtons()
{
for (JButton button : buttons)
{
if (!isCaps)
button.setText( button.getText().toUpperCase() );
else
button.setText( button.getText().toLowerCase() );
//RepaintManager.currentManager( button ).markCompletelyClean( button );
}
//frame.repaint();
isCaps = !isCaps;
}
protected static class ComplexButton extends JButton
{
public ComplexButton( String string )
{
super(string);
}
@Override
public void paint( Graphics g )
{
int breakpoint = 3000000;
super.paint( g );
// Simulate some complex operations.
for (int i = 0; i < breakpoint; ++i)
{
g.setColor( new Color( i%255, (2*i)%255, (3*i)%255 ));
}
}
}
}
Note that, if you change from FormLayout to FlowLayout, it works perfectly fine (though sluggish in nature). It also works fine if you remove the comments on the commented-out code (thanks MadProgrammer).
Also note that, if you put printlns at the beginning and end of the updateButtons() method, the method will end long before the buttons stop updating, and the buttons will not update in unison. This means the coalesced nature of repaint is somehow not preserved with FormLayout.
Regardless, even if it was preserved, sluggish controls are just as bad as chaotically-updating controls. Guess I'll have to attempt to optimize our paint code. Thanks for the support.
Painting in Swing is controlled by the
RepaintManager
which decides when things should get updated. TheRepaintManager
is optimised to reduce the number paint events it schedules in order to maintain performance.This means that when it receives a bunch of requests for repaints, it will consolidate them down into as few paint events as it can. This means that when you call
setText
from within a loop on a bunch of buttons, it's likely that theRepaintManager
has reduced this down to as close to one as it can (depending on what you are updating there might be more, but will most likely be less than the number of iterations of the loop)...For example, it seems to work for me...