Handling drag-and-drop with a moving list - java

I'm working with drag-and-drop. The user has to drag something from a list to somewhere else. However, the list will move when receiving a ListSelectionEvent, so when the user changes selection, he may unexpectedly perform a drag-and-drop.
My code:
import java.awt.*;
import javax.swing.*;
class Main {
public static void createGUI() {
JFrame f = new JFrame();
JList<String> list = new JList<>(new String[] { "Text A", "Text B" });
list.setFont(list.getFont().deriveFont(24f));
list.setDragEnabled(true);
// list.setTransferHandler(new TransferHandler() { /* ... */ });
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addListSelectionListener(e -> f.setLocation(f.getX(),
f.getY() + f.getHeight()));
list.setSelectedIndex(0);
f.add(list, BorderLayout.CENTER);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationByPlatform(true);
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(Main::createGUI);
}
}
To reproduce, launch this application, put it above some application that accepts a drop (e.g. Eclipse, Notepad++) and toggle the selection several times.
I'm using Windows 7 and JDK 1.8.0_5.
I tried but I couldn't find a work-around. How can I fix this issue?
[Not really related] This is my real application: (including the green cross icon)

Okay I think I understand your problem: You want to have DnD enabled, just not when the user is changing their selection. You should try this (in Java 7, I'm not too comfortable with lambda expressions yet, so I'm still not using Java 8. It'll work on Java 8 though):
class Main {
private static boolean listChanging = false;
public static void createGUI() {
final JFrame f = new JFrame();
JList<String> list = new JList<String>(new String[] { "Text A", "Text B" });
list.setFont(list.getFont().deriveFont(24f));
list.setDragEnabled(true);
list.setTransferHandler(new TransferHandler() {
private static final long serialVersionUID = 1L;
#Override
public int getSourceActions(JComponent c) {
if (listChanging) {
listChanging = false;
return NONE;
} else {
return COPY;
}
}
#Override
#SuppressWarnings("unchecked")
public Transferable createTransferable(JComponent c) {
return new StringSelection(((JList<String>) c).getSelectedValue());
}
});
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addListSelectionListener(new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
f.setLocation(f.getX(), f.getY() + f.getHeight());
listChanging = true;
}
});
list.setSelectedIndex(0);
f.add(list, BorderLayout.CENTER);
f.pack();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLocationByPlatform(true);
f.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Main.createGUI();
}
});
}
}
What this code does is that when the user when the user changes selection it set a variable listChanging to true. The when the user drags (by accident or on purpose), it checks if listChanging is true, which means that this was probably an unexpected drag. If the list was not changing, then it allows COPY drags.
Basically, if the drag was during a list change, it disables DnD. If the list did not change, and the user purposefully dragged it enables DnD.
Hope this meets all your needs :)

Still It's not clear what you want to do with your application..
Drag and Drop performs with mouse/key events or you may trigger it through another events.
Here as I can see, it could be in this way that either you can select some of the components from List and perform DnD to transfer that component to another Container.
Please elaborate your question and ask specifically what exactly you want to perform with your sample application.

Related

Disable buttons on GUI in Swing

I have buttons on a GUI that execute tests when clicked on in Selenium. They can only be run serially and are currently added to EventQueue. I would like it so that if a button is clicked on and a test is executed, then it will disable the other buttons so that other tests cannot be added to a queue.
Currently a button looks like:
Test1 = new JButton("Test1 ");
Test1.setLocation(290, 30);
Test1.setSize(120, 30);
Test1.addActionListener(this);
Test1.addMouseListener(new MouseAdapter() {
public void mouseClicked(java.awt.event.MouseEvent e) {
if (Test1.isEnabled()) {
Test1.setEnabled(false);
errorLabel.setText("");
service.submit(()->{
Result result = JUnitCore.runClasses(Test1.class);
EventQueue.invokeLater(()->{
errorMessageDisplay(result);
Test1.setEnabled(true);
});
});
}
}
});
buttonPanel.add(Test1);
I have used the EventQueue as it allows me to reset update Pass/Fail error messages on the GUI.
How can I best achieve this?
You should add ActionListener to your button. What's even more important, you should use naming conventions what also means that your objects' names should start with a small letter. Capital letters are reserved for Classes and static fields (all upper case). The following code adds an ActionListener to your JButton and disables it after clicked. If it's not what you're looking for, I'll add another version in a moment.
test1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
test1.setEnabled(false);
}
});
In case, you want to keep the state of your button, but don't disable it, the following code might be a solution:
private final static String ENABLED = "ENABLED";
private final static String DISABLED = "DISABLED";
public static void main(String[] args) {
Map<JButton, String> map = new HashMap<>();
JButton test1 = new JButton();
map.put(test1, ENABLED);
test1.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (map.get(test1).equals(ENABLED)) {
//do something
} else {
//do something else. I'll enable.
map.remove(test1);
map.put(test1, ENABLED);
}
}
});
}

ActionListener call blocks MouseClick event

I have a window with a MenuItem "maddbound3" with the following ActionListener:
maddbound3.addActionListener
(
new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
menu_addbound3();
}
}
);
When the menu is clicked this listener calls menu_addbound3() below:
void menu_addbound3()
{
while(getEditMode() != EditMode.NONE)
{
System.out.println("!... " + getEditMode());
synchronized(this)
{
try
{
wait();
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
}
A MouseClicked event alters the value of the edit mode and issues a notifyAll() so that the while loop should exit. However, tests have shown that when the system is running through the while loop, the MouseClicked event never occurs on clicking the mouse.
Does the ActionListener block the MouseClicked event? How can I resolve this issue?
Thanks
Don't have a while(true) on the Swing event thread, and likewise don't call wait() on the Swing event thread -- you'll freeze the whole GUI making it completely unresponsive. You need to understand that the main Swing event thread or "event dispatch thread" is responsible for all Swing drawing and user interaction, and so if you tie it up with long-running or freezing code, you lock your entire GUI.
Instead, change the state of your program -- perhaps by setting a variable or two, and have the behavior of your program depend on this state. If you need more specific advice, please tell us what behavior you're trying to achieve, and we can perhaps give you a better way of doing it.
For more on the Swing event thread, please read: Lesson: Concurrency in Swing
Edit
You state:
When the user clicks the menu item I want to obtain information via a series of "discrete" mouse clicks from the window. Hence, on clicking the menu, the user would be prompted to "select a point in the window". So, what I need is for my ActionListener function (menu_addbound3) to then wait for a mouse click. Hence the wait/notify setup. A mouse click changes the edit_mode and notifyAll() causes the wait in the while loop to exit which then causes the while loop to exit and I can then prompt for my next bit of information within the menu_addbound3 function, repeating this as as I need to.
Thanks for the clarification, and now I can definitely tell you that you are doing it wrong, that you most definitely do not want to use the while loop or wait or notify. There are many ways to solve this issue, one could be to use some boolean or enum variables to give the program a state and then alter its behavior depending on the state. Your EditMode enum can be used in the MouseListener to let it know that its active, and then you could also give the MouseListener class a boolean variable windowPointSelected, set to false, and then only set it true after the first click has been made.
Edit 2
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.*;
#SuppressWarnings("serial")
public class ProgState extends JPanel {
private static final int PREF_W = 400;
private static final int PREF_H = PREF_W;
private static final Color EDIT_COLOR = Color.red;
private EditMode editMode = EditMode.NONE;
private boolean firstPointSelected = false;
private JMenuBar jMenuBar = new JMenuBar();
private JTextField firstPointField = new JTextField(15);
private JTextField secondPointField = new JTextField(15);
public ProgState() {
add(firstPointField);
add(secondPointField);
JMenu menu = new JMenu("Menu");
menu.add(new JMenuItem(new AbstractAction("Edit") {
#Override
public void actionPerformed(ActionEvent arg0) {
setEditMode(EditMode.EDITING);
setFirstPointSelected(false);
}
}));
jMenuBar.add(menu);
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent mEvt) {
if (getEditMode() == EditMode.EDITING) {
Point p = mEvt.getPoint();
String pStr = String.format("[%d, %d]", p.x, p.y);
if (!isFirstPointSelected()) {
firstPointField.setText(pStr);
setFirstPointSelected(true);
} else {
secondPointField.setText(pStr);
setEditMode(EditMode.NONE);
}
}
}
});
}
public void setEditMode(EditMode editMode) {
this.editMode = editMode;
Color c = editMode == EditMode.NONE ? null : EDIT_COLOR;
setBackground(c);
}
public EditMode getEditMode() {
return editMode;
}
public void setFirstPointSelected(boolean firstPointSelected) {
this.firstPointSelected = firstPointSelected;
}
public boolean isFirstPointSelected() {
return firstPointSelected;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
public JMenuBar getJMenuBar() {
return jMenuBar;
}
private static void createAndShowGui() {
ProgState progState = new ProgState();
JFrame frame = new JFrame("EditMode");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(progState);
frame.setJMenuBar(progState.getJMenuBar());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum EditMode {
NONE, EDITING
}
From the discussion it seems that having your class assume a number of states is the best way to proceed. We can achieve this by one or more enum variables. The reason I found this so hard to grasp initially is that I couldn't see the benefit of having all of ones code in the MouseClicked function. This is ugly and unmanageable at best.
However, using multiple enums and splitting processing into a number of external functions, we do indeed achieve a nice system for what we want.

JComboBox not showing arrow

I have been searching this site and google for a solution to my problem, and I can't find anything. I think it's supposed to just work; however, it doesn't. The arrow icon for my JComboBox doesn't show up, and I can't find anywhere to set its visibility to true.
Here's my code:
public class Driver implements ActionListener {
private JTextField userIDField;
private JTextField[] documentIDField;
private JComboBox repository, environment;
private JButton close, clear, submit;
private JFrame window;
public Driver()
{
window = makeWindow();
makeContents(window);
window.repaint();
}
private JFrame makeWindow()
{
JFrame window = new JFrame("");
window.setSize(500,300);
window.setLocation(50,50);
window.getContentPane().setLayout(null);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
return window;
}
private void makeContents(JFrame w)
{
makeDropDowns(w);
w.repaint();
}
private void makeDropDowns(JFrame w)
{
String[] repositoryArray = {"Click to select", "NSA", "Finance", "Test"};
repository = new JComboBox(repositoryArray);
repository.setSelectedIndex(0);
repository.addActionListener(this);
repository.setSize(150,20);
repository.setLocation(175,165);
repository.setEditable(false);
w.add(repository);
String[] environmentArray = {"Click to select", "Dev", "Test", "Qual"};
environment = new JComboBox(environmentArray);
environment.setSelectedIndex(0);
environment.addActionListener(this);
environment.setSize(150,20);
environment.setLocation(175,195);
//environment.setEditable(false);
w.add(environment,0);
}
public void actionPerformed(ActionEvent e)
{
String repositoryID = "null", environmentID = "null";
if (e.getSource() == repository)
{
repositoryID = (String)repository.getSelectedItem();
}
if(e.getSource() == environment)
{
environmentID = (String)environment.getSelectedItem();
}
}
}
Here's a link to a picture of the problem:
If anyone could help that would be awesome.
It doesn't appear to be the issue you were suffering from, but I found this post due to the same resulting issue of the arrow disappearing.
In my case it was due to me mistakenly using .removeAll() on the JComboBox rather than .removeAllItems() when I was attempting to empty and then reuse the JComboBox after a refresh of the data I was using. Just thought I'd include it as an answer in case someone else comes across this thread for similar reasons.
The code you show works, but it looks like you're fighting the enclosing container's default layout. Here, ComboTest is a JPanel which defaults to FlowLayout.
Addendum: In general, do not use absolute positioning, as shown in your update. I've changed the example to use GridLayout; comment out the setLayout() call to see the default, FlowLayout.
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
/**
* #see https://stackoverflow.com/a/10824504/230513
*/
public class ComboTest extends JPanel {
private JComboBox repository = createCombo(new String[]{
"Click to select", "NSA", "Finance", "Test"});
private JComboBox environment = createCombo(new String[]{
"Click to select", "Dev", "Test", "Qual"});
public ComboTest() {
this.setLayout(new GridLayout(0, 1));
this.add(repository);
this.add(environment);
}
private JComboBox createCombo(String[] data) {
final JComboBox combo = new JComboBox(data);
combo.setSelectedIndex(1);
combo.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println(e.getActionCommand()
+ ": " + combo.getSelectedItem().toString());
}
});
return combo;
}
private void display() {
JFrame f = new JFrame("ComboTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new ComboTest().display();
}
});
}
}
I had the same issue. I fixed it by revalidating and repainting the panel with the following code :
myPanel.revalidate();
myPanel.repaint();
Maybe a little late, but for those who are still looking for an easy and fail-safe way to use the JComboBox can use this:
public class FixedJComboBox<E>
extends JComboBox<E> {
// Copied constructors
public FixedJComboBox() {
super();
}
public FixedJComboBox(ComboBoxModel<E> aModel) {
super(aModel);
}
public FixedJComboBox(E[] items) {
super(items);
}
public FixedJComboBox(Vector<E> items) {
super(items);
}
#Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
// The arrow is the first (and only) component
// that is added by default
Component[] comps = getComponents();
if (comps != null && comps.length >= 1) {
Component arrow = comps[0];
// 20 is the default width of the arrow (for me at least)
arrow.setSize(20, height);
arrow.setLocation(width - arrow.getWidth(), 0);
}
}
}
As described here, the bug is caused by incorrectly setting both the location and the size of the arrow to (0,0), followed by some repainting issues. By simply overriding the setBounds() function, the arrow is always corrected after the UI/layout manager has wrongly updated the arrow.
Also, since new components are added after the old ones (i.e. higher index), the arrow will always be at the first element in the array (assuming you don't remove and re-add the arrow).
The disadvantage is of this class is that the width of the arrow is now determined by a constant instead of the UI/layout manager.

DefaultListModel.clear errors

I'm trying to display a list of items and, when the user clicks on an item, to clear the list and display another list.
If I run this and click on the first entry on the displayed list, the program dies with a long trail of runtime exceptions. If I remove the clear() line (commented below), it runs fine. Adding try/catch didn't reveal any information useful to me. Apologies for the long code, but I couldn't figure out how to shorten and still generate the errors.
What at I doing wrong?
import java.util.*;
import java.awt.BorderLayout;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
class ListGui extends JPanel implements ListSelectionListener {
private static JList list;
private static DefaultListModel listModel = new DefaultListModel();
public ListGui() {
super(new BorderLayout());
list = new JList(listModel);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.addListSelectionListener(this);
JScrollPane listScrollPane = new JScrollPane(list);
add(listScrollPane, BorderLayout.CENTER);
}
public static void Populate(List<String> lines) {
listModel.clear();
for(String line : lines) {
listModel.addElement(line);
}
}
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false) {
List<String> out = new ArrayList<String>();
out.add("three");
out.add("four");
Populate(out);
}
}
}
public class TestClear {
static JComponent newContentPane = new ListGui();
private static void createAndShowGUI() {
JFrame frame = new JFrame("toast");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(newContentPane);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
List<String> out = new ArrayList<String>();
createAndShowGUI();
out.add("one");
out.add("two");
ListGui.Populate(out);
}
}
I'm trying to display a list of items and, when the user clicks on an item, to clear the list and display another list.
That doesn't sound like the best design to me. The selection will change whenever you click on an item or when you use the arrow keys to move up or down the list. I'm sure for users that like to use the keyboard you don't want the list to change every time you use an arrow key.
The normal design would be to invoke an Action on the list on a "double click" or when the user users "Enter" from the keboard. This is easily implemente using the List Action concept.
However, if you really do want to update the list on every selection then I would use code like:
list.removeListSelectionListener( this );
populate(...);
list.addListSelectionListener(this);
The problem is that you are calling Populate() in valueChanged() which triggers valueChanged() and hence the stackoverflow.
The simplest solution is to have a flag to prevent reentry.
boolean busy = false;
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting() == false && !busy) {
busy = true;
List<String> out = new ArrayList<String>();
out.add("three");
out.add("four");
Populate(out);
busy = false;
}
}
If your code could be accessed by multiple threads, you should be looking into ReentrantLock

Unable to receive grab event by using GRAB_EVENT_MASK?

I added an AWTEventListener to process grab event. So this listener just use sun.awt.SunToolkit.GRAB_EVENT_MASK
mark.
But This listener can not capture UngrabEvent. The tricky thing is, when a JComboBox popuped its menulist, it can capture this event.
I use the following code for testing.
Start the program, click on the empty area of the frame, click on the frame title. Then there should be an UngrabEvent. But the listener does not capture it.
Start the program, click on the combobox and make its menulist popuped. click on the
frame title. Then there should be an UngrabEvent. And the listener captures it.
It is very strange...Is there any relationship between UngrabEvent and JComboBox?
public class ComboboxLearn {
public static void main(String[] args) {
// TODO Auto-generated method stub
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
System.out.println(event);
}
}, sun.awt.SunToolkit.GRAB_EVENT_MASK);
JComboBox box = new JComboBox(new Object[] { "AAA", "BBB", "CCC" });
box.addPopupMenuListener(new PopupMenuListener() {
#Override
public void popupMenuCanceled(PopupMenuEvent e) {
System.out.println(e);
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
System.out.println(e);// Set a breakpoint here
}
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
System.out.println(e);
}
});
JFrame f = new JFrame();
f.getContentPane().setLayout(new FlowLayout());
f.getContentPane().add(box);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(new Dimension(100, 100));
f.setVisible(true);
}
}
I sense you're experimenting; but generally, you shouldn't rely on Sun/Oracle's undocumented APIs.
I want a popup that will hide when the
mouse is pressed outside the popup but
not hide when the mouse is pressed on the popup.
Why not bring up a JDialog when you see isPopupTrigger() and hide it when you see it deactivating, as another window activates? The notion is discussed here.
Although trashgod's reasoning is understandable, it doesn't answer the question: What you are actually trying to do won't work because for the grab even to fire, you need to have a window grabbed: ((SunToolkit)Toolkit.getDefaultToolkit()).grab(someWindow);.
You could change your code as follows
class ComboboxLearn {
public static void main(String[] args) {
// TODO Auto-generated method stub
Toolkit toolkit = Toolkit.getDefaultToolkit();
toolkit.addAWTEventListener(new AWTEventListener() {
#Override
public void eventDispatched(AWTEvent event) {
System.out.println(event);
}
}, sun.awt.SunToolkit.GRAB_EVENT_MASK);
JComboBox box = new JComboBox(new Object[] { "AAA", "BBB", "CCC" });
JFrame f = new JFrame();
box.addPopupMenuListener(new PopupMenuListener() {
#Override
public void popupMenuCanceled(PopupMenuEvent e) {
System.out.println(e);
}
#Override
public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
System.out.println(e);// Set a breakpoint here
//UNGRABBING WINDOW
((SunToolkit) toolkit).ungrab(f);
}
#Override
public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
System.out.println(e);
//GRABBING WINDOW
((SunToolkit) toolkit).grab(f);
}
});
f.getContentPane().setLayout(new FlowLayout());
f.getContentPane().add(box);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(new Dimension(100, 100));
f.setVisible(true);
}
}
Then you will get your grab events but at the level you are working on you don't need them.
Then when would I need them?
Assuming you run into this nasty bug which although closed, I could still reproduce it, you need to implement your own popup mechanism. You did everything fine you attached your AWT listeners to close the popup whenever there is a click outside it but something is missing. You click everywhere and the popup disappears except on the window captions and outside your application! Shoot you think! How does JPopupmenu do it. And after you spend some time reading jdk code and trying various stuff, you realize that it's this undocumented event that does the trick.
I don't know the internals of this grab() method and I don't have time to investigate so there might be side effects not very obvious. Call it on your own risk.

Categories

Resources