In an attempt to fix a problem with printing within the margins, I'm trying to scale my forms so that they'd shrink to the size of the paper it will be printed to.
Inside Printable.java that extends VBox
public void scaleToFit(){
double maxWidth = 497.0;
this.requestLayout();
double width = this.getWidth();
if(width > maxWidth){
double widthFrac = maxWidth / width;
this.setScaleX(widthFrac);
}
System.out.println(this.getWidth());
//edited out same process for height
}
Printable will be kept in a HashMap data.table. When my printing window is loaded I run scaleToFit for each of them. main is a ScrollPane.
Inside ModPrintCycle.java that extends VBox
//inside constructor
main.sceneProperty().addListener(new ChangeListener<Scene>(){
#Override
public void changed(ObservableValue<? extends Scene> observable, Scene oldValue, Scene newValue) {
System.out.println("new Scene");
newValue.windowProperty().addListener(new ChangeListener<Window>(){
public void changed(ObservableValue<? extends Window> arg0, Window arg1, Window arg2) {
System.out.println("new Window");
arg2.setOnShown(new EventHandler<WindowEvent>(){
#Override
public void handle(WindowEvent event) {
Platform.runLater(new Runnable(){
#Override
public void run() {
System.out.println(event.toString());
lookingAt = data.tables.size();
while(lookingAt-1 >= 0 ){
showNext(-1);
}
}
});
}
});
}
});
}
});
Just for this example, I also added scaleToFit() in the button that changes between these Printables. [EDIT: Added the scripts that explicitly show the use of scaleToFit()] Note that data.tables is a HashMap containing the Printables.
Inside ModPrintCycle.java continuation
private void showNext(int move){
boolean k = false;
if(move > 0 && lookingAt+move < data.tables.size()){
k = true;
}
else if(move < 0 && lookingAt+move >=0){
k = true;
}
if(k){
lookingAt+= move;
}
show();
}
private void show(){
if(data.tables.size() > 0){
if(lookingAt >= 0 && lookingAt < data.tables.size()){
//tableOrder is an ArrayList<String> for the arrangement of data.tables
if(tableOrder.size() > 0){
Printable pt = data.tables.get(tableOrder.get(lookingAt));
main.setContent(pt);
pt.scaleToFit();
}
}
else{
if(lookingAt < 0){
lookingAt = 0;
show();
}
else if(lookingAt >= data.tables.size()){
lookingAt = data.tables.size()-1;
show();
}
}
txtStatus.setText((lookingAt+1) + " / " + data.tables.size());
}else{
main.setContent(null);
txtStatus.setText("Empty");
}
}
public void printAll(ArrayList<String> pageList){
//PrinterJob pj is global
//conditions and try declarations
pj = PrinterJob.createPrinterJob(curP);
PageLayout pp = curP.createPageLayout(Paper.LEGAL, PageOrientation.PORTRAIT, MarginType.DEFAULT);
PageLayout pl = curP.createPageLayout(Paper.LEGAL, PageOrientation.LANDSCAPE, MarginType.DEFAULT);
for(String p : pageList){
Printable pt = data.tables.get(p);
pt.scaleToFit();
if(pt.isLandscape()){
pj.printPage(pl,pt);
}
else{
pj.printPage(pp,pt);
}
}
pj.endJob();
// catch statements
}
However, whenever scaleToFit() is called for the first time (when ModPrintCycle is loaded), it tells me that the widths are 0, thus will not scale yet. The second time it is called (when I change between them for the first time), it's still 0. When it finally runs a 3rd time (when I look back at the Printable), it finally works and changes the widths as needed.
Since these Printables need to be printed, I cannot ensure that the forms are scaled until someone looks through all of them twice.
How do I force the forms to take their bounds before having to load them?
Since the code you posted is not fully executable (i.e. not MCVE or SSCCE), the problem cannot be reproduced. It is also difficult to guess the cause. But I see your purpose, so I suggest instead of scaling it manually, let the printable scale itself automatically through listener:
#Override
public void start( Stage stage )
{
ScrollPane scrollPane = new ScrollPane( new Printable( new Label( "looong loooong loooooong looooong loooong text" ) ) );
stage.setScene( new Scene( scrollPane ) );
stage.show();
}
class Printable extends VBox
{
public Printable( Node... children )
{
super( children );
Printable me = this;
this.widthProperty().addListener( new ChangeListener<Number>()
{
#Override
public void changed( ObservableValue<? extends Number> observable, Number oldValue, Number newValue )
{
double maxWidth = 100.0;
double width = newValue.doubleValue();
if ( width > maxWidth )
{
double widthFrac = maxWidth / width;
me.setScaleX( widthFrac );
}
}
} );
}
}
Related
I am making a media player and am trying to get the playback slider value at the cursor position when hovering over the slider bar. In an attempt to do this, i have used the following:
timeSlider.addEventFilter(MouseEvent.MOUSE_MOVED, event -> System.out.println("hovering"));
which prints "hovering" whenever the mouse changes position over the slider. Can anyone please show me how to get the value of the slider at the current cursor position? I can only figure out how to get the value at the thumb position.
Thanks in advance.
Here is a bit (maybe more than a bit) of a hack that works if you are showing the axis under the slider. It relies on looking up the axis via its css class, converting the mouse coordinates to coordinates relative to the axis, and then using API from ValueAxis to convert to the value:
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Scene;
import javafx.scene.chart.NumberAxis;
import javafx.scene.control.Label;
import javafx.scene.control.Slider;
import javafx.scene.layout.StackPane;
import javafx.stage.Popup;
import javafx.stage.Stage;
public class TooltipOnSlider extends Application {
#Override
public void start(Stage primaryStage) {
Slider slider = new Slider(5, 25, 15);
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMajorTickUnit(5);
Label label = new Label();
Popup popup = new Popup();
popup.getContent().add(label);
double offset = 10 ;
slider.setOnMouseMoved(e -> {
NumberAxis axis = (NumberAxis) slider.lookup(".axis");
Point2D locationInAxis = axis.sceneToLocal(e.getSceneX(), e.getSceneY());
double mouseX = locationInAxis.getX() ;
double value = axis.getValueForDisplay(mouseX).doubleValue() ;
if (value >= slider.getMin() && value <= slider.getMax()) {
label.setText(String.format("Value: %.1f", value));
} else {
label.setText("Value: ---");
}
popup.setAnchorX(e.getScreenX());
popup.setAnchorY(e.getScreenY() + offset);
});
slider.setOnMouseEntered(e -> popup.show(slider, e.getScreenX(), e.getScreenY() + offset));
slider.setOnMouseExited(e -> popup.hide());
StackPane root = new StackPane(slider);
primaryStage.setScene(new Scene(root, 350, 80));
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
This is mostly a bug-track-down: James's answer is perfect - only hampered by 2 issues:
the axis has to be visible, that is at least one of ticks or labels must be showing (in practice not a big obstacle: if you want to get the values at mouseOver you'r most probably showing the ticks anyway)
A bug in SliderSkin which introduce a slight skew of axis value vs slider value.
To see the latter, here's a slight variation of James's code. To see the asynchronicity, move the mouse over the slider then click. We expect the value of the popup to be the same as the value of the slider (shown in the label at the bottom). With core SliderSkin, they differ slightly.
public class TooltipOnSlider extends Application {
private boolean useAxis;
#Override
public void start(Stage primaryStage) {
Slider slider = new Slider(5, 25, 15);
useAxis = true;
// force an axis to be used
slider.setShowTickMarks(true);
slider.setShowTickLabels(true);
slider.setMajorTickUnit(5);
// slider.setOrientation(Orientation.VERTICAL);
// hacking around the bugs in a custom skin
// slider.setSkin(new MySliderSkin(slider));
// slider.setSkin(new XSliderSkin(slider));
Label label = new Label();
Popup popup = new Popup();
popup.getContent().add(label);
double offset = 30 ;
slider.setOnMouseMoved(e -> {
NumberAxis axis = (NumberAxis) slider.lookup(".axis");
StackPane track = (StackPane) slider.lookup(".track");
StackPane thumb = (StackPane) slider.lookup(".thumb");
if (useAxis) {
// James: use axis to convert value/position
Point2D locationInAxis = axis.sceneToLocal(e.getSceneX(), e.getSceneY());
boolean isHorizontal = slider.getOrientation() == Orientation.HORIZONTAL;
double mouseX = isHorizontal ? locationInAxis.getX() : locationInAxis.getY() ;
double value = axis.getValueForDisplay(mouseX).doubleValue() ;
if (value >= slider.getMin() && value <= slider.getMax()) {
label.setText("" + value);
} else {
label.setText("Value: ---");
}
} else {
// this can't work because we don't know the internals of the track
Point2D locationInAxis = track.sceneToLocal(e.getSceneX(), e.getSceneY());
double mouseX = locationInAxis.getX();
double trackLength = track.getWidth();
double percent = mouseX / trackLength;
double value = slider.getMin() + ((slider.getMax() - slider.getMin()) * percent);
if (value >= slider.getMin() && value <= slider.getMax()) {
label.setText("" + value);
} else {
label.setText("Value: ---");
}
}
popup.setAnchorX(e.getScreenX());
popup.setAnchorY(e.getScreenY() + offset);
});
slider.setOnMouseEntered(e -> popup.show(slider, e.getScreenX(), e.getScreenY() + offset));
slider.setOnMouseExited(e -> popup.hide());
Label valueLabel = new Label("empty");
valueLabel.textProperty().bind(slider.valueProperty().asString());
BorderPane root = new BorderPane(slider);
root.setBottom(valueLabel);
primaryStage.setScene(new Scene(root, 350, 100));
primaryStage.show();
primaryStage.setTitle("useAxis: " + useAxis + " mySkin: " + slider.getSkin().getClass().getSimpleName());
}
public static void main(String[] args) {
launch(args);
}
#SuppressWarnings("unused")
private static final Logger LOG = Logger.getLogger(TooltipOnSlider.class
.getName());
}
Note that there's an open issue which reports a similar behavior (though not so easy to see)
Looking into the code of SliderSkin, the culprit seems to be an incorrect calculation of the relative value from a mouse event on the track:
track.setOnMousePressed(me -> {
...
double relPosition = (me.getX() / trackLength);
getBehavior().trackPress(me, relPosition);
...
});
where track is positioned in the slider as:
// layout track
track.resizeRelocate((int)(trackStart - trackRadius),
trackTop ,
(int)(trackLength + trackRadius + trackRadius),
trackHeight);
Note that the active width (aka: trackLenght) of the track is offset by trackRadius, thus calculating the relative distance with the raw mousePosition on the track gives a slight error.
Below is a crude custom skin that replaces the calc simply as a test if the little application behaves as expected. Looks terrible due the need to use reflection to access super's fields/methods but now has slider and axis value in synch.
The quick hack:
/**
* Trying to work around down to the slight offset.
*/
public static class MySliderSkin extends SliderSkin {
/**
* Hook for replacing the mouse pressed handler that's installed by super.
*/
protected void installListeners() {
StackPane track = (StackPane) getSkinnable().lookup(".track");
track.setOnMousePressed(me -> {
invokeSetField("trackClicked", true);
double trackLength = invokeGetField("trackLength");
double trackStart = invokeGetField("trackStart");
// convert coordinates into slider
MouseEvent e = me.copyFor(getSkinnable(), getSkinnable());
double mouseX = e.getX();
double position;
if (mouseX < trackStart) {
position = 0;
} else if (mouseX > trackStart + trackLength) {
position = 1;
} else {
position = (mouseX - trackStart) / trackLength;
}
getBehavior().trackPress(e, position);
invokeSetField("trackClicked", false);
});
}
private double invokeGetField(String name) {
Class clazz = SliderSkin.class;
Field field;
try {
field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.getDouble(this);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return 0.;
}
private void invokeSetField(String name, Object value) {
Class clazz = SliderSkin.class;
try {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
field.set(this, value);
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* Constructor - replaces listener on track.
* #param slider
*/
public MySliderSkin(Slider slider) {
super(slider);
installListeners();
}
}
A deeper fix might be to delegate all the dirty coordinate/value transformations to the axis - that's what it is designed to do. This requires the axis to be part of the scenegraph always and only toggle its visibilty with ticks/labels showing. A first experiment looks promising.
I currently have an application which will create a textarea wherever the user clicks. However, I want the pane to only be editable when a certain condition is true. The clickable area never goes away, though. How can I change this so the area is only clickable if myAnchorPane.isVisible() is true?
double oldHeight = 0;
double oldWidth = 0;
#FXML
private void handleTextButton() {
System.out.println("Text Clicked");
TextHeaderTools.setVisible(false);
BackgroundTools.setVisible(false);
VideoTools.setVisible(false);
PageTitleTools.setVisible(false);
TemplateTools.setVisible(false);
ImageTools.setVisible(false);
TextTools.setVisible(true);
workspace.addEventHandler(MouseEvent.MOUSE_CLICKED, new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
int x = (int) me.getX();
int y = (int) me.getY();
System.out.println("(" + x +", " + y +")");
InlineCssTextArea newArea = new InlineCssTextArea();
newArea.relocate(x, y);
newArea.setStyle("-fx-background-color: transparent;");
Text textHolder = new Text();
newArea.setPrefSize(40,40);
textHolder.textProperty().bind(newArea.textProperty());
textHolder.layoutBoundsProperty().addListener(new ChangeListener<Bounds>() {
#Override
public void changed(ObservableValue<? extends Bounds> observable, Bounds oldValue, Bounds newValue) {
if (oldHeight != newValue.getHeight()) {
oldHeight = newValue.getHeight();
newArea.setPrefHeight(textHolder.getLayoutBounds().getHeight() + 30);
}
if (oldWidth != newValue.getWidth()){
oldWidth = newValue.getWidth();
newArea.setPrefWidth(textHolder.getLayoutBounds().getWidth() + 30);
}
}
});
workspace.getChildren().addAll(newArea);
} //end handle
});
}
EDIT:
The condition is
myAnchorPane.isVisible()
You can achieve this nicely with ReactFX.
Instead of workspace.addEventHandler(MOUSE_CLICKED, ...), do this:
EventStreams.eventsOf(workspace, MOUSE_CLICKED)
.suppressWhen(myAnchorPane.visibleProperty().not())
.subscribe((MouseEvent me) -> {
// handle mouse click
});
I see you use RichTextFX, so you already have ReactFX as a dependency anyway.
I am putting together a slideshow program that will measure a user's time spent on each slide. The slideshow goes through several different magic tricks. Each trick is shown twice. Interim images are shown between the repetition. Transition images are shown between each trick.
On the first repetition of a trick the JPanel color flashes on the screen after a click before the next image is shown. This doesn't happen during the second repetition of the same trick. It's possible that the image is taking too long to load.
Is there an easy way to pre-load the images so that there isn't a delay the first time through?
NOTE: Original code deleted.
EDIT 1/10/2013: This code now works on slower machines. trashgod's second addendum helped the most. The mouseClick control structure periodically asks SwingWorker classes to load 40 images or less of the current trick while also setting the used images to null. I have simplified my code down for this to just two Image[]s and added a main method so it stands alone. Images are still required to run though. This is now pretty bare bones code, and if you're trying to make a slideshow with a lot of images I think it would be a good place to start.
NOTE: I think I figured out how to properly implement SwingWorker while still using multiple Image[]s. trashgod and kleopatra is this implementation in-line with what you were suggesting? I didn't end up using publish and process since I couldn't figure out how to get that to work appropriately with an indexed array, but because the StringWorker doesn't load all images in the array (only 40), and the code calls StringWorker every 20 images, there should be a pretty good buffer.
EDIT 1/10/2013 Changed out MouseListener by instead extending MouseAdapter on my Mouse class. Also fixed my paintComponent method to include a call to super.paintComponent(g).
Added publish/process methods to my SwingWorker class ImageWorker. Added a wrapper class, ArrayWrapper to allow passing imageArray[i] and its corresponding index int i with publish to process.
package slideshow3;
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseAdapter;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import java.util.List;
public class SlideShow3 extends JFrame
{
//screenImage will be replaced with each new slide
private Image screenImage;
private int width;
private int height;
//Create panel for displaying images using paintComponent()
private SlideShow3.PaintPanel mainImagePanel;
//Used for keybinding
private Action escapeAction;
//Image array variables for each trick
private Image[] handCuffs; //h
private Image[] cups; //c
//Used to step through the trick arrays one image at a time
private int h = 0;
private int c = 0;
//Used by timeStamp() for documenting time per slide
private long time0 = 0;
private long time1;
public SlideShow3()
{
super();
//Create instance of each Image array
handCuffs = new Image[50];
cups = new Image[176];
//start(handCuffsString);
start("handCuffs");
try
{
screenImage = ImageIO.read(new File("images/begin1.jpg"));
}
catch (IOException nm)
{
System.out.println("begin");
System.out.println(nm.getMessage());
System.exit(0);
}
/******************************************
* Removes window framing. The next line sets fullscreen mode.
* Once fullscreen is set width and height are determined for the window
******************************************/
this.setUndecorated(true);
GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().setFullScreenWindow(this);
width = this.getWidth();
height = this.getHeight();
//Mouse click binding to slide advance control structure
addMouseListener(new Mouse());
//Create panel so that I can use key binding which requires JComponent
mainImagePanel = new PaintPanel();
add(mainImagePanel);
/******************************************
* Key Binding
* ESC will exit the slideshow
******************************************/
// Key bound AbstractAction items
escapeAction = new EscapeAction();
// Gets the mainImagePanel InputMap and pairs the key to the action
mainImagePanel.getInputMap().put(KeyStroke.getKeyStroke("ESCAPE"), "doEscapeAction");
// This line pairs the AbstractAction enterAction to the action "doEnterAction"
mainImagePanel.getActionMap().put("doEscapeAction", escapeAction);
/******************************************
* End Key Binding
******************************************/
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run()
{
SlideShow3 show = new SlideShow3();
show.setVisible(true);
}
});
}
//This method executes a specific SwingWorker class to preload images
public void start(String e)
{
if(e.equals("handCuffs"))
{
new ImageWorker(handCuffs.length, h, e).execute();
}
else if(e.equals("cups"))
{
new ImageWorker(cups.length, c, e).execute();
}
}
//Stretches and displays images in fullscreen window
private class PaintPanel extends JPanel
{
#Override
public void paintComponent(Graphics g)
{
if(screenImage != null)
{
super.paintComponent(g);
g.drawImage(screenImage, 0, 0, width, height, this);
}
}
}
/******************************************
* The following SwingWorker class Pre-loads all necessary images.
******************************************/
private class ArrayWrapper
{
private int i;
private Image image;
public ArrayWrapper(Image image, int i)
{
this.i = i;
this.image = image;
}
public int getIndex()
{
return i;
}
public Image getImage()
{
return image;
}
}
private class ImageWorker extends SwingWorker<Image[], ArrayWrapper>
{
private int currentPosition;
private int arraySize;
private String trickName;
private Image[] imageArray;
public ImageWorker(int arraySize, int currentPosition, String trick)
{
super();
this.currentPosition = currentPosition;
this.arraySize = arraySize;
this.trickName = trick;
}
#Override
public Image[] doInBackground()
{
imageArray = new Image[arraySize];
for(int i = currentPosition; i < currentPosition+40 && i < arraySize; i++)
{
try
{
imageArray[i] = ImageIO.read(new File("images/" + trickName + (i+1) + ".jpg"));
ArrayWrapper wrapArray = new ArrayWrapper(imageArray[i], i);
publish(wrapArray);
}
catch (IOException e)
{
System.out.println(trickName);
System.out.println(e.getMessage());
System.exit(0);
}
}
return imageArray;
}
#Override
public void process(List<ArrayWrapper> chunks)
{
for(ArrayWrapper element: chunks)
{
if(trickName.equals("handCuffs"))
{
handCuffs[element.getIndex()] = element.getImage();
}
else if(trickName.equals("cups"))
{
cups[element.getIndex()] = element.getImage();
}
}
}
#Override
public void done()
{
try
{
if(trickName.equals("handCuffs"))
{
handCuffs = get();
}
else if(trickName.equals("cups"))
{
cups = get();
}
}
catch(InterruptedException ignore){}
catch(java.util.concurrent.ExecutionException e)
{
String why = null;
Throwable cause = e.getCause();
if(cause != null)
{
why = cause.getMessage();
}
else
{
why = e.getMessage();
}
System.err.println("Error retrieving file: " + why);
}
}
}
/******************************************
* End SwingWorker Pre-Loading Classes
******************************************/
//Prints out time spent on each slide
public void timeStamp()
{
time1 = System.currentTimeMillis();
if(time0 != 0)
{
System.out.println(time1 - time0);
}
time0 = System.currentTimeMillis();
}
/******************************************
* User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
private class EscapeAction extends AbstractAction
{
#Override
public void actionPerformed(ActionEvent e)
{
System.exit(0);
}
}
public class Mouse extends MouseAdapter
{
#Override
public void mouseClicked(MouseEvent e)
{
if(!(h<handCuffs.length) && !(c<cups.length))
{
timeStamp();
System.exit(0);
}
else if(h<handCuffs.length)
{
timeStamp();
screenImage = handCuffs[h];
repaint();
System.out.print("handCuffs[" + (h+1) + "]\t");
h++;
//purge used slides and refresh slide buffer
if(h == 20 || h == 40)
{
for(int i = 0; i < h; i++)
{
handCuffs[i] = null;
}
start("handCuffs");
}
if(h == 45)
{
start("cups");
}
}
else if(c<cups.length)
{
timeStamp();
screenImage = cups[c];
repaint();
System.out.print("cups[" + (c+1) + "]\t");
c++;
//purge used slides and refresh slide buffer
if(c == 20 || c == 40 || c == 60 || c == 80 || c == 100 || c == 120 || c == 140 || c == 160)
{
for(int i = 0; i < c; i++)
{
cups[i] = null;
}
start("cups");
}
}
}
}
/******************************************
* End User Input Classes for Key Binding Actions and Mouse Click Actions
******************************************/
}
This example uses a List<ImageIcon> as a cache of images returned by getImage(). Using getResource(), the delay is imperceptible. The next and previous buttons are bound to the Space key by default.
Addendum: You can control navigation by conditioning a button's setEnabled() state using an instance of javax.swing.Timer, for example.
Addendum: Your second example waits until the mouse is clicked to begin reading an image, an indeterminate process that may return a copy immediately or may not complete until after repaint(). Instead, begin reading the images in the background using ImageIO.read(), as shown here. You can process() your List<Image> and show progress, as seen here. The SwingWorker can be launched from the initial thread, running while you subsequently build your GUI on the EDT. You can display the first image as soon as it is processed.
Ok, I have the following code.
public class MyProgressBar extends JPanel implements MyData, Serializable {
/**
*
*/
public static final int MAX = 10000;
public static final int WIDTH = 400;
public static final int HEIGHT = 75;
private JProgressBar MyBar = new JProgressBar( SwingConstants.HORIZONTAL, 0, MAX );
private JFrame MyFrame = new JFrame();
private int MyValue = 0;
private Thread MyThread = new Thread( new ProgressThread() );
public MyProgressBar() {
add(MyBar);
int x = ( MyData.SCREEN.width / 2 ) - ( WIDTH / 2);
int y = ( MyData.SCREEN.height / 2 ) - ( HEIGHT / 2);
this.setBounds( x, y, WIDTH, HEIGHT );
MyFrame.setBounds( x, y, WIDTH, HEIGHT );
MyFrame.setUndecorated(true);
MyFrame.getContentPane().setSize( new Dimension( WIDTH, HEIGHT ) );
MyFrame.setMinimumSize( new Dimension( WIDTH, HEIGHT ) );
MyFrame.setPreferredSize( new Dimension( WIDTH, HEIGHT ) );
MyFrame.setSize( new Dimension( WIDTH, HEIGHT ) );
MyFrame.setVisible(false);
MyFrame.getContentPane().setLayout(null);
MyBar.setStringPainted( true );
MyBar.setBorderPainted( true );
MyBar.setValue( 0 );
MyBar.setBounds( 0, 0, WIDTH, HEIGHT );
MyFrame.add( MyBar );
MyFrame.pack();
MyFrame.repaint();
}
public void MyUpdateBar() {
MyBar.setValue( MyValue );
MyBar.repaint();
MyFrame.repaint();
this.repaint();
//dbug.Message( "MYPROGRESSBAR", "MyUpdateBar", "Value is %3.2f %d", MyBar.getPercentComplete(), MyValue );
}
public void MySetValue( int percent ) {
MyValue = (int)( MAX * ( (double)percent / 100.0 ) );
MyUpdateBar();
//dbug.Message( "MYPROGRESSBAR", "MySetValue", "Value is %3.2f %d percent was %d", MyBar.getPercentComplete(), MyValue, percent );
}
public void CreateAndShow () {
MyFrame.setVisible(true);
MyThread.start();
}
public void HideAndClear () {
MyThread.stop();
//frame.setVisible(false);
}
class ProgressThread implements Runnable {
public void run() {
EventQueue.invokeLater(new Runnable() {
public void run() {
while( MyValue < MyBar.getMaximum() ) {
MyBar.setValue( MyValue );
MyBar.repaint();
MyFrame.repaint();
dbug.Message( "MYPROGRESSBAR", "THREAD", "Value is %3.2f %d", MyBar.getPercentComplete(), MyValue );
}
}
});
}
}
}
As you can see, I have created a class that I want to have show the progress. What happens is I instantiate the class. Load my XML file, then as I am parsing data, I am calling to update the MyValue which I see when I let my dbug messages come out. However, the bar itself does not even show until it is 100% complete. I have read about threading and following someone else's example and if I left it as his example it worked. If I made a few tweaks (changing a loop in the thread to populate the setvalue of the progress bar to read a value) it does not even show until it is 100.
What did I do wrong?
Thanks!
You thread executes SwingUtilities.invokeLater. You're effectively running on Swing's Event Dispatch Thread. Not sure what are you trying to achieve. But it looks like you are blocking EDT and your while loop is not updated as MySetValue is not executed.
Consider using SwingWorker for lengthy operations. How to Use Progress Bars demonstrates use of SwingWorker with JProgressBar.
Make sure you call setValue method from the Event Dispatch Thread. You can use SwingUtilities.invokeLater for that. Read more about Threads and Swing.
Consider this simplified sample:
public static void main(String[] arguments) {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(EXIT_ON_CLOSE);
final JProgressBar bar = new JProgressBar(0, 100);
Thread t = new Thread(){
public void run(){
for(int i = 0 ; i < 100 ; i++){
final int percent = i;
SwingUtilities.invokeLater(new Runnable() {
public void run() {
bar.setValue(percent);
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
};
frame.add(bar);
frame.pack();
frame.setVisible(true);
t.start();
}
The problem is that you use a loop in the EDT that updates the progress. Until that loop exits, the EDT cannot dispatch events (like repaint, revalidate, invokeLater, mouse events, key events, etc...) preventing it from refreshing the progress bar.
You should try to find a way to let the EDT dispatch its events between each update of the progress bar. Ideally, you move your "work" outside the EDT with a SwingWorker, and meanwhile the progressbar get updated through property change listeners in the EDT.
For your information, in Java, methods and variables starts with a lower case letter. Your code is really hard to read for others.
So, I tried to follow the tutorial and here is where I am at.
Ok, I have tried following tutorials but I keep getting lost somewhere. What I need is a class that creates and displays a progress bar (JProgressBar) that I can set the value of as I iterate over data loaded from a file and place into the database memory. My problems come that every example I have found has some kind of counter that fills the progress bar and executes from a "main" function. Every time I alter that tutorial to be a class that I can call at will and display the bar, I do not get the bar showing (ie the frame comes up but the bar does not even look like it is added to the frame until after the iteration is done). I have tried using SwingUtilities.invokeLater and SwingWorker (lastest attempt at class below) all having the same issue. To make matters worse, I can do a dbug.myMessage (basically sends to System.out) and see a message that shows that the bar is changing in memory just not showing. I am obviously missing something probably simple but I can't think of what it is.
Oh, one other thing, if I leave the tutorial as is (http://docs.oracle.com/javase/tutorial/displayCode.html?code=http://docs.oracle.com/javase/tutorial/uiswing/examples/components/ProgressBarDemo2Project/src/components/ProgressBarDemo2.java) and just change the main to a createAndShow method, it works but of course it does not do what I need it to do.
I did post another question about this but have altered the class so much I thought it best to post a new question.
So, here is my altered code that does not seem to work:
public class MyProgressBar extends JPanel implements PropertyChangeListener,
MyData,
Serializable {
/**
*
*/
private static final long serialVersionUID = -1632492668549544408L;
private MyDebug dbug = new MyDebug( MyData.MYDEBUGCHECK.MYPROGRESSBAR.getOn() );
public static final int MAX = 100;
public static final int WIDTH = 400;
public static final int HEIGHT = 75;
private JProgressBar myBar = new JProgressBar( SwingConstants.HORIZONTAL, 0, MAX );
private JFrame myFrame = new JFrame();
public Task task;
class Task extends SwingWorker<Void, Void> {
public int myValue = 0;
#Override
public Void doInBackground() {
//Initialize progress property.
setProgress(0);
while (myValue < 100) {
//Make random progress.
//myValue += random.nextInt(10);
setProgress( Math.min( myValue, 100 ) );
dbug.myMessage( "MYPROGRESSBAR", "doInBackground", "Value is %3.2f %d", myBar.getPercentComplete(), myValue );
myBar.repaint();
}
return null;
}
public void done() {
}
public void mySetValue( int percent ) {
myValue = (int)( MAX * ( (double)percent / 100.0 ) );
dbug.myMessage( "MYPROGRESSBAR", "mySetValue", "Value is %3.2f %d percent was %d", myBar.getPercentComplete(), myValue, percent );
}
}
public MyProgressBar() {
add(myBar);
int x = ( MyData.SCREEN.width / 2 ) - ( WIDTH / 2);
int y = ( MyData.SCREEN.height / 2 ) - ( HEIGHT / 2);
this.setBounds( x, y, WIDTH, HEIGHT );
myFrame.setBounds( x, y, WIDTH, HEIGHT );
myFrame.setUndecorated(true);
myFrame.getContentPane().setSize( new Dimension( WIDTH, HEIGHT ) );
myFrame.setMinimumSize( new Dimension( WIDTH, HEIGHT ) );
myFrame.setPreferredSize( new Dimension( WIDTH, HEIGHT ) );
myFrame.setSize( new Dimension( WIDTH, HEIGHT ) );
myFrame.setVisible(false);
myFrame.getContentPane().setLayout(null);
myFrame.setDefaultCloseOperation(JFrame.HIDE_ON_CLOSE);
myBar.setStringPainted( true );
myBar.setBorderPainted( true );
myBar.setValue( 0 );
myBar.setBounds( 0, 0, WIDTH, HEIGHT );
myBar.addPropertyChangeListener( this );
myFrame.add( myBar );
//Create and set up the content pane.
//JComponent newContentPane = new MyProgressBar();
JComponent newContentPane = myBar;
newContentPane.setOpaque(true); //content panes must be opaque
myFrame.setContentPane(newContentPane);
myFrame.pack();
}
public void createAndShow () {
//Display the window.
myFrame.setVisible(true);
myFrame.repaint();
}
public void hideAndClear () {
//myFrame.setVisible(false);
}
#Override
public void propertyChange(PropertyChangeEvent args) {
dbug.myMessage( "MYPROGRESSBAR", "propertyChange", "Value is %s", args.getPropertyName() );
if ( "progress" == args.getPropertyName() ) {
int progress = (Integer) args.getNewValue();
//myBar.setValue(progress);
}
}
public void start () {
//Instances of javax.swing.SwingWorker are not reusuable, so
//we create new instances as needed.
task = new Task();
task.addPropertyChangeListener(this);
task.execute();
}
}
The below snippet updates the progress bar while in progress
SwingUtilities.invokeLater(new Runnable() {
public void run() {
progressBar.setValue((int)percentage);
//below code to update progress bar while running on thread
progressBar.update(progressBar.getGraphics());}
});
Dynamic progress update of the progress bar is achieved via the below code:
int progress = Math.round(((float)finished/(float)(total)) * 100);
uploadPrgressBar.setIndeterminate(progress == 0);
uploadPrgressBar.setValue(progress);
uploadPrgressBar.update(uploadPrgressBar.getGraphics());
Call the method containing the above code in the loop(code logic) and it will dynamically update progress bar after each iteration
I have a problem with the code I am currently trying to run - I am trying to make 3 buttons, put them on a GUI, and then have the first buttons colour be changed to orange, and the buttons next to that colour change to white and green. Every click thereafter will result in the colours moving one button to the right. My code thus far is as follows, it is skipping colours in places and is not behaving at all as I expected. Can anyone offer some help/guidance please ?
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class ButtonJava extends JButton implements ActionListener {
private int currentColor=-1;
private int clicks=0;
private static final Color[] COLORS = {
Color.ORANGE,
Color.WHITE,
Color.GREEN };
private static ButtonJava[] buttons;
public ButtonJava( ){
setBackground( Color.YELLOW );
setText( "Pick ME" );
this.addActionListener( this );
}
public static void main(String[] args) {
JFrame frame = new JFrame ("JFrame");
JPanel panel = new JPanel( );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE);
buttons = new ButtonJava[3];
for(int i = 0;i<buttons.length ; i++){
buttons[i] = new ButtonJava();
panel.add(buttons[i]);
}
frame.getContentPane( ).add( panel );
frame.setSize( 500, 500);
frame.setVisible( true );
}
private void updateButton() {
clicks++;
changeColors();
// setText( );
}
private void changeColors( ) {
for (int i=buttons.length-1;i>=0;i--){
buttons[i].currentColor = nextColor(currentColor);
buttons[i].setBackground(COLORS[buttons[i].currentColor]);
buttons[i].setText(("# of clicks = " + buttons[i].getClicks() ) );
}
}
private Integer getClicks() {
return clicks;
}
private int nextColor( int curCol ) {
final int colLen = COLORS.length;
curCol--;
curCol = (colLen + curCol % colLen) % colLen;
return curCol;
}
private void firstClick( ActionEvent event ) {
int curCol = 0;
for (int i=buttons.length-1;i>=0;i--){
if ( buttons[i] == event.getSource() ) {
buttons[i].currentColor = curCol;
curCol++;
currentColor++;
}
}}
#Override
public void actionPerformed( ActionEvent event ) {
if ( -1 == currentColor ) {
firstClick( event );
}
updateButton( );
}
}
Thank you very much for the help :)
You have a couple issues with the code you posted, but they generally boil down to being clear about what is a member of the class(static) and what is a member of the instance.
For starters, you buttons array only exists inside your main method and can't be accessed by changeColors(). Along those same lines, since changeColors() is an instance method, setBackground() needs to be called directly on the button in your array. as written you are setting the color for one button 3 times.
Additionally, the logic in changeColors() is not properly rotating the currentColor index. You need to both increase the counter and ensure is wraps for the length of the color array. If the arrays are the same size, you need to make sure there is an extra addition to make the colors cycle.
private static void changeColors( ) {
for (int i=0;i<buttons.length;i++){
buttons[i].setBackground(COLORS[currentColor]);
currentColor = nextColor(currentColor);
}
if (buttons.length == COLORS.length) {
currentColor = nextColor(currentColor);
}
}
private static int nextColor(int currentColor) {
return (currentColor+1)% COLORS.length;
}
Edit for new code:
I'm not sure why you re-wrote nextColor(), as what I posted worked. But in general, I feel like you are running into issues because your code is not well partitioned for the tasks you are trying to achieve. You have code related to the specific button instance and code related to controlling all the buttons mixing together.
With the following implementation, the issue of how many times a button was clicked is clearly self-contained in the button class. Then every button press also calls the one method in the owning panel. This method knows how many buttons there are and the color of the first button. And each subsequent button will contain the next color in the list, wrapping when necessary.
public class RotateButtons extends JPanel {
private static final Color[] COLORS = { Color.ORANGE, Color.WHITE, Color.GREEN };
private static final int BUTTON_COUNT = 3;
private JButton[] _buttons;
private int _currentColor = 0;
public static void main(String[] args)
{
JFrame frame = new JFrame("JFrame");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new RotateButtons());
frame.setSize(500, 500);
frame.setVisible(true);
}
public RotateButtons()
{
_buttons = new JButton[BUTTON_COUNT];
for (int i = 0; i < _buttons.length; i++) {
_buttons[i] = new CountButton();
add(_buttons[i]);
}
}
private void rotateButtons()
{
for (JButton button : _buttons) {
button.setBackground(COLORS[_currentColor]);
_currentColor = nextColor(_currentColor);
}
if (_buttons.length == COLORS.length) {
_currentColor = nextColor(_currentColor);
}
}
private int nextColor(int currentColor)
{
return (currentColor + 1) % COLORS.length;
}
private class CountButton extends JButton {
private int _count = 0;
public CountButton()
{
setBackground(Color.YELLOW);
setText("Pick ME");
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0)
{
_count++;
setText("# of clicks = " + _count);
rotateButtons();
}
});
}
}
}
2nd Edit:
Shows just the changes to shift _currentColor by the necessary amount on the first click.
public class RotateButtons extends JPanel {
...
private boolean _firstClick = true;
...
private void rotateButtons(CountButton source)
{
if (_firstClick) {
_firstClick = false;
boolean foundSource = false;
for (int i = 0; i < _buttons.length; i++) {
if (foundSource) {
_currentColor = nextColor(_currentColor);
} else {
foundSource = _buttons[i] == source;
}
}
}
...
}
private class CountButton extends JButton {
...
public CountButton()
{
...
addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0)
{
...
rotateButtons(CountButton.this);
}
});
}
}
One thing that I noticed is you are using currentColor to assign the color, but the currentColor is initialized to 0 and the only manipulation is currentColor %= 2 which does not change it.
If I'm understanding what you are wanting to achieve, I'm thinking to change currentColor %= 2 to ++currentColor, and setBackground(COLORS[currentColor]); to buttons[i].setBackground(COLORS[(i + currentColor) % 3]);. That way, your colours should cycle around your buttons each time one is clicked.
EDIT: it's probably also worth calling changeColors from within main to initialise your button colours. And, as #unholysampler notes, your array buttons is local to main and should (for example) be refactored as a static member variable, and have changeColors become a static method.