JTextField can't be focused with mouse on Mac OS - java

We are tasked with making a simple game. Now, we have had the game done for a long while now, but I have only just gotten around to implementing the join/host menu (using a JPanel in our JFrame, which only contains a canvas which is used to render our sprites/shapes/etc.). We have 4 JTextFields and two JButtons. Our canvas is set to disabled when this display is shown so as to not interfere with input (i.e., with its mouse listener and such). On Windows machines, we can click on all of the boxes, we get the nice I-beam cursor, etc., and we can type in there normally and then click the buttons. However, when the same is attempted on MacOS, you cannot click on the boxes or the buttons. You don't get the I-beam on the boxes. It's like they don't exist. However, we can use the tab key to switch focus through all elements, and can use that to type in the boxes, press the buttons, etc., just as you should be able to with the mouse. I've tried requesting focus like 20 different ways, but that didn't seem to work. I've made many other apps the same way (JFrame > JPanel > JButton/TextField), and they all have worked just fine on MacOS. I have never seen anything like this before.
Rather than post a whole bunch of entire files, I'll trim them down. The first one is our main entry point, the Game class. It looks something like this:
public class Game extends Canvas implements Runnable
private JFrame frame;
private ConnectScreen connectScreen;
public getFrame() { /* get reference to frame */ }
public setFrame() { /* set the frame */ }
public void run() { /* game loop, calls render() */ }
public Game() { /* create window, add canvas to it, get reference to frame, instantiate stuff, etc. */ }
public void tick() { /* every frame this happens, just tells spawners to spawn stuff, etc. */ }
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if (bs == null) {
this.createBufferStrategy(3);
return;
}
Graphics g = bs.getDrawGraphics();
///////// Draw things below this/////////////
g.setColor(Color.black);
g.fillRect(0, 0, WIDTH, HEIGHT);
// SCREEN
if (!isPaused()) {
// THIS is where we tell the canvas to enable if we're not on the connect screen
if (gameState != STATE.Join && gameState != STATE.Host) {
this.setEnabled(true);
}
/* i omitted rendering from all of these, since it's not useful here. most say somethingScreen.render(g) or something like that. */
if (gameState == STATE.Wave || gameState == STATE.Multiplayer || gameState == STATE.Bosses || gameState == STATE.Survival) { // render gameplay items
} else if (gameState == STATE.Menu || gameState == STATE.Help || gameState == STATE.Credits) { // render menu
} else if (gameState == STATE.Upgrade) { // render upgrade screen
} else if (gameState == STATE.GameOver) { // render game over screen
} else if (gameState == STATE.Leaderboard) { // render leaderboard
} else if (gameState == STATE.Color) { // render color picker screen
} else if (gameState == STATE.Join || gameState == STATE.Host) {
// if we are on the connect screen, disable this canvas
this.setEnabled(false);
connectScreen.render(g);
} else if (gameState == STATE.LeaderboardDisplay) { // render the leaderboard
}
} else {
pauseMenu.render(g);
}
if(!isPaused()) {} // renders the handler's things
///////// Draw things above this//////////////
g.dispose();
bs.show();
}
}
Other than that, there's not much of interest in that class. Here's the (trimmed down) ConnectScreen class:
public class ConnectScreen extends JPanel {
JFrame frame; // this is the frame from the Game class
JTextField _____; // there are 4 of these
JButton _____; // there are 2 of these
public ConnectScreen(Game game) { // the game is passed in to get the frame from it
super();
this.setBackground(Color.black);
this.setPreferredSize(new java.awt.Dimension(Game.WIDTH, Game.HEIGHT));
this.setSize(new java.awt.Dimension(Game.WIDTH, Game.HEIGHT));
this.game = game;
this.mpSpawn = mp;
game.getFrame().add(this);
this.setFocusable(true);
/* set up each component */
/* add each component (click handlers, etc.) */
/* add panel to frame */
}
public void render(Graphics g) {
super.paintComponent(g);
this.paintComponents(g);
}
}
We know the setup of elements and such all worked, since we can see and use them all on Windows, but for some reason when we try it on MacOS, it doesn't work. We can tab-select them and type/press buttons, but for some reason you cannot click on them to focus. Any help would be appreciated. Thanks!

Fixed it, thanks to MadProgrammer. Apparently, a BufferStrategy cannot be used to render Swing components. I just added an if statement in Game::render() where it only does any rendering if the ConnectScreen isn't shown. If it is, it just disables and turns the canvas invisible, while the ConnectScreen::render() (renamed to override JPanel::paintComponent()) just uses super.paintComponent() to passively draw Swing components.

Related

JPanels not being drawn

I'm working on a vertical scrolling game, and I'm using a thread to generate new enemies every 2 seconds. Each enemy is an image in a JPanel. For some reason, The generated enemies are not showing up in the JFrame, but they are present. When the player collides with one of the enemies, all the enemies show up.
Here's the code:
private void checkCollision() {
for(AlienShip as : enemies) {
if(player.getBounds().intersects(as.getBounds()))
player.setVisible(false);
}
}
private void setAlien() {
alien = new AlienShip();
add(alien);
enemies.add(alien);
System.out.println("Enemies: " + enemies.size());
}
public Thread alienGenerator() {
for(int i = 0; i < 3; i++) { // these are being drawn
setAlien();
}
return new Thread(new Runnable() {
#Override
public void run() {
int sleepTime = 2000;
while(true) {
try {
Thread.sleep(sleepTime);
} catch(InterruptedException e) {
System.out.println(e);
}
setAlien(); //these aren't
}
}
});
}
private void gameLoop() {
alienGenerator().start();
mainTimer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
repaint();
checkCollision();
}
});
mainTimer.start();
}
It always seems that you're Darned If You Do And Darned If You Don't. As far as I'm concerned the code you had placed in your earlier post was adequate, as a matter of fact, it was still lacking (no PlayerShip Class). The code example in this post does even less justice. Never the less......
Before I get started I just want you to know that I personally would have tackled this task somewhat differently and the meager assistance provided here will be solely based on the code you have already provided in this and previous posts.
The reason you are not seeing your Alien Ships displaying onto the Game Board upon creation is because you don't revalidate the board panel. As you currently have your code now this can be done from within the Board.setAlien() method where the Alien Ships are added. Directly under the code lines:
alien = new AlienShip();
add(alien);
enemies.add(alien);
add the code line: revalidate();, so the code would look like this:
alien = new AlienShip();
add(alien);
enemies.add(alien);
revalidate();
Your Alien Ships should now display.
On A Side Note:
What is to happen when any Alien Ship actually makes it to the bottom of the Game Board? As a suggestion, have them re-spawn to the top of the game board (serves ya right fer missin em). This can be done from within the AlienShip.scrollShip() method by checking to see if the Alien Ship has reached the bottom of the board, for example:
public void scrollShip() {
if (getY() + 1 > this.getParent().getHeight()) {
setY(0 - PANEL_HEIGHT);
}
else {
setY(getY() + 1);
}
}
In my opinion, PANEL_HEIGHT is the wrong field name to use. I think it would be more appropriate to use something like ALIEN_SHIP_WIDTH and ALIEN_SHIP_HEIGHT. Same for the variables panelX and panelY, could be alienShipX and alienShipY. Food for thought.
As you can see in the code above the current Game Board height is acquired by polling the Game Board's getHeight() method with: this.getParent().getHeight(). This allows you to change the Game Board size at any time and the Alien Ships will know where that current boundary is when scrolling down. All this then means that the setResizable(false); property setting done in the Main Class for the Game's JFrame window can now be resizable: setResizable(true);.
You will also notice that when the Alien Ship is re-spawned at top of the Game Board it is actually out of site and it flows into view as it moves downward. I think this is a much smoother transition into the gaming area rather than just popping into view. This is accomplished with the setY(0 - PANEL_HEIGHT); code line above. As a matter of fact even when the game initially starts, your Alien Ships should flow into the the gaming area this way and that can be done from within the AlienShip.initAlienShip() method by initializing the panelY variable to panelY = -PANEL_HEIGHT;.
This now takes me to the initialization of the PANEL_WIDTH and PANEL_HEIGHT fields. The values seem enormous (224 and 250 respectively). Of course you may have set to these sizes for collision testing purposes, etc but I think an image size of 64 x 35 would most likely suffice:
This image should be a PNG image with a transparent background which then eliminates the need for the setBackground(Color.BLUE); code line located within the AlienShip.initAlienShip() method.
The AlienShip.getX() and AlienShip.getY() methods should be overridden:
#Override
public int getX() { ... }
#Override
public int getY() { ... }
I think extending the AlienShip Class to JLabel would be better than to JPanel. To JPanel seems like overkill:
public class AlienShip extends JLabel { ... }
Adding a background image to the Game Board can add pizazz to the game. This can be achieved by adding the following code to the Board.paintComponent() method:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
ImageIcon imgIcon = new ImageIcon("images/StarBackground.png");
Image img = imgIcon.getImage();
g.drawImage(img, 0, 0, this.getSize().width, this.getSize().height, this);
}
Images can be acquired here.
This should keep you going for a while. Before to long it'll be Alien mayhem.

Mouse input problems in java

Edit:
I have an application that uses a swing Timer to control when an action listener interface fires. The mouse logic works but occasionally will not detect a click. Below is my commented code.
public class Board extends JPanel implements MouseListener, MouseMotionListener, ActionListener
{
private MainMenu mainMenu = new MainMenu();
private static String State; /*Makes the control flow simpler, just checking
strings that describe the state. All the states are contained in GameState class.*/
public Board()
{
this.addMouseListener(this);
this.addMouseMotionListener(this);
setVisible(true);
mainMenu.initLogIn(); /*This just loads the button images*/
Timer timer = new Timer(12, this); /*(millisecond delay, tells this class
to update any actionlistener (mouselistener etc)*/
timer.start();
}
public void paint(Graphics G)
{
super.paint(G);
Graphics G2d = (Graphics2D)G;
/*Main menu paint logic*/
// This paints buttons from mainMenu class on screen
G.drawImage(mainMenu.getTopic1().getspriteImage(),
mainMenu.getTopic1().getxCoord(),
mainMenu.getTopic1().getyCoord(),this);
G.drawImage(mainMenu.getTopic2().getspriteImage(),
mainMenu.getTopic2().getxCoord(),
mainMenu.getTopic2().getyCoord(), this);
G.drawImage(mainMenu.getTopic3().getspriteImage(),
mainMenu.getTopic3().getxCoord(),
mainMenu.getTopic3().getyCoord(),this);
/*Shows mouse input worked by changing the background color*/
if (State == GameState.MAINMENU_TOPIC1)
{
setBackground(Color.BLACK);
}
if (State == GameState.MAINMENU_TOPIC2)
{
setBackground(Color.BLUE);
}
if (State == GameState.MAINMENU_TOPIC3)
{
setBackground(Color.GRAY);
}
repaint(); //tells paint to repaint, which allows gui to update
}
#Override
public void mouseClicked(MouseEvent e)
{
Point point = e.getPoint();
/*This contains the logic to change State based on mouse clicks*/
if(mainMenu.getTopic1().getRectangle().contains(point))
{
State = GameState.MAINMENU_TOPIC1;
}
if(mainMenu.getTopic2().getRectangle().contains(point))
{
State = GameState.MAINMENU_TOPIC2;
}
if(mainMenu.getTopic3().getRectangle().contains(point))
{
State = GameState.MAINMENU_TOPIC3;
}
}
So, I am unsure why mouse clicks would not always be detected. I know there is a chance that the time allocated to update the action listeners could be too short. However, there is not very much code for the machine to loop through, so I figure this is not the problem. Any thoughts on what might cause the mouse to behave this way?
Also, I will definitely implement this later using JButtons. I am sure that would help clean up my code on the larger project
Thanks for the comments, and I hope this clears up the majority of the questions.
A mouse "click" may essentially be a double or triple click. You can get that by using evt.clickCount. It will coalite as one event.
If you want to get every "press", use mousePressed() instead.

Getting graphics of a Jframe issue

Pre note: I can not include my engine right now. I have my own that I developed that works, and I've used some fr tutorials. The draw method is being invoked 60 times a second.
So I have my own Java Jframe (I created one and set its name and that's all) and I use frame.getGraphics() to get the Graphics object.
In my method that's being called 60 times persecond I increment an int which I then use to draw an image. Basically each second I increment the x value of a quick graphics.fillRect().
The rectangle is drawn but it is very laggy and not smooth.
Are there any extra steps that I need to do to make sure I have a smooth jFrame that can draw many images per second?
I recommend adding a canvas to your frame, then using the canvas's BufferStrategy.
class Game extends Canvas {
public static void main(String[] args) {
Game game = new Game()
//init frame and add canvas
while(true) {
render();
}
}
public void render() {
BufferStrategy bs = getBufferStrategy();
if(bs == null) {
createBufferStrategy(3); //triple buffering
return;
}
Graphics g = bs.getDrawGraphics();
//do what you want with Graphics g
g.dispose();
bs.show();
}
}

JTextField on Key Entered flashing black

I am making a game canvas using swing and decided to use JTextField's for the input of a username and password into the panel.
I am buffering an image then rendering it onto the screen instead of drawing everything directly onto the panel real-time.
I have ran into a problem though, I paint a background and have set both of my text fields to opaque, but it seems that whenever I go to enter something into those text field's it flashes a black box where the JTextField is.
It happens in both of my username and password fields. Any idea of what the cause of this could be?
Other helpful information: Whenever I click on a text box, both of the components flash black where the first character would be.
EDIT -- I just noticed that the login button I have also flashes black when MOUSE_ENTERED and MOUSE_EXIT.
public class GamePanel extends JPanel implements Runnable {
public GamePanel(int width, int height) {
this.pWidth = width;
this.pHeight = height;
setController(new LoginGameController(this));
setPreferredSize( new Dimension(pWidth, pHeight));
setBackground(Color.BLACK);
setFocusable(true);
requestFocus(); // the JPanel now has focus, so receives key events
// create game components
addMouseListener(this);
addKeyListener(this);
setLayout(null);
startGame();
}
private void startGame()
// initialise and start the thread
{ if (animator == null) {
animator = new Thread(this);
animator.start();
}
}
public void run() {
while(true) {
gameUpdate();
if(getGraphics() != null){
gameRender(); // render the game to a buffer
paintScreen(); // draw the buffer on-screen
}
try {
Thread.sleep(28);
} catch (InterruptedException e) {}
}
}
private void paintScreen() {
Graphics2D g = (Graphics2D) getGraphics();
if ((g != null) && (img != null))
g.drawImage(img, 0, 0, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
private void gameRender() {
if(getWidth() > 0 && getHeight() > 0)
img = createImage(getWidth(), getHeight());
if(img != null) {
Graphics2D g = (Graphics2D) img.getGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.BLACK);
g.fillRect(0, 0, pWidth, pHeight);
getController().render(img);
paintComponents(img.getGraphics());
}
}
}
Here is text fields: (from a seperate class entirely calling to the GamePanel using getPanel()...)
//Setup Login fields
usernameTF = new JTextField();
usernameTF.setOpaque(false);
usernameTF.getCaret().setBlinkRate(0);
usernameTF.setForeground(Color.WHITE);
usernameTF.setBounds(USERNAME_FIELD);
usernameTF.setBorder(null);
getPanel().add(usernameTF);
passwordTF = new JPasswordField();
passwordTF.setOpaque(false);
passwordTF.getCaret().setBlinkRate(0);
passwordTF.setForeground(Color.WHITE);
passwordTF.setBounds(PASSWORD_FIELD);
passwordTF.setBorder(null);
getPanel().add(passwordTF);
loginBtn = new JButton();
loginBtn.setOpaque(false);
loginBtn.setBackground(null);
loginBtn.setBorder(null);
loginBtn.setBounds(LOGIN_BUTTON);
loginBtn.addMouseListener(getPanel());
getPanel().add(loginBtn);
Thanks!
The basic problem is, you circumventing Swings repaint process and not honoring the EDT when you up-date your graphics.
JComponent#getGraphics is a temporary/scratch buffer which will be re-draw on the next repaint. Also, if you didn't create the graphics, you shouldn't dispose it!
public void run() {
while(true) {
gameUpdate();
if(getGraphics() != null){
gameRender(); // render the game to a buffer
try {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
paintScreen(); // draw the buffer on-screen
}
});
} catch (Exception exp) {
exp.printStackTrace(); // please clean this up
}
}
try {
Thread.sleep(28);
} catch (InterruptedException e) {}
}
}
I don't know if this will fix it, but it can't hurt. (This WILL effect you FPS and you should be taking into consideration how long it took to paint and how long you want to wait)
Alternativly, rather then calling paintScreen(), you could call repaint (or have paintScreen do it) and override the paintComponent method to paint your buffer.
This would allow Swing to resume control over the paint process properly.
A window or component has at least three ways to repaint.
on demand from the operating system when it has lost a part of the window buffer.
on demand from a library component when it has changed its state.
manually by manipulating its own frame buffer.
normally, the following pathway is active:
The operating system may request a particular rectangle to be updated. This is picked up by the toolkit. Additionally, a component may signal its change to the toolkit as well through its repaint method. The toolkit keeps collecting an merging incoming repaint requests for a while then asks the window to repaint a particular rectangle. The default action for the window is to paint itself by calling its paintComponent method where it doesn't overlap with solid children and then paint its components recursively.
If you do your own rendering, the toolkit is doing it as well. Since you didn't override your paintComponent, when this method runs, it acts as default. The default action is to do nothing. The toolkit then picks up the empty frame buffer and paints the button (which doesn't paint its background) over it.
You can disable the toolkit's rendering process and do all rendering yourself (grab a buffer, paint into it, then submit). You can call the setIgnoreRepaint(true) method to ignore the requests by OS.

multiple graphics2d

ok i have two classes similar like this(the graphics are set up the same way) and another class that is displayed on the bottom. as you can see i have two graphics2ds that i would like to display at the same time with the items class being transparent and on top (the items class has almost nothing in it, and the game class is fully covered with pictures and such)
is there any way to do this?
currently the items class take priority ever the game class because it was called last and totally blocks the game class.
public class game extends Canvas implements Runnable
{
public game()
{
//stuff here
setBackground(Color.white);
setVisible(true);
new Thread(this).start();
addKeyListener(this);
}
public void update(Graphics window)
{
paint(window);
}
public void paint(Graphics window)
{
Graphics2D twoDGraph = (Graphics2D)window;
if(back==null)
back = (BufferedImage)(createImage(getWidth(),getHeight()));
Graphics graphToBack = back.createGraphics();
//draw stuff here
twoDGraph.drawImage(back, null, 0, 0);
}
public void run()
{
try
{
while(true)
{
Thread.currentThread();
Thread.sleep(8);
repaint();
}
}catch(Exception e)
{
}
}
}
class two
public class secondary extends JFrame
{
private static final int WIDTH = 800;
private static final int HEIGHT = 600;
public secondary()
{
super("Test RPG");
setSize(WIDTH,HEIGHT);
game game = new game();
items items = new items();
((Component)game).setFocusable(true);
((Component)items).setFocusable(true);
getContentPane().add(game);
getContentPane().add(items);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main( String args[] )
{
secondary run = new secondary();
}
}
Here are my suggestions:
Extend JComponent rather than Canvas (you probably want a lightweight Swing component rather than a heavyweight AWT one)
Then don't bother with the manual back-buffering for your drawing - Swing does back-buffering for you automatically (and will probably use hardware acceleration while doing so)
Have one component draw both items and the rest of the game background. There is no good reason to do it separately (even if you only change the items layer, the background will need to be redrawn because of the transparency effects)
Capitalise Your ClassNames, it makes my head hurt to see lowercase class names :-)
EDIT
Typically the approach would be to have a class that represents the visible area of the game e.g. GameScreen, with a paintCompoent method as follows:
public class GameScreen extends JComponent {
....
public void paintComponent(Graphics g) {
drawBackground(g);
drawItems(g);
drawOtherStuff(g); // e.g. animated explosions etc. on top of everything else
}
}

Categories