I need to get action type during drag and drop or copy/cut and paste (copy vs. move). It is a Swing application and there is implemented TransferHandle. I need this information at the end of the action, in importData method.
For drag and drop it seems to be possible test getUserDropAction, like this
#Override
public boolean importData(final TransferSupport support) {
if(support.isDrop() && support.getUserDropAction() == TransferHandler.MOVE) {
// drag and drop, MOVE
}
}
...but how to get this information for cut/copy and paste? Or is there a better, universal way?
Maybe you can get enough tips from the Swing tutorial which contains a working example:
CCP in a non-Text Component (The Java™ Tutorials > Creating a GUI With JFC/Swing > Drag and Drop and Data Transfer)
ListTransferHandler.java
Whether it is drag and drop or copy/cut and paste can be determined by the TransferSupport#isDrop() method.
/**
* Perform the actual data import.
*/
public boolean importData(TransferHandler.TransferSupport info) {
// ...
if (info.isDrop()) { // This is a drop
// ...
} else { // This is a paste
// ...
}
Use the the TransferHandler#exportDone(...) method's int action argument to determine if it is copy and paste or cut and paste.
Since the paste is executed with the TransferHandler#importData(...) method, override the TransferHandler#exportDone(...) method that is called after the paste is completed, and if the action of the argument is TransferHandler.MOVE, cut the transfer source data.
If the action is anything other than TransferHandler.MOVE, there is no need to do anything because it is a copy.
/**
* When the export is complete, remove the old list entry if the
* action was a move.
*/
protected void exportDone(JComponent c, Transferable data, int action) {
if (action != MOVE) {
return;
}
JList list = (JList)c;
DefaultListModel model = (DefaultListModel)list.getModel();
int index = list.getSelectedIndex();
model.remove(index);
}
Related
I'm writing a small program for a project for Uni and it's basically a library program to manage books anr read/write to JSON file instead of using a database cause it'd be simpler since it's my first proper Java application.
I'm utilizing a TextField to filter a ListView with all the books' titles, and it works, it shows the correct book in the list and updates the corresponding informations on screen when that book is selected, the issue is that even if the program works as intended, it throws an error everytime I update the search field I get an ArrayIndexOutOfBoundsException. The full stack is as follows:
Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 7
at javafx.base#19-ea/javafx.collections.transformation.FilteredList.get(FilteredList.java:172)
at com.libraryproject.javalibrary/com.libraryproject.javalibrary.MainViewController.populateDetails(MainViewController.java:200)
at com.libraryproject.javalibrary/com.libraryproject.javalibrary.MainViewController.lambda$initialize$3(MainViewController.java:127)
at javafx.graphics#19-ea/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics#19-ea/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
at javafx.graphics#19-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture(InvokeLaterDispatcher.java:96)
at javafx.graphics#19-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java)
at javafx.graphics#19-ea/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at javafx.graphics#19-ea/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)
After some googling, some people suggested that when updating the GUI from user input, one should do it in the Application Thread, which to be honest I'm not absolutely sure what that means, but anyway I followed the advice and wrapped the functions that would then update the UI variables in a Platform.runLater(() -> {} , but the issue still remains, and it's the stack above, at this point I have absolutely no idea what the problem could be, so, following the stack posted, let's see the code of the parts that are shown:
I'm using a FilteredList to, well, filter the listrView using the search, here's the code managing that and most of the initialize method:
private FilteredList<Book> filteredBooks;
...
...
// inside the initialize method
#Override
public void initialize(URL arg0, ResourceBundle arg1) {
// Populate the variable we use throughout the program with the data from the JSON file
filteredBooks = new FilteredList<Book>(handleJSON.getBooks());
// Then update the list view for the first time
populateView(filteredBooks);
...
...
// section of code responsible to check for search changes, when found, fires populateView once more, this time with the variable updated.
searchField.textProperty().addListener((obs, oldText, newText) -> {
filteredBooks.setPredicate(book -> {
if(newText == null || newText.isEmpty() || newText.isBlank()) {
return true;
}
String lowerCaseCompare = newText.toLowerCase();
if(book.getTitle().toLowerCase().contains(lowerCaseCompare)) {
return true;
}
return false;
});
Platform.runLater(() -> populateView(filteredBooks));
}); // Listener
...
...
...
// This one handles the selection of an item in the list, when selected, the fields on the other side of the windows will get populated with the respective data from the book based on the id from the list, since they essentialy share the same FilteredList
listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSel, newSel) -> {
Platform.runLater(() -> {
populateDetails(listView.getSelectionModel().selectedIndexProperty().getValue(), filteredBooks);
editButton.setDisable(false);
});
As you can see I wrapped all of the function that will update the ListView and fields in the window with Platform.runLater, but it doesn't seem to help.
Now for the populateView function that fires the first time the program is opened and everytime there's a change in the searchfiield:
public void populateView(FilteredList<Book> booksList) {
// clears the listview to avoid old elements stacking in the list.
listView.getSelectionModel().clearSelection();
listView.getItems().clear();
ObservableList<String> rawTitles = FXCollections.observableArrayList();
for(Book book: booksList) {
rawTitles.add(book.getTitle());
}
listView.setItems(rawTitles);
} // populateView()
And last but not least the populateDetails function that fills the fields about a book based on the list selection:
public void populateDetails(Integer selectedBookID, FilteredList<Book> books) {
Book currentBook = books.get(selectedBookID);
titleValue.setText(currentBook.getTitle());
authorValue.setText(currentBook.getAuthor());
languageValue.setText(currentBook.getLanguage());
genreValue.setText(currentBook.getGenre());
pagesValue.setText(currentBook.getPages().toString());
yearValue.setText(currentBook.getRelease().toString());
if (currentBook.getAvailable()) {
availableRadio.setSelected(true);
} else {
unavailableRadio.setSelected(true);
}
} // populateDetails
Thats basically I tried to use the runLater in different places just to be sure, I still get the same stack, any idea what could cause this?
The stack trace tells you exactly what the problem is. The ArrayIndexOutOfBoundsException occurs when you call get(..) on a FilteredList with the value -1, which you do on line 200 of MainViewController.java, in the populateDetails(...) method. Looking at your code, this line must be the line
Book currentBook = books.get(selectedBookID);
so selectedBookID must be the culprit, having the value -1.
selectedBookID is a parameter passed to the method, and you call the method from line 127 of MainController.java, in a lambda expression in the initialize() method. (Again, this information is in the stack trace.) The value you pass is
listView.getSelectionModel().selectedIndexProperty().getValue()
The documentation tells you explicitly when this happens:
The selected index is either -1, to represent that there is no selection, or an integer value that is within the range of the underlying data model size.
So your populate details needs to handle the case where nothing is selected (probably by clearing the text fields). I think it's cleaner to listen to the selectedItemProperty() instead of the selectedIndexProperty(), as it directly gives you the selected Book (or null if nothing is selected), and you don't have to retrieve the Book from the list:
public void populateDetails(Book currentBook) {
if (currentBook == null) {
titleValue.setText("");
authorValue.setText("");
languageValue.setText("");
genreValue.setText("");
pagesValue.setText("");
yearValue.setText("");
availableRadio.setSelected(false);
unavailableRadio.setSelected(false);
} else {
titleValue.setText(currentBook.getTitle());
authorValue.setText(currentBook.getAuthor());
languageValue.setText(currentBook.getLanguage());
genreValue.setText(currentBook.getGenre());
pagesValue.setText(currentBook.getPages().toString());
yearValue.setText(currentBook.getRelease().toString());
if (currentBook.getAvailable()) {
availableRadio.setSelected(true);
} else {
unavailableRadio.setSelected(true);
}
}
}
Your code is overkill; there is basically no need for the populateView() method. The filtered list will update its contents when you change the predicate, and notify observers that its content has changed. So you should just set the list view's items list to the filtered list directly. Then your listener for the search field only has to update the predicate, and the list view will automatically update.
Delete the populateView() method and update the initialize() method as:
public void initialize(URL arg0, ResourceBundle arg1) {
// Populate the variable we use throughout the program with the data from the JSON file
filteredBooks = new FilteredList<Book>(handleJSON.getBooks());
listView.setItems(filteredBooks);
// ...
// ...
// section of code responsible to check for search changes, when found, fires populateView once more, this time with the variable updated.
searchField.textProperty().addListener((obs, oldText, newText) -> {
filteredBooks.setPredicate(book -> {
if(newText == null || newText.isEmpty() || newText.isBlank()) {
return true;
}
String lowerCaseCompare = newText.toLowerCase();
return book.getTitle().toLowerCase().contains(lowerCaseCompare)
});
}); // Listener
// ...
// This one handles the selection of an item in the list, when selected, the fields on the other side of the windows will get populated with the respective data from the book based on the id from the list, since they essentialy share the same FilteredList
listView.getSelectionModel().selectedItemProperty().addListener(
(obs, oldSel, newSel) -> populateDetails(newSel)
);
}
I'd like to know how can I determine if the KeyCombination (constructed via Mnemonic) for a control is triggered. Or for simplicity's sake, a handler method for Mnemonics?
Basically, I'm working with a custom control which extends from Labeled and from its Behavior class, I want to perform some extra actions when the assigned Mnemonic is triggered.
EDIT
So after a little bit digging, I came up with an idea to simply listen to a KeyEvent from the Scene (but take note that this is JUST AN IDEA. Either way, I'll figure out the rest later on). Here's how:
public CustomLabeledBehavior(CustomLabeled control) {
super(control, ...);
/* IMPORTANT PART */
// Assume that mnemonicParsing is set to true
TextBinding tb = new TextBinding(control.getText());
KeyCombination kc = tb.getMnemonicKeyCombination();
control.getScene().addEventFilter(KeyEvent.KEY_PRESSED, e -> {
if (kc.match(e)) {
System.out.println("MATCHED!");
}
});
}
UPDATE
To make this answer more useful than it used to be, I'll show you the usage of the method I'm currently using to determine if the KeyCombination is triggered. So I'll try to explain every single detail if I can.
We are only focusing on adding a Mnemonic, using the MnemonicParsing property of a custom control. And we will call this custom control as CustomLabeled since based on the question, it extends from the Labeled class, hence the name is based to it. Then we will work things inside its Skin class called, CustomLabeledSkin.
#1 Initializing the control:
<!-- Assuming that this FXML is already set and is attached to a Scene -->
<CustomLabeled text="_Hello World!" mnemonicParsing="true"/>
#2 Setting up our MnemonicHandler:
/* These two variables are initialized from the Constructor. */
private KeyCombination kb; // The combination of keys basically, ALT + Shortcut key
private String shortcut; // The shortcut key itself basically, a Letter key
private boolean altDown = false; // Indicator if the user press ALT key
/**
* This handler is a convenience variable to easily attach
* and detach to the Scene's event handlers. This is the
* whole idea to determine whether the KeyCombination is
* triggered.
*/
private EventHandler<KeyEvent> mnemonicHandler = new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent event) {
// The KeyCombination, basically, the ALT + Shortcut key.
if (kb.match(event) {
// TODO: Execute command here.
event.consume(); // Prevent from further propagation.
return; // Make sure that the rest won't be executed.
}
// While these two functions are for separate event.
// Example, pressing ALT first before the Shortcut key.
if (event.isAltDown()) altDown = !altDown;
if (altDown && event.getCode() == KeyCode.getKeyCode(shortcut)) {
// TODO: Execute command here.
event.consume();
}
}
}
#3 Initialize our Skin class:
/**
* Constructor
*/
public CustomLabeledSkin(CustomLabeled control) {
// Since this is just an example/testing, we will only assume
// that MnemonicParsing is enabled and a valid Mnemonic is
// registered. So if you want to validate a mnemonic, you
// might want to do it here.
TextBinding tb = new TextBinding(control.getText());
kc = tb.getMnemonicKeyCombination();
shortcut = tb.getMnemonic();
// Then we can just filter for a KEY_PRESS from the Scene, then
// everything else will be handled by our MnemonicHandler.
control.getScene().addEventFilter(KeyEvent.KEY_PRESSED, mnemonicHandler);
}
NOTE: The TextBinding class is not part of the public API, it is used by the Labeled node to handle Mnemonics.
Further more, you can create a method to detach the MnemonicHandler if there's no currently Mnemonics assigned (E.g previously there's a mnemonic, then code was changed...).
I have customized ListGrid, in which records can be expanded and expansion component is displayed. I know there exists little arrow to expand/collapse record in upper left corner of the record, but I wonder if it is possible to manually check if the selected record is expanded or collapsed. I want the record to expand/collapse on single record click. My code example:
private RecordClickHandler gatherRecordClickHandler() {
return new RecordClickHandler() {
#Override
public void onRecordClick(RecordClickEvent event) {
//Here i want to check if the record is expanded/collapsed
if(/*expanded check method here*/)
collapseRecord(event.getRecord());
else
expandRecord(event.getRecord());
}
};
}
Try this:
if(myListGrid.isExpanded(event.getRecord))
collapseRecord(event.getRecord());
else
expandRecord(event.getRecord());
I have a TreePanel which shows different kind of objects hierarchically. Region, City, Location...
I want to be able to show different context menu items in different levels. For example: miR for Region, miC for City, miL for Location...
I used this snipped to achieve that dynamic structure:
contextMenu.addListener(Events.BeforeShow, new Listener<MenuEvent>() {
#Override
public void handleEvent(MenuEvent be) {
//First make all menu items invisible
List<Component> menuItems = contextMenu.getItems();
for (Component c : menuItems) {
c.setVisible(false);
}
//And make apprepriate menu items visible
TopologyTreeElement s = tree.getSelectionModel().getSelectedItem();
if (s instanceof TopologyTreeElement.Region) {
miR.setVisible(true);
}
if (s instanceof TopologyTreeElement.City) {
miC.setVisible(true);
}
}
});
But, in any level if all of the items are invisible, it shows an empty box. I want it not to show the menu totally. I tried adding this code snippet to the method, but it gave no help.
//Do not show menu if no menu item is invisible
boolean isMenuShouldBeVisible = miC.isVisible() || miR.isVisible();
if (!isMenuShouldBeVisible) {
be.preventDefault();
be.stopEvent();
}
Anyone can suggest a different approach?
Since you are listening to the BeforeShow event, you are allowed to cancel the event and stop the actual Show event from happening. Check to see if all items are invisible, and if so, call be.setCancelled(true).
Any event that starts in Before can be used to cancel the later event - this is why these before- events exist at all.
I'm using the following
org.eclipse.jface.viewers.CheckboxCellEditor.CheckboxCellEditor(Composite parent)
I'm creating a table viewer with cellEditors and doing the following
CellEditor[] editors = new CellEditor[columnNames.length];
editors[7] = new CheckboxCellEditor(table);
I have a CellModifier that has the following
public Object getValue(Object element, String property) {
Object result = null;
...
result = Boolean.valueOf(task.isDfRequested());
return result;
}
public void modify(Object element, String property, Object value) {
item.isSelected(((Boolean)value).booleanValue());
}
Finally I have a LabelProvider that has the following
public String getColumnText(Object element, int columnIndex) {
String result = "";
try {
result = Boolean.toString(item.isSelected());
} catch (Exception ex) { }
break;
However, in my UI instead of having a check box I have the word true or false && clicking it results in switching state to false or true. Any ideas on why I don't have a checkbox??
I've searched in the source code of CheckboxCellEditor class and in the constructor the control associated to the CellEditor is created in the createControl(Composite parent) method. This method is abstract in CellEditor class and it's implemented like this in CheckboxCellEditor:
protected Control createControl(Composite parent) {
return null;
}
So a control is not created, that's why you don't see the checkbox. In the documentation of the Class you can read:
Note that this implementation simply
fakes it and does does not create any
new controls. The mere activation of
this editor means that the value of
the check box is being toggled by the
end users; the listener method
applyEditorValue is immediately called
to signal the change.
I solved this using a ComboBoxCellEditor with yes and no items.
Regards.
Well, I have no idea how SWT works or what component you are even talking about.
But I do know that when using Swing you can have custom editors for a column in a JTable. If you don't tell the table the class of data for the column then the toString() method of the data is invoked. But if you tell the table that Boolean data is displayed in the column then the table will use the check box editor.
Sounds like a similiar symptom, but I don't know your particular solution.
What I've decided to do is to just implement a dirty hack others have been using.
Create two images of check boxes, one checked the other not checked. Switch the state between the two based on the boolean.
It's not perfect, but for now it gets the job done