JavaFX Erratic behavior with Node.setOnMouseEntered/Exited - java

In my JavaFX project, I have a ModifiedTreeCell class which extends TreeCell. In that class, I have two functions which listen to whether the mouse enters or exits the TreeCell. However, these methods appear to be horrendously unreliable.
Either:
A) They don't detect the mouse entering at all and no popup occurs.
B) They keep calling ENTERED and EXITED back to back forever, so the popup flashes over and over.
C) They will detect an Enter and then immediately detect an Exit even though the mouse is still well within the boundary of the Label.
How can I fix this?
If you'd like to see what I mean first hand, I compiled a runnable Jar file of my project:
Here is the repo. Once you download it, run D2BT.jar and make a new account, a new character, then add an item like Ring, click Magic from the ChoiceBox, and then click the Add Item Button. Go to the View Account Tree Tab to and then hover over one of the Label(s) in Blue.
Okay in the image below, I know my mouse cursor looks like it's on the edge of the Label but I'm telling you if you tried it yourself it doesn't really matter where your mouse is. The setOnMouseEntered/Exited commands simply don't work properly. I know it may seem like it's my ItemFrame Popup causing the problem but I assure you that it will occur even if you comment out the frame.show(); and frame.hide(); methods shown in the image.
Here is the ModifiedTreeCell class:
package application;
import javafx.scene.control.Label;
import javafx.scene.control.TreeCell;
import logic.Item;
/**
* This TreeCell object is for the TreeCellFactory
* function in the GUI class. The TreeCellFactory
* function will convert TreeItem objects into
* ModifiedTreeCell objects which display the
* necesary information to the user, such as
* a Label displaying either the Account name,
* Character name, or Item in a readable fashion.
* #author Kevin
*
*/
public class ModifiedTreeCell extends TreeCell<TreeViewable>
{
/**
* This Label will display the neccesary text related to the item.
* The Label may have a `on Mouse Hover` listener which will display
* a ItemFrame_Animated IF the TreeViewable object claims it to be an item.
*/
private Label displayProperty;
#Override public void updateItem(TreeViewable value, boolean empty)
{
if (!empty)
{
setEditable(true);
// Create the label for this cell.
displayProperty = new Label(value.getTextProperty());
displayProperty.getStyleClass().clear();
// If Value is an Item object...
if (value instanceof Item)
{
// Item Reference.
Item ref = (Item) value;
// Create a Popup window of the item hovered over.
ItemFrame_Animated frame = new ItemFrame_Animated(ref);
displayProperty.setFont(GUI.diabloFont);
displayProperty.setTextFill(ref.getQuality().getColor());
displayProperty.setOnMouseEntered(e ->
{
System.out.println("MOUSE ENTERED THIS LABEL");
frame.show(GUI.window);
});
displayProperty.setOnMouseExited(e ->
{
System.out.println("MOUSE EXITED THIS LABEL");
frame.hide();
});
}
displayProperty.setStyle(GUI.DEBUG_PANE_BORDER);
// Display a Node for this cell.
setGraphic(displayProperty);
}
else
{
// No cell to be displayed, set to null.
setText("");
setGraphic(null);
}
}
}

I finally succeded compiling your project.
Result is no surprise -> by commenting the frame.hide() line, the popup stays on. A solution is given below at 3.
Now some points you can apply to get a super duper app that will not become a mess very quickly.
Get a nice build architecture for your Java project. You can do this by organizing it as a Maven Project. This is just a suggestion, the point is that you should be very confident by fetching your resource files for instance (in your project, you do it by at least 2 or 3 different methods, using File class and getResource(String path) method.
Think more about your architecture before start coding. I am quite sure you did'nt think about any pattern or any idioma before. Think about how you can split your app in multiples mini-API that handle a specific part of logic. Moreover, the gap between your logic and the GUI should be moremore larger to avoid spaghetti code.
Read more tutorials about JavaFX. What you are trying to achieve with a Popup already exists and is called a Tooltip, you already use it in your app and it is as customizable as any other JavaFX component.
I hope you will succeed in a very usefull App for all Diablo2 gamers! :)

Related

How do action events work with players' turns, need help showing which card the user played before the computer shows what card it played

Im creating an uno game and it is almost fully functional and i'm using javaFX to create it. It is Computer vs User and the problem is that when a user clicks on a card button, the middle card is supposed to be replaced with that card and then the computer goes, however what ends up happening is that the computer goes right away and so you only see the computer's card played in the middle. I am sure this has something to do with how i structured my code specifically the action event pertaining to the button clicked. However, since this is my first ever java GUI and i am fairly new to java i am struggling to understand how to structure it so that i am able to show the user's move. Also due to this, i am unable to implement a skip card even though i have tried using a boolean and creating another method i think the main problem lies within how the main gameloop and the actionevent inside is set up.
This method is called and it checks for a button click and then gets the index of the button in the player's arraylist/hand/ The boolean was my attempt at implementing a skip card but what kept happening is the computer would always play right after the user's move when a skip card was played.
public void gameLoop(){
for(int i =0; i<buttonList.size();i++){
buttonList.get(i).setOnAction((ActionEvent)->{
indexOfHandButton = buttonList.indexOf((Button)ActionEvent.getSource());
humanMakeMove();
if(!isReverseTurn()) {
computerMakeMove();
}
});
}
}
This method used the index given from the gameloop actionevent and makes the actual move in the backend. It then clears the hbox containing the users hands and calls showhumanHand which fills it up with the updated cards and similarily with the middle card. The if statements to try and implement the skip cards have not worked, i have also tried creating a new action even inside the if statement and creating a method or recursively calling humanMakeMove but of course the index will be the same and will cause an error.
public void humanMakeMove() {
String result = human.makeMove(game, theDeck, indexOfHandButton, computer);
if(result.equals("skip")){
setReverseTurn(true);
}
hbHumanHandBtn.getChildren().clear();
hbMiddleCard.getChildren().remove(middle);
middle = generateMiddleCard();
hbMiddleCard.getChildren().add(middle);
showHumanHand();
System.out.println(">>>>>>>>>>>>>>>>>>>>>>>" + buttonList);
System.out.println(result);
if (middleCard.getType() != null) {
if (middleCard.getType().equals("skip") && isReverseTurn()) {
System.out.println(isReverseTurn());
gameLoop();
setReverseTurn(false);
}
}
}
So my main question is how can i restructure this or add something that allows for the user's card to show in the middle before the computer goes right away, i have tried a timer/sleep method too. And my skip card doesnt work but i think that has to do with how i set up my action listener which is messing up two problems. Thank you for reading this and let me know if you have any input!

GWT widget that has Button and text?

I want to create a GWT widget that has a rectangle div element (looks like a button) along with some text to the right of that. I want to be able to select this widget so that when I click on either the div/button or the text, the entire widget is selected.
Any suggestions on implementing this? If there was an easy way to nest multiple items, like a Button and a Label, inside of a GWT Button, that would probably work. I am thinking of creating a class that extends Composite and holds a Button and a Label, but I am not sure how to make both of those be selected when my entire new widget is selected.
Do you know how to draw what you are trying to make in plain HTML?
If so, you can simply subclass Widget, and create the elements required, along with the specific styling - there is no need to build multiple other widgets. Then, just add a click handler on your new widget, and you'll know when anyone clicks on any of the content inside the widget.
Likewise, if you use a HTMLPanel (wrapped, as you say, in a Composite) or something to contain both a Label and a Button, you could add a click handler to the panel, and you would know when either of the other widgets were clicked on, since the click event will "bubble" - if nothing prevents it, it will keep firing on each element, then its parent, so that everything knows about the click. However, nesting widgets will make your new widget somewhat more expensive to create than simple creating the HTML, so be sure this is what you would prefer to do.
In either case, you'll use Widget.addDomHandler(...) to actually add the click handler. To make life easier, you can create a new method, implementing HasClickHandlers, and declaring the new method like this:
#Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
As requested, a very short (more comments than code) example widget that seems to do what is described:
public class ButtonWithText extends Widget implements HasClickHandlers {
public ButtonWithText(String text, String buttonLabel) {
// Start with a <div></div> wrapper
setElement(Document.get().createDivElement());
// Insert a <span> for the text, allowing us to change the text later
Element textElement = Document.get().createSpanElement();
textElement.setInnerText(text);
// Create a button element as well
Element buttonElement = Document.get().createButtonElement();
buttonElement.setInnerText(buttonLabel);
// Attach both to the div we already created
getElement().appendChild(textElement);
getElement().appendChild(buttonElement);
// Note that we could have done all of the above with SafeHtmlTemplate
// to let us write a string, or could do the escaping ourselves and just
// make a simple HTML string in code. I find making DOM elements like
// this easier to read, but many people prefer the template approach.
// When considering a template tool, also look into the new library
// Elemento to see what it offers.
}
#Override
public HandlerRegistration addClickHandler(ClickHandler handler) {
return addDomHandler(handler, ClickEvent.getType());
}
}
Here is the project, running for you to see it https://viola.colinalworth.com/proj/54177c9456d1c4f777d17dc660005643/project/client/SampleEntryPoint.java - you can click either on the text, or on the button, and the popup appears as expected.

JList - locationToIndex() always returning the same index for mouse location in thread

I'm testing around a bit with JLists and I wanted a little window to pop up on MouseEnter and show the content of the list entry the mouse was hovering over; also the window disposes on MouseExit.
So far this worked great, but for it to update the mouse has to exit and re-enter.
Now I wanted to make it work in a parallel Thread and update the window all the time, so it would change without me having to exit first, and tried this:
public void run() {
while (!Thread.interrupted()) {
Point p = MouseInfo.getPointerInfo().getLocation();
String text = list.getModel().getElementAt(
list.locationToIndex(p));
lab.setText(text);
}
But it always only shows the content of the last entry in the list, no matter where the mouse is.
If I print out textit's always the same content, even though the mouse position changes.
What am I doing wrong?
MouseInfo.getPointerInfo().getLocation() gives the location of the mouse within the context of the screen.
JList#locationToIndex expects the point to be within the context of the JList.
This can be a little confusing at first, but, basically, when dealing with functionality like this, components except the point to be within their coordinate context, where 0x0 is the top, left corner of the component. This actually makes life a lot easier, as you don't constantly need to be translating between screen and component coordinates...
You will need to first convert the Point to component coordinates...
SwingUtilities.convertPointFromScreen(p, list);
Then request the index of represented by the point
Also note that Swing is not thread safe, that means that you should only ever access or modify UI components from within the context of the Event Dispatching Thread.
So this makes...
String text = list.getModel().getElementAt(
list.locationToIndex(p));
lab.setText(text);
...very dangerous.
See Concurrency in Swing for more details
Without knowning exactly what you're trying to do it's difficult to provide alternative suggestions, but you might consider using a MouseListener and/or MouseMotionListener, registered to the JList, instead
The immediate benefit of this is that the MouseEvent is already converted to the coordinate space of the component that triggered the event
Use this code instead yours code
Point p = MouseInfo.getPointerInfo().getLocation();
list.setSelectedIndex(list.locationToIndex(p));
String text = list.getSelectedValue().toString();
lab.setText(text);
private void JLISTE_clicked(java.awt.event.MouseEvent evt) {
if (SwingUtilities.isRightMouseButton(evt)){
if (evt.getComponent()==JLISTE){
int idx = JLISTE.locationToIndex(evt.getPoint());
System.out.println("Index rightclicked : " + idx); // testing
JLISTE.setSelectedIndex(idx);
}
}
....
}

Adding components in GUI upon repaint method

I'm not understanding Java GUI's as well as I thought. In my paint method for a frame, I'd like to wipe all of the current buttons, and add new ones. (The overall goal is to have an interface where the user can see characters and click on the buttons to download documents related to the character. Since every character is different, when the user selects a new user from my list, a new set of documents and buttons will be available to them.)
This is a test frame that I just wrote that shows where things go sideways. It has the similar paradigms that I use in my actual program, without too much clutter:
public class GUITest extends JFrame
{
/**
* #param args
*/
public static void main(String[] args)
{
Container gui_test = new GUITest();
}
private JComponent content = null;
public GUITest()
{
super();
setVisible(true);
}
public void paint(Graphics g)
{
this.removeAll();
content = new JPanel();
JComponent test_button = new JButton("New Button 1");
JComponent button = new JButton("New Button 2");
content.add(button);
content.add(test_button);
this.add(content);
super.paint(g);
}
}
Without the call to removeAll(), buttons will continue to be thrown on top of the JPanel, but with the call, nothing shows up. I don't know why this is, as I'm adding the components appropriately, right?
Edit
Got it, let me give you a more detailed breakdown. A client is navigating my program by looking at a list of characters in a game on a west panel. They can select a row from the list which will show char details on the east panel. The details are an image and description. Recently, I added relevant documents for that particular char, which will show on the bottom of the east panel. I created key listener's, so the client can quickly view the document by pressing a num key, but I also want to give them the ability to click on the button to launch a pdf view and see the contents of the document.
Since every char has different related docs and different number of docs, I repainted the buttons every time, to reflect the amount of related docs and the appropriate titles for the docs. This is where the repaint is acting strange. You gave me a good explanation of what's going wrong, but I don't know how to give the client access to the docs now, aside from painting a description of the doc along with the hot key needed to launch it. Does that make sense?
Never add components to your GUI or remove components in the paint or paintComponent methods. Just don't do it. Ever. Period.
These methods are for drawing only, and need to be as fast as possible, else your program will appear unresponsive. Not only that, you do not have full control over when or even if these methods will be called, so program logic and structure should not go into these methods.
Instead react to user events with event listeners such as ActionListeners, ListSelectionListeners, or with key bindings.
Edit
Regarding
Got it, let me give you a more detailed breakdown. A client is navigating my program by looking at a list of characters in a game on a west panel. They can select a row from the list which will show char details on the east panel. The details are an image and description. Recently, I added relevant documents for that particular char, which will show on the bottom of the east panel. I created key listener's, so the client can quickly view the document by pressing a num key, but I also want to give them the ability to click on the button to launch a pdf view and see the contents of the document.
I'd use a JList to hold the list of selectable information on the left, and would react to it with a ListSelectionListener. In the listener, I'd change the related displayed information. I also avoid using KeyListeners with Swing but instead gravitate towards Key Bindings as they're more flexible and less rigid regarding focus.
Regarding
Since every char has different related docs and different number of docs, I repainted the buttons every time, to reflect the amount of related docs and the appropriate titles for the docs. This is where the repaint is acting strange. You gave me a good explanation of what's going wrong, but I don't know how to give the client access to the docs now, aside from painting a description of the doc along with the hot key needed to launch it. Does that make sense?
I'm not sure what you're doing here or what you're trying to do.
Since every char has different related docs and different number of docs, I repainted the buttons every time, to reflect the amount of related docs and the appropriate titles for the docs. This is where the repaint is acting strange. You gave me a good explanation of what's going wrong, but I don't know how to give the client access to the docs now, aside from painting a description of the doc along with the hot key needed to launch it. Does that make sense?
So rather then "painting" the buttons, why not just change there text (setText(...)).
When a user selects a "char". You are going to need to rebuild portions of your screen. Change the list model (as suggest above) and remove/add any buttons you need on the document container.

Java JTree valueChanged Event before MouseEvent

Here's the situation, I have a jFrame with a tabbed pane and within the tabs I have a couple of jTables and a jTree. I want to be able to chain the selections between the tables and the tree based on whether a user uses a ctrl/shift + click versus a regular click. (If you hold ctrl and click in the first table/tree, it adds to the overall selection, if you use a regular click it clears the selections in the other tables/tree). Currently I'm having an issue with Java's jTree component. I have added a TreeSelectionListener and a MouseListener with a class that implements both interfaces, call it MyBigListener;
i.e.
MyBigListener listener = new MyBigListener();
jTree1.addMouseListener( listener );
jTree1.addTreeSelectionListener( listener );
MyBigListener implements TreeSelectionListener, MouseListener {
private boolean chained = false;
public synchronized setChained(boolean ch){
chained = ch;
}
public synchronized boolean isChained(){
return chained
}
public void valueChanged(TreeSelectionEvent e){
if(isChained()){ blah... }
}
public void mousePressed(MouseEvent e){
setChained(e.isControlDown() || e.isShiftDown());
}
}
My plan was to set a boolean flag if the user uses a ctrl/shift + click that I could check during the valueChanged(TreeSelectionEvent e) implemented by the tree selection listener.
I want to be able to process the mouse events before the valueChanged TreeSelectionEvents, but the problem is that I receive the mouse events after the valueChanged treeSelection event. This seems weird to me that I receive the selection change before the mouse pressed event fires, when the selection change is actually initiated by the mouse pressed. (I've already synchronized the boolean flag setting, which ironically helped to highlight the mis-order of events.)
I've already tried alternatives like adding a keyListener, but this doesn't work when the focus is on a separate frame, which goofs me up when a user holds ctrl and then clicks into the jTree causing it to receive both the focus and fire any valueChanged selection events.
Any help would be appreciated, thanks!
--EDIT-- #akf
I have separate jTables and jTrees in a tabbed Pane that serve as a summary/control panel for data in a nodal-graph. I'm using these components in the tabbed Pane to do coordinated selection to a graph displayed in a separate jFrame. Individually each table works just fine for its selection as does the jTree. It's coordinating between the panes that's tricky. This works fine so far with jTable components because I fire the new selections as the result of a MouseEvent where I can tell if the shift/ctrl button was down, formulate my new selection, and pass it to the parent frame which coordinates selections between all panes and sends the final selection to the graph. However, the parent needs to know if it needs to chain a selection between panes, or squash the others. With the jTables it's fine again, because I fire selection changes as the result of a mouse click. The jTree is more of a problem because I'm doing some forced selections. If you click on a branch, it forces all leaves into the selection. I needed to implement a TreeSelectionListener to do that, but only get a valueChanged(TreeSelectionEvent) to realized changes. I added a mouseListener to listen for ctrl+clicks and shift+clicks, but apparently the events don't always happen in the same order.. at least so far I receive the valueChanged event before the mousePressed event, so checking to if a ctrl+click happened posts after the selection has already been modified.
Right now, I'm posting a pending selection change and then have the MouseListener grab that and send it up the chain, but if these events aren't guaranteed to happen in the same order, at some point it's going to fail. Implementing a delayer also rubs me the wrong way.
Thanks for the help so far.
--EDIT2-- #ykaganovich
I think overriding the fireValueChanged method is closer to the right way to go about things. Depending on my definition of what actions should cause a "chained" selection to the other components, I'd need to gather some context on what's going on before the valuedChanged method fires. This basically means calling it myself in all cases where I can define what it means by who triggers it. I.e. If a mouse event causes it and ctrl is down then set what I need to set (interpret) then fire. If it's going to change due to a keyboard event, again, set what I need to set, then fire. I don't think the TreeSelectionModel is the way to go, because I still won't know what the circumstances were when the event fired. I think this means I'll need to rewrite parts of the jTree to do this but I guess I'll see how it goes. Thanks.
Don't do it that way, override JTree.fireValueChanged instead.
Try something like this (untested):
class ChainedSelectionEvent extends TreeSelectionEvent {
ChainedSelectionEvent(TreeSelectionEvent e) {
super(e.newSource, e.paths, e.areNew, e.oldLeadSelectionPath, e.newLeadSelectionPath);
}
}
protected void fireValueChanged(TreeSelectionEvent e) {
if(chained) { // figure out separately
super.fireValueChanged(new ChainedSelectionEvent(e));
} else {
super.fireValueChanged(e);
}
}
Then check instanceof ChainedSelectionEvent in your listener
EDIT
Actually, I think the right way to do this is to implement your own TreeSelectionModel, and override fireValueChanged there instead. Assuming setSelectionPath(s) methods imply a new selection, and add/removeSelectionPath(s) imply chaining, you could distinguish between the two cleanly. I don't like listening to either keyboard or mouse events explicitly, because there's more than one way to change a selection (e.g. if someone is holding down SHIFT and hitting a down-arrow, you won't get a mouse event).
You may get the mouse event before or after the tree selection event. Best not to rely on such orders. The reason is that the tree selection event is caused in response to the mouse event. Is that mouse event listener called before or after your listener? Could be either.
This sort of thing is closely involved with the implementation of the PL&F.
the key here is to understand that a JTree is delivered with a BasicTreeUI.MouseHandler, see javax.swing.plaf.basic.BasicTreeUI.
to answer the central question, "what fires fireValueChanged", the answer is "this mouse handler"... which turns out to be the only mouse listener returned when you go tree.getMouseListeners().
So you have to replace this default mouse listener with your own version ... which is a little tricky. I use Jython, which everyone needs to discover. This code shouldn't be too difficult to translate into Java however:
from javax.swing.plaf.basic import BasicTreeUI
def makeMouseHandlerClass():
class MouseHandlerClass( BasicTreeUI.MouseHandler ):
def mousePressed( self, mouseEvent ):
genLog.info( "mouse handler MOUSE PRESSED!" )
nTFSelf.mousePressedStatus = True
BasicTreeUI.MouseHandler.mousePressed( self, mouseEvent )
nTFSelf.mousePressedStatus = False
return MouseHandlerClass
suppliedMouseHandler = nTFSelf.taskTree.mouseListeners[ 0 ]
nTFSelf.taskTree.removeMouseListener( suppliedMouseHandler )
nTFSelf.taskTree.addMouseListener( makeMouseHandlerClass()( nTFSelf.taskTree.getUI() ))
... basically the equivalent Java here would be to extend BasicTreeUI.MouseHandler as MyMouseHandler, and then in the last line replace with new MyMouseHandler( tree.getUI() )... "nTFSelf" here is merely a reference to the "this" object of the code where all this is being written...

Categories

Resources