JSwing - Re utilse event handler with different parameter value - java

I'm trying to code a telephone and the first thing I wanted to do is to assign a different sound to each button pressed.
I don't want to write ten handlers such as this one:
private void buttonOneHandler(java.awt.event.MouseEvent evt) {
try{
buttonSound = AudioSystem.getClip();
AudioInputStream ais = AudioSystem.getAudioInputStream(new File("resources/buttonOne.wav"));
buttonSound.open(ais);
buttonSound.loop(0);
}
catch(Exception e)
{
e.printStackTrace();
}
}
Instead, I would like to use a unique handler, which accepts a String parameter (the route to the wav file), so that each button changes getAudioInputStream parameter value.
Is that possible?
Thank you very much.

private static class PlaySoundActionListener implements ActionListener {
private String soundPath;
private PlaySoundActionListener(String soundPath) {
this.soundPath = soundPath;
}
#Override
public void actionPerformed(ActionEvent e) {
// play the sound at this.soundPath
}
}
...
button1.addActionListener(new PlaySoundActionListener("resources/buttonOne.wav"));
button2.addActionListener(new PlaySoundActionListener("resources/buttonTwo.wav"));
button3.addActionListener(new PlaySoundActionListener("resources/buttonThree.wav"));
Note that a MouseListener is not the appropriate listener to use with a button. ActionListener is the one which is invoked when a button is clicked, and regardless of the way it's clicked (with the mouse, the keyboard, a keyboard shortcut).

This is possible by writing a single ActionListener, and registering it with each button.
You'd then have to use JButton(Action) constructor to force the button to generate your custom action on click.
This is covered in the swing tutorial:
http://docs.oracle.com/javase/tutorial/uiswing/misc/action.html

Related

Add KeyListener to Jframe issue

I have a jframe of which I have made in Netbeans, this jframe is being "launched" by another java class, but for the current question that doesn't matter. What matters is the fact that I can't seem to figure out how to add my key listener to this jframe of mine. I have implemented the key listener, added the required functions (key typed, key pressed and key released). But I can't figure out how to actually add/initiate the actual key listener, to make it work.
As of right now I have tried two different things, first I have tried to add the line addKeylistener(new JFrameList()); in the start of the code, where the actual jframe is being initiated, but doing so the actual frame won't even show. Apart from this I have tried to add the same line within another function callJframFForm(), which is called from another class at the same time as the jframe is called. But this just returns the error non-static method addKeyListener(KeyListener) cannot be referenced from a static context. I am not sure what other ways I could add the key listener and thus am looking for a little help.
Currently my code looks like the one below.
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class JFrameList extends javax.swing.JFrame implements KeyListener{
public static String keyPresCod = "";
public JFrameList() {
initComponents();
addKeyListener(new JFrameList()); //This is where I am currently trying to call from, but frame won't show
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new JFrameList().setVisible(true);
}
});
}
// Variables declaration - do not modify
private javax.swing.JButton jButton1;
private javax.swing.JButton jButton2;
private javax.swing.JLabel jLabel1;
private javax.swing.JLabel jLabel2;
// End of variables declaration
static void callJframFForm(){
try {
//This is where I have also tried to add the initialization line
} catch(Exception e) {
e.printStackTrace();
}
}
#Override
public void keyTyped(KeyEvent e) {
int codeFKey = e.getKeyCode();
if (codeFKey == KeyEvent.VK_A) {
System.out.println("Button A clicked");
keyPresCod = "A";
} else if (codeFKey == KeyEvent.VK_B) {
System.out.println("Button B clicked");
keyPresCod = "B";
} else {
System.out.println("Different key pressed");
keyPresCod = "Another key";
}
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
Problem
addKeyListener(new JFrameList())
This creates a new JFrameList object and uses it's listener. This means any keystrokes are being stored in the new object's member. To see the results, you would have to do
JFrameList list = new JFrameList();
addKeyListener(list);
//use list variable to access keyPressed code
Of course this isn't the behavior you want. You want the keys strokes to be stored in the current instance, not a new object. This means you should be doing
addKeyListener(this)
Although you may notice the listener only works "sometimes", or maybe not at all depending on how you're testing it.
Swing uses a focus system to manage which listeners should be receiving events, and since you are adding the listener to a JFrame, the listener will receive events only when the frame is in focus.
Solution
You should use key bindings rather than a key listener.
If you choose to continue using the listener, you should add it to your buttons, not your frame:
jButton1.addKeyListener(this);
jButton2.addKeyListener(this);
Instead of checking the key code of the event, you could grab the source of the event (your button) by calling event.getSource().
Key bindings allows you to set flexible focus settings for your components. All you need to do is access the input map of the component:
String actionCommand = "Press Button A";
jButton1.setActionCommand(actionCommand);
jButton1.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("A"), actionCommand);
jButton1.getActionMap(actionCommand, this);
Your JFrameList should now implement ActionListener rather than KeyListener, as it will be receiving your events as actions:
class JFrameList extends JFrame implements ActionListener {
private JButton jButton1;
public JFrameList() {
jButton1 = new JButton("A");
//set action command, add to input map, add to action map
}
public void actionPerformed(ActionEvent event) {
JButton button = (JButton) event.getSource();
System.out.println(button.getActionCommand() + " was performed.");
}
}
Alternative
JButton has built-in mnemonic handling. You can specify a mnemonic via JButton#setMnemonic(int), where the argument is a key code:
jButton1.setMnemonic(KeyEvent.VK_A);
This is the standard way of handling hotkeys in graphical interfaces. Simply hold down the Alt key (windows) then press the key you set the mnemonic to.
Key Events are only dispatched to the component with focus. You didn't post the entire code but I'm guessing that focus is on the button that you add to the frame, so the button gets the KeyEvent.
Not sure what you are trying to do with the KeyListener. You can't tell which button was clicked by looking at the character typed in the KeyEvent.
If you want to know what button is clicked then you need to add an ActionListener to each button. Read the section from the Swing tutorial on How to Use Buttons for more information and examples.

Trying to get one mouse listener action to stop before another one begins

Not sure how well I will explain this; I'm quite new to programming...
So I'm trying to make a desktop application that draws musical notes of a given type on some sheet music when the user selects the button corresponding to that type of note. Currently, if the user selects the "Whole Note" button, the user can then start clicking on the screen and the note will be drawn where the click occurred. It will also make a "ding" sound and write some info about that note to a text file.
That's all well and good, but unfortunately when the user selects a new button, say the "Quarter Note" button, for each mouse click there will be two notes drawn (one whole, one quarter), two dings, and two packets of info written to the file. I have no idea how to make this work! Currently, I'm trying to use threads, such that each button creates a new thread and the thread currently in use is interrupted when a new button is pressed, but that doesn't resolve the issue.
Initially, an empty linked list of threads ("NoteThreads") is constructed. Also, a private class known as SheetMusicPane (given the variable name "smp") is constructed in order to draw the sheet music. The buttons are added in the main constructor (public CompositionStudio), whereas the method containing the mouse listener (see what follows) is contained in the SheetMusicPane private class. Not sure whether that is part of the problem.
I have a button action listener:
button.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!threads.isEmpty()) {
NoteThread oldThread = threads.remove();
oldThread.interrupt();
}
NoteThread newThread = new NoteThread(e.getActionCommand());
threads.add(newThread);
newThread.run();
}
});
that produces a thread:
private class NoteThread extends Thread {
private String note;
public NoteThread(String note) {
this.note = note;
}
public void run() {
smp.getShape(smp.getGraphics(), note);
}
}
that when, on running, calls this method with graphics and a mouse listener:
public void getShape(final Graphics g, final String note) {
this.addMouseListener(new MouseListener() {
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mouseClicked(MouseEvent e) {
Point p = MouseInfo.getPointerInfo().getLocation();
addShape(g, p.x, p.y, note);
int pitch = 12;
piano.playNote(pitch);
advance(1.0, piano);
try { addToFile(pitch, note);}
catch(FileNotFoundException fnfe) {}
catch(IOException ioe) {}
}
});
}
The above method is responsible for drawing the note ("addShape()"), making the ding sound, and writing to the file.
Thanks in advance for any help you can give!
what you're trying to do does not require multithreading. This is the approach that I'd take:
set up a set of toggle buttons or radio buttons to select the note to paint. this way, only one note will be selected at a time. add action listeners to those that store in an adequately scoped variable what note is selected, or infer that every time a note should be drawn. this way, you don't even add action listeners to the buttons. in any case, don't spawn new threads.
in your mouse listener, find out what note to draw, and do that - only one note.
if you can, stay away from multithreading, especially as a beginner. also, I think you confuse adding and running listeners here. each call to getShape() adds a new listener, meaning they accumulate over time, which might be the cause of your problems.
PS: welcome to stackoverflow! your question contained the important information and I could infer that you tried solving the problem yourself. It's pleasant to answer such questions!
One solution would be to simply fetch all the listeners (which should be 1) and remove them before adding the new listener:
public void getShape(final Graphics g, final String note) {
MouseListener[] listeners = this.getMouseListeners();
for (MouseListener ml : listeners) {
this.removeMouseListener(ml);
}
this.addMouseListener(new MouseListener()...);
}
An alternative, since you have a finite number of buttons, would be to create a finite set of listeners, eg:
private MouseListener wholeNote = new MouseListener()...;
private MouseListener quarterNote = new MouseListener()...;
Create a reference to the "current" listener (private MouseListener current;), and have a means of deciding which listener to use whenever getShape is called (a series of if conditions on the note String would work, although I would prefer some refactoring to use an enum personally). Then you could do something along the lines of:
private MouseListener wholeNote = new MouseListener()...;
private MouseListener quarterNote = new MouseListener()...;
private MouseListener current;
...
public void getShape(final Graphics g, final String note) {
if (current != null) {
this.removeMouseListener(current);
}
if (...) { // note is Whole Note
current = wholeNote;
} else if (...) { // note is Quarter Note
current = quarterNote;
} // etc.
this.addMouseListener(current);
}
Another alternative would be to change your listener so that you only ever need the one, but clicking a button changes a variable which the listener has access to. For example:
// In the listener
public void mouseClicked(MouseEvent e) {
Point p = MouseInfo.getPointerInfo().getLocation();
addShape(g, p.x, p.y, currentNote);
int pitch = 12;
piano.playNote(pitch);
advance(1.0, piano);
try { addToFile(pitch, currentNote);}
catch(FileNotFoundException fnfe) {}
catch(IOException ioe) {}
}
// In the same class
protected String currentNote;
...
public void getShape(final Graphics g, final String note) {
currentNote = note;
}

Can I call ActionPerformed method from an Event Handler class for JButton?

I have a JButton titled "select"
In the class that creates that JButton and other classes, I want to use an if condition with ActionPerformed method.
Something like(pseudo-code)
if(_selectListener.actionPerformed(ActionEvent)) { //i.e., if select Button is clicked,
//do something
}
Is this possible?
I want to call this method because I have to handle a situation in which a player should be able to choose something by clicking "select" button, or another "scroll" button, and I want to control it using something similar to a bunch of if statements like the one above.
If it is possible, what is the syntax for it? What is the argument ActionEvent?
Thank you!
The easiest and cleanest way is to add a dedicated, specific action listener to each button. That way, when the actionPerformed() method is called, you're sure that the associated button has been clicked, and don't need to test which button has been clicked:
selectButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// handle click on select button
}
});
scrollButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// handle click on scroll button
}
});
Another way is to use a common ActionListener, and use the getSource() method of ActionEvent to know which component triggered the event. Compare the result with each potential button to determine which is the one that has been clicked:
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == selectButton) {
// handle click on select button
}
else if (e.getSource() == scrollButton) {
// handle click on scroll button
}
}
What is the argument ActionEvent?
The answer is in the documentation. Read it.
no you cant call, if needs boolean expression/value, but this method returns void.

Java ActionListener buttonPress() restriction

Is there a way to restrict this button to only being impressed once? The reason I ask is because for some reasons every time the button is pressed it disrupts the rest of my code. So in effort to save a massive amount of time debugging, it would be much easier to just somehow restrict the number of times it can be pressed. Thanks in advance.
ActionListener pushButton = new buttonPress();
start.addActionListener(pushButton);
To prevent clicking a button you can use JButton.setEnabled(false). So you could do this as the first statement in your ActionListener.
An alternative would be to set a flag in your ActionListener like so:
final ActionListener pushButton = new ActionListener()
{
private boolean clicked;
public void actionPerformed(final ActionEvent e)
{
if(clicked)
{
JOptionPane.showMessageDialog(null, "Action already started");
return;
}
clicked = true;
// ... rest of the action to do ...
}
}
Note that you should not execute long running tasks in your event handler, see design considerations to keep in mind when implementing event handlers in The Java Tutorials.

Setting a key-binding to perform the same action as in my action listener

I have a JButton that's attached to an ActionListener, but I also wanted to add a shortcut key to the button to be more user-friendly. Say, the user can click the button and the program performs some function "f" or the user can also press "Enter" on the keyboard to perform the same function f. So here's what the gist of my code looks like
private JButton button;
public static void main(String[] args){
Action buttonListener = new AbstractAction() {
public void actionPerformed(ActionEvent e) {
//Perform function f
}
};
button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("ENTER"),
"test");
button.getActionMap().put("test",
buttonListener);
button.addActionListener(new OtherListener());
}
private class OtherListener implements ActionListener{
public void actionPerformed(ActionEvent e){
//Perform function f
}
}
Seems a bit tedious having to add an Action and an ActionListener to do the same thing. Maybe I'm not seeing it, but is there a way to cut the code down so I can eliminate the Action and just use the actionListener? I was thinking switching the buttonListener parameter in the getActionMap().put() method to but the method only takes Action types.
Action extends ActionListener, so you should be able to define a single Action and use it wherever you need an ActionListener.
e.g.
public static void main(String[] args){
Action buttonListener = new Action() {
public void actionPerformed(ActionEvent e) {
//Perform function f
}
};
button.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
.put(KeyStroke.getKeyStroke("ENTER"), "test");
button.getActionMap().put("test", buttonListener);
button.addActionListener(buttonListener);
}
JRootPane has a method setDefaultButton(...) that will do what you want. You will need to get the root pane from the top-level container, then you can call this method passing a reference to your JButton, and it will perform its action when enter is pressed on the GUI. And this makes sense when you think about it as "enter" is a special key, one whose behavior should be the responsibility of the GUI, not a single button.

Categories

Resources