Java JSlider not showing up after JFileChooser is used - java

When I use JFileChooser then try to add other components, they don't show up. If I remove JFileChooser they do show up. I'm writing in java on eclipse, and there are two files.
I have removed a majority of my code to simplify the problem, but it still exists.
Main.java:
import java.awt.Color;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import javax.swing.JFrame;
public class Main {
public static void main(String args[]) throws InterruptedException, IOException {
int width = 1280;
int height = 720;
Frame f = new Frame(Color.BLACK, width, height);
JFrame frame = new JFrame("Title"); //create a new window and set title on window
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); //set the window to close when the cross in the corner is pressed
frame.setSize(width,height);
frame.add(f); //add the content of the game object to the window
frame.setVisible(true);
long interval = (long)10 * 10000000;
long t = 0;
while(true) {
if(System.nanoTime() - t >= interval) { //repaints at a certain fps
t = System.nanoTime();
f.repaint();
}
TimeUnit.NANOSECONDS.sleep(10);
}
}
}
Frame.java:
import java.awt.Color;
import java.awt.Graphics;
import java.io.IOException;
import javax.swing.JSlider;
import javax.swing.JButton;
import javax.swing.JFileChooser;
import javax.swing.JPanel;
public class Frame extends JPanel {
int menuNum = 0;
boolean first = true;
JButton nextButton = new JButton("Next");
JSlider slider = new JSlider(0,255,0);
JFileChooser fileChooser = new JFileChooser();
public Frame(Color background, int w, int h) throws IOException { //initialize
this.setBackground(background);
setFocusable(true);
}
public void paintComponent(Graphics G) {
super.paintComponent(G);
G.setColor(Color.WHITE);
G.drawString("MenuNum: " + menuNum, 1000, 500); //for debugging
if(menuNum == 0) { //first menu
if(first) { //only run once
first = false;
this.removeAll();
this.add(nextButton);
System.out.println("HERE");
}
if(fileChooser.showOpenDialog(null) == JFileChooser.APPROVE_OPTION) { //if "Done" is selected
menuNum = 1; //go to next menu
first = true;
}
}
if(menuNum == 1) { //second menu
if(first) { //only run once
first = false;
this.removeAll();
this.add(nextButton);
this.add(slider); //<This is the slider that is not showing up
System.out.println("HERE2");
}
}
}
}
If you are running this on your own machine, you can select any file to test it, since it does nothing with the selected file.
I am somewhat new to JPanels and JFrames so any advice will be well appreciated.
Thanks.

First of all there is absolutely no reason to do any custom painting. You should never try to add/remove components from a JPanel in a painting method.
The components should be added to the panel in the constructor of your class. So this means the button should be added to the panel.
Then you add an ActionListener to the button. When the button is clicked you do some processing.
If you want to alter the components on the panel in the ActionListener then the basic logic is:
panel.remove(...);
panel.add(...);
panel.revalidate();
panel.repaint();
So you need the revalidate() to invoke the layout manager. Otherwise the size of the added component is (0, 0), which means there is nothing to paint.
Learn Swing basics by reading the Swing Tutorial. Maybe start with section on:
How to Write an ActionListener
How to Use Sliders
How to Use CardLayout (instead of adding/removing components).

Just follow same idea, you will get
public MyControlPanel() {
initComponents();
JSlider slider = new JSlider();
slider.setMajorTickSpacing(10);
slider.setPaintLabels(true);
slider.setPaintTicks(true);
JTextField boundary_length = new JTextField("Boundary Length");
JTextField area = new JTextField("Area");
setLayout(new FlowLayout());
this.add(slider);
this.add(area);
this.add(boundary_length);
}

I had a similar problem and I found the solution with the updateUI() method. Look below:
private void refresh()
{
if(slider != null)
{
slider.updateUI();
}
}
So, when your JFilechooser closing you must call the refresh() thus:
if(fileChooser.showOpenDialog(null) == 0 // this is the value for JFileChooser.APPROVE_OPTION)
{ //if "Done" is selected
menuNum = 1; //go to next menu
first = true;
}
else {
refresh();
}
I hope this should works.

Related

Java - Close Window on Event

people of the internet.
I want to have a sort of start screen for a game ive been writing. Thus far it features 4 Buttons for each one of the 4 Players that change color on click from red to green and vice versa representing their individual "ready"-status if that makes sense. I used JFrame and JButtons.
Now i want that window to close if every one of those Buttons is currently set to "ready" aka button.getBackground() == Color.GREEN.
Any suggestions as to which EventListeners to use for this/implementation tips/code snippets would be greatly appreciated since my research on Windowclosing on Event didnt bring up much for me.
Thank you in advance and Greetings.
Since you're awaiting and acting on button presses, the most logical listener would be an ActionListener.
Consider making the buttons JToggleButtons, and then in your listener querying each button to see if it is selected (isSelected()) and if so, launch your program. As a side bit, I'd consider making the intro window a JDialog and not a JFrame, either that or making it a JPanel and swapping it out via a CardLayout when necessary.
For example:
import java.awt.Color;
import java.awt.Dialog.ModalityType;
import java.awt.Dimension;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
public class AreWeReady extends JPanel {
List<AbstractButton> buttons = new ArrayList<>();
private int userCount;
public AreWeReady(int userCount) {
this.userCount = userCount;
ButtonListener buttonListener = new ButtonListener();
for (int i = 0; i < userCount; i++) {
JButton btn = new JButton("User " + (i + 1));
buttons.add(btn);
btn.addActionListener(buttonListener);
add(btn);
}
}
private class ButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
AbstractButton btn = (AbstractButton) e.getSource();
Color c = Color.GREEN.equals(btn.getBackground()) ? null : Color.GREEN;
btn.setBackground(c);
for (AbstractButton button : buttons) {
if (!Color.GREEN.equals(button.getBackground())) {
// if any button does not have a green background
return; // leave this method
}
}
// otherwise if all are green, we're here
Window win = SwingUtilities.getWindowAncestor(btn);
win.dispose();
// else launch your gui
}
}
private static void createAndShowGui() {
int userCount = 4;
AreWeReady areWeReadyPanel = new AreWeReady(userCount);
JFrame frame = new JFrame("Main Application");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(Box.createRigidArea(new Dimension(400, 300)));
frame.pack();
frame.setLocationByPlatform(true);
JDialog dialog = new JDialog(frame, "Are We Ready?", ModalityType.APPLICATION_MODAL);
dialog.add(areWeReadyPanel);
dialog.pack();
dialog.setLocationByPlatform(true);
dialog.setVisible(true);
// this is only reached when the modal dialog above is no longer visible
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}

How can I switch between jpanels?

I'm still very new to java programming, so please help me to correct any mistakes I might have overlooked or give tips on how to improve this program.
Okay, so a lot of problems have been solved, and now I have a CardLayout, but I still have questions about how I should make my pipes show inside it.
When I tried to add in my refresh rate timer and my speed timer, I have problems about how I need to declare and initialize boolean variables.
Also, when I compile and run this game, I get files such as Game$1.class. Is there a way for me to clean this up, and could someone explain why this happens? Do these have an affect on the finished product? (When the game is compiled and packaged into a JAR.)
I want to set playerIsReady to true when the play button is clicked. And from there, when the if statement is true, then switch to a panel that displays the pipes, and start moving the pipe across the screen. Preferably 3 instances of that pipe, each starting at different times, but whatever you can help with is fine.
Some of this code needs work, so I have commented some parts out and left notes.
My other questions about this game can be found here.
This is my current code
Game.java
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.border.EmptyBorder;
import javax.swing.SwingUtilities;
public class Game {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
final JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
/*if (playerIsReady) {
Timer speed = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
pipes.move();
}
});
speed.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
pipes.repaint();
}
});
refresh.start();
}*/
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
Pipes.java
// What import(s) do I need for ArrayList?
public class Pipes {
List<Pipe> pipes = new ArrayList<Pipe>();
public Pipes() {
pipes.add(new Pipe(50, 100));
pipes.add(new Pipe(150, 100));
pipes.add(new Pipe(250, 100));
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for ( Pipe pipe : pipes ){
pipe.drawPipe(g);
}
}
}
PipeObject.java
import java.awt.Graphics;
public class PipeObject {
//Declare and initialiaze variables
int x1 = 754; //xVal start
int x2 = 75; //pipe width
//total width is 83
int y1 = -1; //yVal start
int y2 = setHeightVal(); //pipe height
int gap = 130; //gap height
public void drawPipe(Graphics g) {
g.clearRect(0,0,750,500); //Clear screen
g.drawRect(x1,y1,x2,y2); //Draw part 1
g.drawRect(x1-3,y2-1,x2+6,25); //Draw part 2
g.drawRect(x1-3,y2+25+gap,x2+6,25); //Draw part 3
g.drawRect(x1,y2+25+gap+25,x2,500-y2-49-gap); //Draw part 4
}
public void move() {
x1--;
}
public int getMyX() { //To determine where the pipe is horizontally
return x1-3;
}
public int getMyY() { //To determine where the pipe is vertically
return y2+25;
}
public int setHeightVal() { //Get a random number and select a preset height
int num = (int)(9*Math.random() + 1);
int val = 0;
if (num == 9)
{
val = 295;
}
else if (num == 8)
{
val = 246;
}
else if (num == 7)
{
val = 216;
}
else if (num == 6)
{
val = 185;
}
else if (num == 5)
{
val = 156;
}
else if (num == 4)
{
val = 125;
}
else if (num == 3)
{
val = 96;
}
else if (num == 2)
{
val = 66;
}
else
{
val = 25;
}
return val;
}
}
The best way to approach this is using a CardLayout.
Notes
A button with an ActionListener is far better than a MouseListener over a rectangle.
The button will show focus when the mouse is pointed at it, or the component is tabbed to via the keyboard.
The button is keyboard accessible.
The button has facility to support multiple icons built in (e.g. for 'initial look', focused, pressed etc.)
White space in the GUI is provided around the menu panel and game by adding an EmptyBorder
The button is made larger by setting a margin.
Adjust margins, borders and preferred size according to need. These sizes were set by me so as not to make the screenshots too large.
See more tips in the code comments.
Code
Here is the MCTaRE (Minimal Complete Tested and Readable Example) that produced the above screenshots.
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import javax.swing.border.EmptyBorder;
public class PipesGame {
public static void main(String[] args) {
Runnable r = new Runnable() {
#Override
public void run() {
// the GUI as seen by the user (without frame)
final CardLayout cl = new CardLayout();
final JPanel gui = new JPanel(cl);
// remove if no border is needed
gui.setBorder(new EmptyBorder(10,10,10,10));
JPanel menu = new JPanel(new GridBagLayout());
JButton playGame = new JButton("Play!");
ActionListener playGameListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
cl.show(gui, "game");
}
};
playGame.addActionListener(playGameListener);
Insets margin = new Insets(20, 50, 20, 50);
playGame.setMargin(margin);
menu.add(playGame);
gui.add(menu);
cl.addLayoutComponent(menu, "menu");
JPanel pipes = new Pipes();
gui.add(pipes);
cl.addLayoutComponent(pipes, "game");
JFrame f = new JFrame("Pipes Game");
f.add(gui);
// Ensures JVM closes after frame(s) closed and
// all non-daemon threads are finished
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
// See https://stackoverflow.com/a/7143398/418556 for demo.
f.setLocationByPlatform(true);
// ensures the frame is the minimum size it needs to be
// in order display the components within it
f.pack();
// should be done last, to avoid flickering, moving,
// resizing artifacts.
f.setVisible(true);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency
SwingUtilities.invokeLater(r);
}
}
class Pipes extends JPanel {
Pipes() {
setBackground(Color.BLACK);
setForeground(Color.WHITE);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("Pipes game appears here..", 170, 80);
}
#Override
public Dimension getPreferredSize() {
// adjust to need
return new Dimension(500,150);
}
}
"Is there a way for me to add my GameMenu jpanel to my jframe, and then replace it with the Pipes jpanel?"
As other have suggested, for this you want a CardLayout. It is very simple to you. Personally, I always wrap my CardLayout in a JPanel rather than the JFrame, just force of habit.
What you want to do is have a mainPanel that will have the CardLayout
CardLayout card = new CardLayout();
JPanel mainPanel = new JPanel();
Then you want to add your panels to the mainPanel. What the CardLyaout does is layer the panels, making just one visible at a time. The first one you add, will the in the foreground. Also when you add the panel, you'll also want to issue it a key it can be called from. The key, can be any String you like.
mainPanel.add(gameMenu, "menu");
mainPnael.add(pipes, "pipe");
Now gameMenu is the only panel shown. To show pipes, all you do is use this method
public void show(Container parent, String name) - Flips to the parent that was added to this layout with the specified name, using addLayoutComponent. If no such component exists, then nothing happens.
So you'd use, card.show(mainPanel, "pipes");
Whatever even you want to trigger the showing of pipes, just add that line in that event handler. You could add a button or something to the GameMenu that will allow movement to the Pipes panel.
This works with a mouse click on the menu. You can change it later, to a click on some button or whatever you want.
I added a MouseListener to the Game class. When the user presses the mouse on the menu JPanel, it adds the Pipes JPanel to JFrame and calls the pack method.
Game.java:
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Game {
GameMenu menu = new GameMenu();
Pipes game;
boolean start = false;
JFrame f;
Rectangle2D menuRect = new Rectangle2D.Double(20, 20, 60, 40);
public Game() {
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(menu);
f.setTitle("Pipe Game");
f.setResizable(false);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
menu.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
Point click = new Point(e.getX(), e.getY());
System.out.println("Clicked on the Panel");
if(menuRect.contains(click))
{
System.out.println("Clicked inside the Rectangle.");
start = true;
menu.setVisible(false);
game = new Pipes();
f.add(game);
f.pack();
Timer timer = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
game.move();
}
});
timer.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
game.repaint();
}
});
refresh.start();
}
}
#Override
public void mouseReleased(MouseEvent e) {
}
});
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game();
}
});
}
}

label.setVisible(true) in panel for images if correct

for(i=0; i<16; i++){
images[i]=new ImageIcon(getClass()
.getResource("/images/1 ("+i+").jpg"));
}
for( i=0; i<buttons.length; i++){
for (j=0; j<buttons[i].length;j++){
n=i*buttons.length+buttons[i].length;
buttons[i][j]=new JButton();
label[n].setIcon((images[i*buttons.length+j]));
buttons[i][j].add(label[n]);
label[n].setVisible(false);
panel.add(buttons[i][j]);
buttons[i][j].addActionListener(this);
}
}
public void actionPerformed(ActionEvent e) {
if(e.getSource() instanceof JButton){
JButton pressedButton = (JButton) e.getSource();
opens[open]=(JButton) e.getSource(); //i want to put label[n] into array?
if((pressedButton.getIcon() == null)){
label[n].setVisible(true);
open=open++;
} else {
//pressedButton.setIcon(null);
}
}
if (open==1){
opens[0].setVisible(false);
opens[1].setVisible(false);
}
}
hello friends.i am making a memory game, if buttons have same icon, they will stay open.
i made frame in it panel, in it buttons and each button has label. If facing is true and user click, they will open by setvisible(true)
but in void actionperformed, how can i take label[n]? Not button[][]?
label[n].setIcon((images[i*buttons.length+j]));
i think error is that.is not it correct? because it doesnot execute.
edit after suggestions:
for(i=0; i<16; i++){
images[i]=new ImageIcon(getClass().getResource("/images/1 ("+i+").jpg"));
} //adding images to local variables
for( i=0; i<buttons.length; i++){
for (j=0; j<buttons[i].length;j++){
n=i*buttons.length+buttons[i].length;
buttons[i][j]=new JButton();
//buttons[i][j].setIcon((images[i*buttons.length+j]));
//if i make this code, all icons are displayed at first?
panel.add(buttons[i][j]);
buttons[i][j].addActionListener(this);
}
}
public void actionPerformed(ActionEvent e) {
if(e.getSource() instanceof JButton){
JButton pressedButton = (JButton) e.getSource();
if(pressedButton.getIcon() == null){
pressedButton.setIcon((images[i*buttons.length+j]));
} else {
pressedButton.setIcon(null);
}
}
}
I made this code how you suggested. but now images dont display and clicks doesnot work.
I think this post is related to this question. Maybe an assignment?
Couple of tips:
label[n].setIcon((images[i*buttons.length+j]));
How many images do you have in images array? A quick math would say that if you have for instance a 4x4 array then you'll need i*buttons.lenght+j == 4*4+4 == 20 images at last iteration. And you only would need i*j/2 == 8 images, assuming is a pair match game.
if buttons have same icon, they will stay open. i made frame in it
panel, in it buttons and each button has label.
Why do you need labels? I think if two button matches you can disable those buttons so user won't be able to click them again and dispatch an actionPerformed event.
If it's not strictly necessary use arrays, you could use ArrayList instead and get benefits of its methods.
Update
I post this code because I think you still stuck using arrays. I just want to show you that there's no need to use them to keep references, you just need think a little more under objects paradigm and delegate this task to appropriate object.
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.UIManager;
public class Demo {
/*
* This variable will point to a pressed button to keep reference.
*/
JButton _alreadyPressedButton = null;
/**
* Initializes the GUI
*/
private void initGUI(){
/*
* Create needed icons - As this example uses 6 buttons, then I need 3 icons
*/
ImageIcon icon1 = (ImageIcon) UIManager.getIcon("OptionPane.errorIcon");
ImageIcon icon2 = (ImageIcon) UIManager.getIcon("OptionPane.informationIcon");
ImageIcon icon3 = (ImageIcon) UIManager.getIcon("OptionPane.warningIcon");
/*
* Make a list with 6 icons (add each icon twice)
*/
List<ImageIcon> iconsList = new ArrayList<>();
iconsList.add(icon1);
iconsList.add(icon1);
iconsList.add(icon2);
iconsList.add(icon2);
iconsList.add(icon3);
iconsList.add(icon3);
Collections.shuffle(iconsList); /* Shuffle the list */
ActionListener actionListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() instanceof JButton){
final JButton pressedButton = (JButton) e.getSource();
/*
* Execute this code in a SwingWorker to prevent block EDT at "Thread.sleep(500)" line
* Google for EDT (Event Dispatch Thread)
*/
SwingWorker sw = new SwingWorker() {
#Override
protected Object doInBackground() throws Exception {
if(_alreadyPressedButton != pressedButton){
pressedButton.setIcon(pressedButton.getPressedIcon());
if(_alreadyPressedButton != null){
Thread.sleep(500);
if(_alreadyPressedButton.getIcon() == pressedButton.getIcon()){
_alreadyPressedButton.setEnabled(false);
pressedButton.setEnabled(false);
} else {
_alreadyPressedButton.setIcon(null);
pressedButton.setIcon(null);
}
_alreadyPressedButton = null;
} else {
_alreadyPressedButton = pressedButton;
}
}
return null;
}
};
sw.execute();
}
}
};
JPanel panel = new JPanel(new GridLayout(3, 2));
panel.setPreferredSize(new Dimension(200, 200));
for(ImageIcon icon : iconsList){
JButton button = new JButton();
button.setPressedIcon(icon);
button.addActionListener(actionListener);
panel.add(button);
}
JFrame frame = new JFrame("Demo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Demo().initGUI();
}
});
}
}
You appear to be adding a JLabel to a JButton?? If this is what in fact you're doing, don't. Instead, why not simply set the JButton's ImageIcon when you desire to show or change what image the button displays, if any. You would do this simply via myButton.setIcon(fooIcon).
Edit
You state in comment:
and how will it show the image from jbutton and hide it?
You can simply swap icons. If you want to show an image, set its ImageIcon as the JButton's Icon via setIcon(myIcon). and when done, swap it out via either setIcon(otherIcon) or setIcon(null).
The link that you've provided does not put JLabels on top of JButtons which it appears that you're trying to do, and which is why I'm telling you this information.

How do I make JScrollPane scroll to follow input focus?

I have a Swing app with a large panel which is wrapped in a JScrollPane. Users normally move between the panel's subcomponents by tabbing, so when they tab to something out view, I want the scroll pane to autoscroll so the component with input focus is always visible.
I've tried using KeyboardFocusManager to listen for input focus changes, and then calling scrollRectToVisible.
Here's an SSCCE displaying my current strategy (just copy/paste and run!):
import java.awt.KeyboardFocusManager;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
KeyboardFocusManager.getCurrentKeyboardFocusManager()
.addPropertyChangeListener("focusOwner",
new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
focused.scrollRectToVisible(focused.getBounds());
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
If you run this example, you'll notice it doesn't work very well. It does get the focus change notifications, but the call to scrollRectToVisible doesn't appear to have any effect. In my app (which is too complex to show here), scrollRectToVisible works about half the time when I tab into something outside of the viewport.
Is there an established way to solve this problem? If it makes any difference, the Swing app is built on Netbeans RCP (and most of our customers run Windows).
My comment to the other answer:
scrollRectToVisible on the component itself is the whole point of that
method ;-) It's passed up the hierarchy until a parent doing the
scroll is found
... except when the component itself handles it - as JTextField does: it's implemented to scroll horizontally to make the caret visible. The way out is to call the method on the field's parent.
Edit
just for clarity, the replaced line is
content.scrollRectToVisible(focused.getBounds());
you have to take Rectangle from JPanel and JViewPort too, then compare, for example
notice (against down-voting) for final and nice output required some work for positions in the JViewPort
import java.awt.KeyboardFocusManager;
import java.awt.Rectangle;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.*;
//http://stackoverflow.com/questions/8245328/how-do-i-make-jscrollpane-scroll-to-follow-input-focus
public class FollowFocus {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final int ROWS = 100;
final JPanel content = new JPanel();
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JLabel(
"Thanks for helping out. Use tab to move around."));
for (int i = 0; i < ROWS; i++) {
JTextField field = new JTextField("" + i);
field.setName("field#" + i);
content.add(field);
}
final JScrollPane scroll = new JScrollPane(content);
KeyboardFocusManager.getCurrentKeyboardFocusManager().
addPropertyChangeListener("focusOwner", new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (!(evt.getNewValue() instanceof JComponent)) {
return;
}
JViewport viewport = (JViewport) content.getParent();
JComponent focused = (JComponent) evt.getNewValue();
if (content.isAncestorOf(focused)) {
System.out.println("Scrolling to " + focused.getName());
Rectangle rect = focused.getBounds();
Rectangle r2 = viewport.getVisibleRect();
content.scrollRectToVisible(new Rectangle(rect.x, rect.y, (int) r2.getWidth(), (int) r2.getHeight()));
}
}
});
JFrame window = new JFrame("Follow focus");
window.setContentPane(new JScrollPane(content));
window.setSize(200, 200);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
});
}
}
Here my short summary.
Add this to your Tools class:
public static void addOnEnter(Component c, Consumer<FocusEvent> onEnter) {
FocusListener fl = new FocusListener() {
#Override
public void focusGained(FocusEvent e) {
onEnter.accept(e);
}
#Override
public void focusLost(FocusEvent e) { }
};
c.addFocusListener(fl);
}
public static void scrollToFocus(FocusEvent e) {
((JComponent) e.getComponent().getParent()).scrollRectToVisible(
e.getComponent().getBounds());
}
and use it like this:
Tools.addOnEnter(component, Tools::scrollToFocus);
component can be JTextField, JButton, ...
One major issue in your code is:
focused.scrollRectToVisible(focused.getBounds());
You are calling scrollRectToVisible on the component itself! Presumably a typo.
Make your JScrollPane a final variable and call
scrollPane.getViewport().scrollRectToVisible(focused.getBounds());
Here jtextbox is the component you want to focus and jscrollpane is your scrollpane:
jScrollpane.getVerticalScrollBar().setValue(jtextbox.getLocation().x);

Replacing a imageIcon

I have a value called 'AmountWrongGuessed' that gives the amount of wrong guesses the users puts while guessing a word.
Each times the word is not found in the arraylist the AmountWrongGuessed goes ++. (tested this wih a println and it works properly)
Now the problem is each time the AmountWrongGuessed goes 1 up it should display a ImageIcon.
But insteed it displays the last image icon all the time, and skips the other icons.
I use no layout mananger (its set to null, if this makes any difference in the total picture setLayout = null)
Also while initialising this game the amountwrongguessed is default 0, yet it does not display the first imageicon either. (i used different labels before to add each icon on the same position but then i had the problem only the first image displayed and nothing changed).
public HrView(Hrgame hg) {
this.hg = hg;
CreateComponents();
SetLayoutComponents();
UpdateComponents();
AddListeners();
}
Creation of the images:
private void CreateComponents() {
hang0 = new ImageIcon("hang0.gif");
lblHang = new JLabel(hang0);
lblHang.setLocation(60, -10);
lblHang.setSize(200, 200);
hang1 = new ImageIcon("hang1.gif");
lblHang = new JLabel(hang1);
lblHang.setLocation(60, -10);
lblHang.setSize(200, 200);
hang2 = new ImageIcon("hang2.gif");
lblHang = new JLabel(hang2);
lblHang.setLocation(60, -10);
lblHang.setSize(200, 200);
}
private void AddListeners()
{
btnCheck.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
hg.Input(tfToGuessInput.getText().toLowerCase());
Pictures();
lblHang.updateUI();
}
});
}
private void Pictures()
{
//works, does increment
System.out.println(hg.getAmountWrongGuessed());
if (hg.getAmountWrongGuessed() == 0) {
add(lblHang);
}
if (hg.getAmountWrongGuessed() == 1) {
add(lblHang);
}
if (hg.getAmountWrongGuessed() == 2) {
add(lblHang);
}
}
After CreateComponents() your attribute lblHang references the label you created last (the one containing image hang2.) In order to use the 3 labels later on, you need to have 3 label attibutes which you can then use in Pictures().
Btw, in Java the naming convention is that method names start with a lowercase character.
import javax.swing.*;
import javax.swing.JLabel;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.ImageIcon;
import java.awt.BorderLayout;
import java.awt.*;
import java.awt.event.*;
public class testgui{
private static int flag = 1;
public static void main(String[] args){
final JLabel label = new JLabel("",new ImageIcon("0.jpg"),JLabel.CENTER);
final JLabel label1 = new JLabel("",new ImageIcon("1.jpg"),JLabel.CENTER);
final JLabel label2 = new JLabel("",new ImageIcon("2.jpg"),JLabel.CENTER);
final JFrame frame = new JFrame();
final JPanel panel = new JPanel();
frame.add(panel,BorderLayout.CENTER);
frame.setVisible(true);
final JButton button = new JButton("Next");
frame.add(button,BorderLayout.SOUTH);
panel.add(label);
frame.pack();
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
if(flag==0){
System.out.println("0.jpg");
//label image, flag increment
flag = flag+1;
panel.removeAll();
panel.add(label);
frame.pack();
} else if(flag==1){
System.out.println("1.jpg");
//label1 image, flag increment
flag = flag+1;
panel.removeAll();
panel.add(label1);
frame.pack();
} else if (flag==2){
System.out.println("2.jpg");
//label2 image, reset flag to 0
flag = 0;
panel.removeAll();
panel.add(label2);
frame.pack();
}
else{
System.out.println("Wrong flag number !");
}
panel.validate();
panel.updateUI();
}
});
}
}
I think if you want to switch images, using jlabels, the above codes would help. It would help to rotate jlabels containing images but this codes are not optimized.

Categories

Resources