Ok, so, what I've been trying to figure out is how I can make a custom MouseListener for all my buttons that would not require listing every single one of them in the Handler, because I'm going to have a lot of them. Here's the code I have in my Listener as of now:
package com.dinobuilding.handler;
import java.awt.Graphics;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import com.BLANK.BLANKScreen;
import com.BLANK.BLANKWindow;
import com.BLANK.menufeature.Button;
public class ButtonHandler implements MouseListener {
public BLANKWindow dbw;
public BLANK Screen dbs;
static Button button = new Button();
public int buttonX = button.x;
public int buttonY = button.y;
public int buttonSizeX = button.xSize;
public int buttonSizeY = button.ySize;
public ButtonHandler(BLANKWindow dbw, BLANKScreen dbs) {
this.dbw = dbw;
this.dbs = dbs;
}
public static void setButton(Button b) {
button = b;
}
public int mouseEventX;
public int mouseEventY;
Graphics g;
public void mouseClicked(MouseEvent e) {
mouseEventX = e.getLocationOnScreen().x;
mouseEventY = e.getLocationOnScreen().y;
if(mouseEventX <= buttonX && mouseEventX >= buttonX + buttonSizeX) {
if(mouseEventY <= buttonY && mouseEventY >= buttonY + buttonSizeY) {
button.onClicked(dbs, dbw, g);
}
}
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
And here's the code in the first button that I'm trying to implement:
package com.BLANK.menus;
import java.awt.Color;
import java.awt.Graphics;
import com.BLANK.BLANKScreen;
import com.BLANK.BLANKWindow;
import com.BLANK.handler.ButtonHandler;
import com.BLANK.menufeature.Button;
public class MainMenuPlayButton extends Button {
public static int x;
public static int y;
public static int xSize;
public static int ySize;
public static String s;
public static Graphics g;
public MainMenuPlayButton(int x, int y, int xSize, int ySize, String s, Graphics g) {
super(x, y, xSize, ySize, s, g);
this.x = x;
this.y = y;
this.xSize = xSize;
this.ySize = ySize;
this.s = s;
this.g = g;
setColor(new Color(0, 226, 26));
draw();
}
public MainMenuPlayButton() {
}
public static void draw() {
drawButton(x, y, xSize, ySize, g, s);
ButtonHandler.setButton(new MainMenuPlayButton());
}
public void onClicked(BLANKScreen dbs, BLANKWindow dbw, Graphics g) {
setColor(new Color(216, 0, 0));
}
I think my main problem is that the code in the ButtonHandler gets called before the code in the Button class and therefore the ButtonHandler is utilizing the Button class itself, not the MainMenuPlayButton class. If you need the Button class as well, simply tell me, however I can't imagine why. Thank you in advance!
Edit
Ok, after debugging some, I have found that I in fact have the opposite problem. The button is never being clicked. The getSource() method could work, however I don't really know how to use that and I don't think that I could use that without hardcoding every single button, which is really something I do not want to do.
EDIT 1:
Do you think maybe I could do use the MouseEvent's getX or getXOnScreen? By the way, I registered the ButtonHandler using frame.addMouseListener on my JFrame, so...
EDIT 2:
It would seem that the getX method does not work either. If you could help me on that, I would very much appreciate that.
If you want to get the object that was pressed and tripped the MouseListener, use the MouseEvent's getSource() method. For example, this might work:
public void mouseClicked(MouseEvent e) {
(YourButton) button = (YourButton) e.getSource();
button.onClicked(...);
}
Other bits:
Rename your class from Button to something else, since the Button name clashes with the java.awt.Button class, and this can cause difficult to debug errors.
I cringe any time I see a Graphics field declared in a class, as it suggests possible inappropriate painting. Make sure that you really know what you're doing if you ever use one of these as a field since it's easy to get image loss or a NullPointerException if not used correctly, since the Graphics object is frequently changed by Java, and this change is completely out of your (the programmer's) control. Don't say that you haven't been warned.
Edit
Regarding your comments:
Yes, I do know what I'm doing with the Graphics field, however, if it makes you feel better, know that it's only temporary and I will be changing it to something else later.
OK, I've just been burned on this before. As long as you get it from a BufferedImage and don't try to get it by calling getGraphics() on a component or by pulling it out of a paint or paintComponent method, then you might be OK.
Also, I'm pretty sure that I'm getting the object it clicked correctly, but I can't get it to access the correct subclass of Button. It's only getting the Button class itself, not the MainMenuPlayButton.
Sorry, but this doesn't make sense since you don't get "classes" when you obtain a reference, an object pure and simple, and in fact you would get the very same object that the ButtonListener was added to and that tripped the listener, and the class of this reference will be whatever class your button is. I am assuming that you're adding your MouseListener directly to your "Button" object, correct? Again, time to do some debugging.
Edit 2
Regarding the most recent edit to your question:
Ok, after debugging some, I have found that I in fact have the opposite problem. The button is never being clicked. The getSource() method could work, however I don't really know how to use that and I don't think that I could use that without hardcoding every single button, which is really something I do not want to do.
No, there is no need to hard-code each button, trust me. That's the whole reason for using listeners that are added to the buttons.
EDIT 1: Do you think maybe I could do use the MouseEvent's getX or getXOnScreen? By the way, I registered the ButtonHandler using frame.addMouseListener on my JFrame, so... the
There's one of your problems. If you want to listen to your buttons, you're going to want to be able to register listeners on the button itself. If you have an array or collection of them registering listeners is easy. And no, I don't recommend using x and y on screen since it makes your program extremely fragile. If you did this, any changes to the structure of your GUI would require subsequent hard-code changes to your x and y handling. Ugh.
This begs the question of why create your own Button class, and why not instead use JButtons or a subclass of JButtons. You appear to be re-inventing the wheel, but (sorry to be blunt) creating one that is square.
Edit 3
But you cast the variable to a button, meaning that if I have multiple buttons I have to cast each and every one of them to a different thing.
No absolutely not as the magic of polymorphism should work here. But they're objects of the same type, no? Or do you have many different subclasses of your Button class? And regardless, inside of the mouseClicked(...) method, you appear to want to call only one method on your button, onClicked(...), which I imagine has to be an object of the super class, right? So by calling this method on the current button, It should call its own correct code.
The problem I have with JButton is that they already exist. I can't edit them and I can't customize them, ...
This is patently not true. You can change their appearance and behaviors by many means, including by subclassing or by a factory creation method. Plus they already come with the machinery for being able to register listeners and respond to mouse actions.
...Also, would I have to register/make a new handler for each and every one of the buttons?
Again, you appear to be forgetting that polymorphism should take care of all of this. One handler should do, depending on how well-behaved your code is.
I am going to have a LOT of buttons, and I don't think that that would be a viable solution. If not the getX how would I get it to do something when the thing is clicked?
I've given you my recommendation, other than sometimes it is better to re-write sections of code if the design can be improved, meaning again you may want to consider retrofitting your code to use JButtons.
Related
I have not found a similar question anywhere, so I decided to post.
The problem is that texttooltip is not showing on element on android, though works perfectly on desktop launch. I added some sysouts and app seems to detect enter and exit events correctly(desktop shows same console output), but for some reason tt still wont popup.
The objective is so when user holds the element(touchable), app shows a texttooltip.
What may be the problem?
Edit: I have tried extending TextTooltip, overriding enter() and exit() methods and only adding sysout to them. Holding finger on button with my TextTooltip triggers enter() once and releasing a finger triggers exit() once. Like hovering a mouse on desktop, same output. Only difference is actual tooltip not showing up.
Okay I fixed this. Maybe it helps anyone with similar os same issue.
public class HoldTooltip extends ActorGestureListener {
Label tooltip_text;
Table tooltip_actor;
public HoldTooltip(String tooltip_text) {
this.tooltip_text = new Label(tooltip_text, skin);
tooltip_actor = new Table();
tooltip_actor.add(this.tooltip_text);
tooltip_actor.setSize(this.tooltip_text.getPrefWidth(), this.tooltip_text.getPrefHeight());
getGestureDetector().setLongPressSeconds(0.5f);
}
#Override
public boolean longPress(Actor actor, float x, float y) {
tooltip_actor.setPosition(x+50, y+50);
actor.getStage().addActor(tooltip_actor);
return super.longPress(actor, x, y);
}
#Override
public void touchUp(InputEvent event, float x, float y, int pointer, int button) {
tooltip_actor.remove();
super.touchUp(event, x, y, pointer, button);
}
Basically I just redone the stock TextTooltip, the difference being this one actually works as intended. If you want cool animations just add a sequence when adding or removing tooltip_actor to stage.
Having an issue with tables and updating a label! Here is the dilemma, I have a sell button in my game that is updating the player's coins whenever they sell an item, that part is working perfectly. The issue I am having is trying to get the coin value to update on the screen while there in this separate menu (see pic attached, coins in the top left). The problem is that the coin value is in another stage in another class. This is because I have different tables that pop up in the middle when I click the different buttons at the bottom. I have tried helper methods for going in and clearing that table and updating it and then sending me back to this item page but it is not working, I can post any code needed but this is more of a general question on how to update a label within a table in a stage.
Update: So to kinda sum up my question, I have a Screen and I have have three tables in it the bottom table the top left and the top right. Then I add the table to the stage in the middle when they press the inventory or shop button etc. What I am looking to do is to keep the item page open and simply just update the value of the Coin label, I know I can change the text using .setText(); Im just not sure how I can update that portion of the screen etc..
Update 2: If I just set the screen back to a new screen of this screen it updates the coin value but then I am not on the item page anymore which is not ideal.
Update 3: Thanks for the help so far guys, #John your answer is super helpful aswell. Im still not getting this working though here is a little bit of the code where the label is being handled.
playerCoinLabel = new Label(playerSave.getCoinsString(),skin,"defaultMiddle");
This is where it is getting added to the table.
tableLeft = new Table(skin);
stage.addActor(tableLeft);
tableLeft.setBounds(0,0, Gdx.graphics.getWidth(), Gdx.graphics.getHeight());
tableLeft.setFillParent(true);
tableLeft.top().left();
tableLeft.add(healthNonButton).size(84,80).left().padLeft(10).padTop(5);
tableLeft.add(playerHealthLabel).left().padLeft(15);
tableLeft.row();
tableLeft.add(levelNonButton).size(74,70).center().padLeft(10);
tableLeft.add(playerLevelLabel).left().padLeft(19);
tableLeft.row();
tableLeft.add(coinNonButton).size(74,70).center().padLeft(10);
tableLeft.add(this.playerCoinLabel).left().padLeft(15); //This line
tableLeft.row();
Then I have this method for updating my label using the setText like you guys were telling me about.
public void updatePlayerCoins() {
playerCoinLabel.setText(playerSave.getCoinsString());
}
and if I call this method anywhere, render() or where im setting the new coin value it is not updating/changing the label in the top left of my screen. I can post all the code to a github if I need to just posted the things involving the label. This is just a project im working on to increase my skill set so sorry if I sound amateur, it is because I am!
Thanks everyone!
It seems like you're asking two things- how do I update a label? and How do I structure my code? It's hard to tell what's going wrong with the former since we can't see your code, but #Tenfour04 is right- you want to retain a reference to the label somewhere and call setText() when you want to change the amount.
As far as structuring your code, I would suggest a simple OOP design and then evolve it like so:
First, we need an object to represent the player:
class Player {
private int coins; // Pretend there are getters / setters.
private int health;
private int level;
}
Now you probably have more than one way that you want to represent this player information, so we'll split the rendering code into a separate class or set of classes:
class StatWidget {
private Stage stage;
private Player player;
private Label lblCoins;
public StatWidget(Player player) { // Pseudo-code
this.player = player;
this.stage = new Stage();
Table tbl = new Table();
this.lblCoins = new Label(); // Notice we keep a reference to the label
tbl.add( this.coins );
}
public void update() {
lblCoins.setText(player.getCoins());
}
}
Now you can sync the UI with your player object's state simply by calling Player#update(). But when do you call it?
You could call update() in your render method. This is a little inefficient because you're updating the object whether it needs to be updated or not, but it's dead simple, and if you're only updating a few UI elements this way it probably doesn't matter. Personally, I'd stop here.
If you want to be more precise, you would only call update() when you actually make a change to the Player's coins. You can do this by finding the places in your code where you set the player's coins and add the update call like so:
player.setCoins( A_LOT_OF_MONEY );
statWidget.update();
Problem is this gets more cumbersome as you add more widgets- all your game logic now has to know about StatWidget and make calls to it. We could cut this dependency a little bit by using an event-driven architecture. Essentially, whenever player's state changes, it would send an event to interested parties notifying them of the change. You could use the pseudo-code below:
interface Publisher {
void subscribe(Subscriber subby);
void unsubscribe(Subscriber subby);
}
class Player implements Publisher {
private List<Subscriber> subscribers;
private int coins;
// ...
public void setCoins(int amount) {
this.coins = amount;
for(Subscriber subscriber : subscribers) subscriber.notify("COINS", amount);
}
public void subscribe(Subscriber subby) {
this.subscribers.add(subby);
}
public void unsubscribe(Subscriber subby) {
this.subscribers.remove(subby);
}
}
interface Subscriber {
void notify(String event, int qty);
void dispose();
}
class StatWidget implements Subscriber {
private Publisher player;
private Label label;
// ...
public StatWidget(Player player) {
this.player = player;
this.player.addSubscriber(this);
void notify(String event, int qty) {
if(event.equals("COINS")) label.setText(qty);
}
void dispose() {
this.player.unsubscribe(this);
}
}
The event system above could certainly be polished, and you could likely do clever things with generics (or use a library that has thought all this out for your), but hopefully it illustrates the concepts.
Since I'm new I can't post more than two links, but this is an x-post from reddit.com/r/learnprogramming, just for full disclosure.
I'll basically just be pasting what I said there to here. Thanks for your help, if you can help.
I'm writing somewhat of a graphing application. I currently only have it able to graph sin(x), but that's not the point of this question. I am not able to draw to my main panel. Here is what it currently looks like.
I had an overridden paint function in my Window.java class, which drew the sin(x) function and the axes, but when I made an inner class which extended JPanel(), it would no longer draw.
I then tried to make a separate file, but that didn't draw anything either.
What could be preventing it from drawing?
Here are all my files in question.
edit: code in question:
GraphDraw.java:
//import stuff
Public class GraphDraw extends JPanel {
SinX sinx = new SinX();
GraphPanel p = new GraphPanel();
#Override
public void paintComponent(Graphics gc) {
super.paintComponent(gc);
Graphics2D g = gc;
p.paintComponent(g);
sinx.paint(g);
}
}
And in Window.java, I initialize GraphDraw and add it to my main panel, which is underneath the buttons in the picture and above the x/y min/max labels.
GraphDraw drawer = new GraphDraw();
/*
GUI code
*/
mainPanel.add(drawer);
SinX.java
//import stuff
public class SinX extends Component {
public void paint(Graphics g) {
g.setColor(Color.red);
for(double x=-400;x<=400;x=x+0.5) {
double y = 50 * sin(x*((Math.PI)/180));
int Y = (int)y;
int X = (int)x;
g.drawLine(400+X,300-Y,400+X,300-Y);
}
}
}
First, before anything else, do the following:
Change you object from Component to JComponent
Do not ever, ever call paintComponent() or paint() on a graphics object from swing or awt, use object.repaint(); (For reasons I won't go into here, because it's long and complicated)
From there I would try calling setVisible(true); on all your objects. If you are getting this code from a tutorial, then stop and use a different tutorial. You need to learn how swing and the AWT library work before you can start making user interfaces. Nobody uses AWT anymore because Swing is much better. For reasons why, look at the following page. If you are too lazy to do that, its because it's more optimized and more powerful.
What is the difference between Swing and AWT?
I am cleaning my code. I read that I putting the ActionListener is another class is better. So that's what I did.
But in my ActionListener, everything works except at some point in the code, I got a setSize(xx,xx). I worked before because it was in the same class. But not anymore. I've tried multiple solutions but I couldn't figure it out.
ActionListener:
public class ActionFrame implements ActionListener{
public void actionPerformed(ActionEvent e){
Object src = e.getSource();
if(src == Frame.Console_Bouton){
System.out.println("Bouton console");
if(getSize().getWidth() >= 750){
/** If True (Retirer) */
for(int i = 1090; i > 689; i--){
setSize(i, 490);
System.out.println("Rétractation du Frame");
}
}else{
/** If False (Etirer) */
for(int i = 689; i < 1090; i++){
setSize(i, 490);
System.out.println("Etirage du Frame");
}
}
}
...
As for errors, there are none, it will just freeze the program.
Guessing: possibly this is a case where extracting the ActionListener is not a great idea, since it uses a private method of your object.
Classes with generic/reusable functionality deserve to be on their own. As long as they are intended for specific usage, it's not bad practice (at all!) to only put them as close as possible to the spot where they're used. I can imagine that your setSize method is not part of your class' public interface, so the ActionListener is merely 'glue' to couple an event to your specific class.
In this case, you would create a 'tiny' line of glue:
abstract class ActionAdapter implements ActionListener {
}
...
frame.Console_Bouton.addActionListener(
new ActionAdapter(){ // anonymous inner class
void actionPerformed(ActionEvent e){
... // (no need to check source!)
}
});
Create a new class as:
ButtonAction implements actionListner
{
//put the code above here
}
A Good way of doing this is use Callback mechanism.
I have posted an answer in the same context here
JFrame in separate class, what about the ActionListener?
-- EDIT--
Get the source from ActionEvent then find its parent (get parent of parent if needed until you get the desired component that needs to be re sized) and call setSize() on it.
Create an instance of the View in the controller
Change the access modifier of setSize(xx,yy)method from private to public.
replace setSize in actionPerformed() with to view.setSize(xx,yy).
I am working on a custom JSlider that has a custom Track Rectangle. I want the ability to set the color of the track rectangle when first declaring the slider.
Here's a snippet of what I have (The classes are in separate files in the same package):
public class NewSlider extends JSlider {
Color kolor;
public NewSlider (Color k) {
kolor = k;
}
public void updateUI() {
setUI(new NewSliderUI(this, kolor);
updateLabelUIs();
}
}
public class NewSliderUI extends BasicSliderUI {
Color sliderColor = Color.BLACK;
public NewSliderUI (JSlider b, Color k) {
super(b);
sliderColor = k;
}
}
In the above code, "kolor" is initially null and leads to and error when NewSliderUI tries to use it. It appears that the updateUI() method is called before anything else. Then the NewSlider constructor is called. I have tried a variety of things, but because updateUI() appears to run before anything else, nothing I add to the NewSlider class seems to matter.
If I hardcode a Color (ie. setUI(new NewSliderUI(this, Color.BLACK);), then it works, but having a different class for each color seems silly.
Thanks.
I don't see how kolor could be null unless one of the following are happening:
You're passing a null value to the constructor
You're not instantiating NewSlider in the Swing EDT and are having some strange cache issues
NewSlider is being constructed via reflection/deserialization and kolor is not being set.
Have you tried running this in the debugger with some breakpoints? I'd be curious to ensure that the NewSlider constructor is being called (and before the NewSliderUI constructor).
Edit: I see what you mean below. I forgot that the no args constructor for JSlider was being called implicitly. What about doing the following:
public class NewSlider extends JSlider {
Color kolor = Color.BLACK;
public NewSlider (Color k) {
kolor = k;
updateUI();
}
public void updateUI() {
setUI(new NewSliderUI(this, kolor);
updateLabelUIs();
}
}
You end up calling updateUI() twice, but the end result should be what you want.