MouseListener across several panels? - java

My user story is the following:
In order to upgrade a tower, the player has to drag a gun from the shop and drop it on the tower.
In practice, the GameView contains both the BattlefieldCanvas and the ShopView. Besides, the ShopView contains a GunSelector for each buyable gun. All those guys are sub-classes of JPanel.
I'm currently using a MousListener to handle several actions performed on the BattlefieldCanvas; I thought I could use the same stuff to handle a mouse trip from one of the GunSelector to the BattlefieldCanvas ( = across several panels ), so I tried to add the same MousListener to the gun selectors and the battlefield.
Problem: doesn't work. The getSource() method of the event object returns a reference to the gun selector while the mouse is actually released on the battlefield.
PS: Unlike gun selectors, towers are not swing components but images drawn by the paintComponent method.

1) To answer to your original question as to why source is still the component mouse was clicked on. You just need to read the JavaDoc:
public Object getSource()
The object on which the Event initially occurred.
Returns:
The object on which the Event initially occurred.
2) Now, how can we get the actual component that mouse is released on. You can try this approach:
#Override
public void mouseReleased(MouseEvent e) {
Component theCOmponentMouseIsReleasedOn = frame.findComponentAt( e.getLocationOnScreen() );
}
You don't need to always call findComponentAt on frame, you can call on the container that contains your BattlefieldCanvas.

Related

Extending Swing's ToolTipManager to change behaviour on hover?

I'd like to implement a ToolTip in Swing that has customised behaviour: the longer the user hovers over the component, the more detail should be shown in the tooltip (i.e., a few new lines are added after a few seconds of the user hovering over the component). I just need to check whether this is really doable with Swing without things getting too messy. My idea at the moment would probably be:
Extend ToolTipManager
Override mouseEntered to start a timer (maybe use javax.swing.Timer?). Call setToolTipText and createToolTip to refresh the tooltip and add new information at regular intervals
Override mouseExited to reset the timer
Probably use setDismissDelay to set the dismiss delay to something a lot longer (or Integer.MAX_VALUE)
Is such a thing feasible or is this not a good way to work with Swing (sorry, I'm pretty new to it)? Is there a better way of doing this?
[edit] Hmm, just remembered that ToolTipManager is a singleton with a constructor that only has package visibility, so it can't be extended.
[edit 2] I'm trying out a few solutions at the moment. One thing that I forgot to add is that I do need to know which component is being hovered over - which I guess means I'll need to be working with some sort of listener with a mouseEntered() method (or be able to access this information). And no other interactivity with the popup/tooltip is needed - it just needs to display information.
(This may seem a bit confusing so let me know if you need me to clarify let me know and I'll try to show you how I picture the code) I think your idea might work like if you extend it, and also make a private class that extends Threadand then in the run() method you do something like
while(true)
{
sleep(1);
timeElapsed++;
}
And in your class that extends ToolTipManager, create a field for that class that extends Thread and in the mouseEntered(MouseEvent e) instantiate the thing like:
extendsThreadClass = new ExtendsThreadClass();
extendsThreadClass.start();
and then in the mouseExited(MouseEvent e) method do
extendsThreadClass = null;
Then in that mouseEntered(MouseEvent e) method after starting the Thread then you can do what you want to do after the time thing like
if(timeElapsed > 3000)
{
//what you want to do here
}
Sorry it may be confusing, let me know if i can clear it up for you
I thought I should update this with the approach I took before I saw l1zZY's answer (which I think is the better way of doing things - I still had bugs in my code before I moved onto something else, but this might still be helpful to someone). This is what I did:
Extend JToolTip
Use a Swing Timer for timing
Add a MouseMotion listener to the JTree (in my case I wanted the popup to show when a node was hovered over)
Somewhat inelegantly, detect when the mouse moved over a tree node like this:
public void mouseMoved(MouseEvent e) {
int x = (int) e.getX();
int y = (int) e.getY();
TreePath path = getPathForLocation(x, y);
if (path == null) {
tooltip.hide();
} else {
TreeNode node = (TreeNode) path.getLastPathComponent();
tooltip.setHoveredNode(node);
if (!tooltip.isVisible) {
int absX = e.getXOnScreen();
int absY = e.getYOnScreen();
final Popup tooltipContainer = PopupFactory.getSharedInstance().getPopup(PDTreeView.this,
tooltip, absX, absY);
tooltip.setToolTipContainer(tooltipContainer);
tooltip.show();
}
}
}
tooltip.show() refers to how the tooltip was contained in a Popup
in order to show or hide it programmatically. show() shows the
Popup (and therefore tooltip) and also starts the Swing timer.
Timer has a method called actionPerformed() which is called at whatever interval you set. I just had that method call the code that adds new information to the tooltip. in hide(), I reset the tooltip and the timer.
I had issues with the popup or tooltip not resizing to fit the content, but otherwise this seemed ok.

Keypressed while a thread is running - how to capture the keys?

I have a JPanel that contains a few other objects that do stuff. I will simplify the example by talking about some circle object (defined by circle class I made), and a square object (similar).
the circle moves randomly around the screen, while the square sits at its place. my intention is to move the square using the arrow buttons.
the current design is to have a thread with a while loop that contains a delay that sets the 'refresh rate' inside its run method.
I'm trying every method I know to capture the arrow keys and move the square around while the ball is running around the screen.
how do I capture keypresses (arrows for the example) so I can know where to move my square to?
I tried implementing keylistener in the jpanel but it didn't work. when I tried to use a KeyEvent in the run, I got an exception.
please save me. :)
EDIT:
Thanks for that info. I would like further help to settle this issue -
lets say I have the following code:
this.getInputMap().put(KeyStroke.getKeyStroke("UP"), "actionName");
this.getInputMap().put(KeyStroke.getKeyStroke("DOWN"), "actionName");
this.getActionMap().put("actionName",
new AbstractAction("actionName") {
public void actionPerformed(ActionEvent evt) {
//dostuff
}
}
);
how do I distinguish between UP and DOWN presses? what do I need to change?
Thanks! I'm a bit of a newbie, I know :)
KeyListener isn't designated for listening in Swing GUI, this Listener was builded for pre_historic AWT Component, these days so far away, use KeyBindings, this example can save your person

capture key press within frame

This seems like a simple behavior, but I'm having difficulty making it happen. I'm working on software which graphs data. I want to redraw the graph when the user hits enter. Well more accurately I want to draw the graph when the user hits enter and doesn't have a text field selected; but for now I'll be satisfied with drawing whenever the user hits enter.
I tried installing a basic KeyListener to the frame first, but that doesn't work since the JFrame children, not the frame, receive the event.
I then tried to use the KeyEventDispatcher, but it's proving too global a concept. I can have two graphs on screen at once, and an old graph can be closed or replaced with a new graph. With the KeyEventDispatcher I have no easy way of knowing which plot I want to activate out of the multiple plots open at a time and the plots currently open now may not be the plots that were opened when I instantiated the key dispatcher. I know this solution could still work, but it requires my storing extra data as to what plot is currently active that doesn't fit well into the program architecture.
It seems as if there should be an easier method to capture any KeyEvents dispatched to a JFrame/JPanel or any of the JFrame's children; but ignore events dispatched to other frames. Preferable a method that can be added to the frame and thus is automatically disposed when the frame is disposed. Can anyone suggest a simpler method then what I've tried?
Don't use a KeyListener.
Add a Key Binding to the graph panel.
This works very well
this.getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "fecharAction");
this.getRootPane().getActionMap().put("fecharAction", new AbstractAction() {
private static final long serialVersionUID = 1L;
#Override
public void actionPerformed(ActionEvent e) {
int resp = JOptionPane.showConfirmDialog(MainForm.this, "Encerrar sistema?", "Confirmação", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE);
if (resp == 0) {
MainForm.this.setVisible(false);
MainForm.this.dispose();
}
}
});

Java Swing: Do something when a component has *finished* resizing

Apologies for the somewhat unclear question - couldn't think of a better way of putting it.
I use a JXTaskPane (from the Swing labs extension API) to display some information.
The user can "click" the title to expand the panel. The JXTaskPane is in a container JPanel, which is then added to a JFrame, my main application window.
I want my application window to resize to the size of the expanded task pane. To achieve this, I added a component listener to my container JPanel which would set size to the now expanded panel.
panel.addComponentListener(new ComponentListener()
{
public void componentResized(ComponentEvent e)
{
Dimension newSize = ((JXTaskPane)e.getSource()).getSize();
reSizeFrame(newSize);
}
}
private void reSizeFrame(Dimension newSize)
{
if ((newSize.height < maxSize.height) && (newSize.width < maxSize.width))
{
containerPanel.setSize(newSize);
appFrame.setSize(containerPanel.getSize());
appFrame.pack();
}
}
The problem is that the componentResized method is called as the task pane expands, as a result the resizeFrame method is called lots of times, and looks really awful on the screen.
How can I detect when the JXTaskpane has finished resizing? I thought of two approaches:
Put the resizeFrame() method in a SwingUtilities.invokeLate(..) call.
Put in a timer resizeFrame call, so any subsequent calls do not do anything until the timer fires. This should give enough time for the panel to resize.
What is the best way forward?
Also - This is my first serious Java GUI app after years of server side program. StackOverflow has been very helpful. So thanks!
I know you've already selected an answer, but overriding the paint method is definitely not correct, and while you may be able to hack something in place, it won't be ideal.
Looking at the source for JXTaskPane and specifically looking in setExpanded() (line 387), you can see it calls JXCollapsiblePane.setCollapsed(...) and then fires a property change event for expanded. A listener on that property won't be correct, because it'll fire before the animation is complete. So, if you go into JXCollapsiblePane and look at setCollapsed(...) (line 470) you'll see that if it's animated, it sets the paramaters and starts a timer. We want to know when the animation ends, so in that file, look at the animator (line 620, and specifically 652-667), which shows that when the animation ends, it fires a property change for ANIMATION_STATE_KEY with a value of "collapsed" or "expanded". This is the event you actually want. However, you don't have access to JXCollapsiblePane, so go back to JXTaskPane and search for ANIMATION_STATE_KEY, and you find line 208, which shows that JXTaskPane creates a listener on JXCollapsiblePane.ANIMATION_STATE_KEY and refires it as it's own event.
Since you do have access to JXTaskPane, you can listen for that event, so doing ...
taskPane.addPropertyChangeListener(JXCollapsiblePane.ANIMATION_STATE_KEY, new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent e) {
if(e.getNewValue().equals("expanded") {
...
}
else if(e.getNewValue().equals("collapsed") {
...
}
}
}
should get your event exactly when you want it.
The correct way to listen for events in Swing is through property listeners. Unfortunately, the only way to find out what the correct properties and values are is by digging through source code.
As a suggestion, have you tried overriding the paint method, first calling super and then putting your resize code at the end of that if (and only if) the size has changed significantly.
I'm not familiar with JXTaskPane, but my first reaction is that maybe you're handling the wrong event. You want the frame to resize when the user clicks on the header - so why not handle that event (perhaps using EventQueue.invokeLater() to resize the frame after the task pane has been resized)?
But if that doesn't work and you need to use the approach you've outlined above, using a javax.swing.Timer is probably best. Set it for 200 milliseconds or so and just restart() it every time componentResized() fires.

Is there a standard way to handle many different options for mouse events in java?

I'm developing a grid based sim game in java, and I was wondering if there is a standard way of doing the following.
I have a panel which is the game panel, and there are many different things which could happen when the panel is clicked. For example, when building a room, there are several stages, where dragging the mouse and left clicking will have different actions.
Right now, the way I have done it, is to use booleans to check what's being built, and then what stage it is at.
Is there any better or standard way of handling something like this? I had a quick google, but as I have said before, people on Stack Overflow always give a better, more relevant, up to date answer.
I consider myself still rather new to java.
Thanks in advance.
You might try looking into something similar to the strategy pattern.
Basically, you start by clicking the room button on your toolbar. The toolbar goes through and tells the grid to use the 'room place' actionlistener. Presumably removing any previous action listener that was listening
The room place actionlistener would in turn implement all the interesting bit of logic for left/right clicking, dragging, etc.
If you have multiple stages to building a room (say, placing doors, then windows, then trap doors); the action listeners would be responsible for handing control off to the next stage: a bit of a finite state machine.
So, start by clicking 'room' button, 'place room' listener is added. Drag out the area you want the room to be, 'place room' modifies the game state, then changes the actionlistener to the 'place windows' listener. Ad infinitum... until you finish.
One very simple (non compilable) example:
class GridPanel extends JPanel
{
void SetMouseListener(MouseListener newListener)
{
for(MouseListener ml : getMouseListeners())
removeMouseListener(ml);
addMouseListener(newListener);
}
}
class ControlPanel extends JPanel
{
GridPanel gameGrid;
void OnRectangleButtonClicked(some stuff)
{
gameGrid.SetMouseListener(new PlaceRoomListener(gameGrid));
}
}
class PlaceRoomListener extends MouseAdapter
{
GridPanel gameGrid;
//constructor, etc
void OnClick(mouse event)
{
gameGrid.doCoolStuff();
gameGrid.SetMouseListener(new PlaceTrapDoorListener());
}
}
//etc
Now, that non-compilable example aside, Pyrolistical does have a point: you generally don't want to combine your game model and graphic interface into one single glob of classes. You want the model separated from the GUI, and to manipulate it through a well defined interface.
(Also, there are probably better methods for going about removing the mouse listener then just randomly removing all listeners... I was in a bit of a rush, sorry)
It sounds like you need to define your game model/state and keep it separate from your mouse actions.
Are you using MVC?

Categories