JTable: single selection for the user, multiple selections programmatically - java

I have a JTable where the user should be able to select only a single row, but whenever a row is selected by the user, some other rows (that are related according to some logic) should also be selected programmatically. The problem is that if I set the selection mode of the table to ListSelectionModel.SINGLE_SELECTION, addRowSelectionInterval will also select only one row. Any ideas?
EDIT: I think all ideas (custom selection model, clearing all but last user selections, custom renderer for highlighting) were good, but the best is to use SwingX, because it doesn't require much infrastructure-code, only a clever usage of the library. (and it's easy to be clever when a SwingX-guru is helping :)

Biased me would say: certainly much easier in SwingX :-)
All you need is
a custom HighlightPredicate which decides about what is related
a ColorHighlighter configured with the selectionColors
set the custom predicate on receiving change notification from the selection model
Some code:
// the custom predicate
public static class RelatedHighlightPredicate implements HighlightPredicate {
List<Integer> related;
public RelatedHighlightPredicate(Integer... related) {
this.related = Arrays.asList(related);
}
#Override
public boolean isHighlighted(Component renderer,
ComponentAdapter adapter) {
int modelIndex = adapter.convertRowIndexToModel(adapter.row);
return related.contains(modelIndex);
}
}
// its usage
JXTable table = new JXTable(someModel);
table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
final ColorHighlighter hl = new ColorHighlighter(HighlightPredicate.NEVER,
table.getSelectionBackground(), table.getSelectionForeground());
table.addHighlighter(hl);
ListSelectionListener l = new ListSelectionListener() {
#Override
public void valueChanged(ListSelectionEvent e) {
if (e.getValueIsAdjusting()) return;
invokeUpdate((ListSelectionModel) e.getSource());
}
private void invokeUpdate(final ListSelectionModel source) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
int singleSelection = source.getMinSelectionIndex();
if (singleSelection >= 0) {
int first = Math.max(0, singleSelection - 2);
int last = singleSelection + 2;
hl.setHighlightPredicate(new RelatedHighlightPredicate(first, last));
} else {
hl.setHighlightPredicate(HighlightPredicate.NEVER);
}
}
});
}
};
table.getSelectionModel().addListSelectionListener(l);

You might set multiselection possible for the table, but with each selection change - take only 1 (last selected) row, clear other selections and add your own computed selection.

The problem is that if I set the selection mode of the table
use ListSelectionModel.SINGLE_SELECTION for events came from mouse and keyborad
some other rows (that are related according to some logic) should also be selected programmatically
have look at Renderer for JTable, then required row(s), columns or whatever could be highlighted until programmatic rules stay unchanged
... maybe will help you

Related

JComboBox to display multiple lines of text

I'm currently writing a small tool for sending sql queries to a database and recieving the according data.
Now to my problem:
I want to allow the user to enter a new search query or select from a "latest" list, where the last few queries are saved.
For that, I planned on using an editable JComboBox, but I'm having trouble diplaying multiple lines of text in the box itself.
The reason I want to do that, is because sql queries can get quite long and since I want make the box editable and at the same time keep the frame clean.
I've found ways to display multiple lines in the dropdown menu, but nothing for the box itself.
Thank you in advance and please forgive me if I overlooked something simple ;)
Greetings
Zeus
Extended editing functionality is supplied by the ComboBoxEditor, this allows you to define the actual component which is used as the combobox's editor
Based on your requirements, you're going to need (at the very least) a JTextArea, to provide (optionally) word wrapping and multiple lines
A rough and ready example might look something like this...
public class TextAreaComboBoxEditor implements ComboBoxEditor {
private JTextArea ta = new JTextArea(4, 20);
private JScrollPane sp = new JScrollPane(ta);
public TextAreaComboBoxEditor() {
ta.setWrapStyleWord(true);
ta.setLineWrap(true);
}
#Override
public Component getEditorComponent() {
return sp;
}
#Override
public void setItem(Object anObject) {
if (anObject instanceof String) {
ta.setText((String) anObject);
} else {
ta.setText(null);
}
}
#Override
public Object getItem() {
return ta.getText();
}
#Override
public void selectAll() {
ta.selectAll();
}
#Override
public void addActionListener(ActionListener l) {
}
#Override
public void removeActionListener(ActionListener l) {
}
}
This doesn't support ActionListener, as JTextArea uses the Enter key for it's own purposes. If you wanted to, you could use the key bindings API to add your own Action that can trigger the ActionListeners, for that, you'd need to supply a List or other means for managing them so you can call them back

How to make cells in a column un-selectable

I have a JTable in which some columns are uneditable. I do that by overriding the isCellEditable method. I now want to make the cells in these columns un-selectable. if the user is using the tab key to go through the cells, I want to focus to "skip" these uneditable cells. Can you please tell me how this is done? Thanks.
All navigation behaviour is controlled by actions registered in the table's actionMap. So the way to go is to hook into the action that is bound to the tab key, implement a wrapper that invokes that action as often as needed and replace the original action with the wrapper.
A raw code snippet for skipping not-editable cells:
Object actionKey = table.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
.get(KeyStroke.getKeyStroke("TAB"));
final Action traverseAction = table.getActionMap().get(actionKey);
Action wrapper = new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
traverseAction.actionPerformed(e);
while(shouldRepeat((JTable) e.getSource())) {
traverseAction.actionPerformed(e);
}
}
private boolean shouldRepeat(JTable source) {
int leadRow = source.getSelectionModel().getLeadSelectionIndex();
int leadColumn = source.getColumnModel().getSelectionModel().getLeadSelectionIndex();
return !source.isCellEditable(leadRow, leadColumn);
}
};
table.getActionMap().put(actionKey, wrapper);

Java - JCheckBox Filters Against a List

I have a List, that included JPA Entity objects of a certain type. Their reference String values are displayed in a JList for the user to see.
I want my user to be able to select filters as JCheckBoxes in the UI such as 'only from Client x', or 'only of Type x' and dynamically filter the Entity List.
I had thought to just keep a copy of static List completeList; and static List filteredList; and then just run individual filter methods each time a new filter is selected in the UI to update filteredList, which would work fine until you have to un-select a single filter and leave the others selected (at which point it all falls apart).
Every situation I think through fall apart at one point or another, usually when trying to select multiple filters of from one Menu.
An example of my thought pattern that checks all the filters to determine what needs to go in the new JList;
public static void filterList(){
List filteredList = new ArrayList<Job>(StoredDataClass.completeList);
if(clientSmithsCheckBox.isSelected()){
for(Job job : filteredList){
if(!job.getClient.equals(clientSmithsCheckBox.getText())){
filteredList.remove(job);
}
}
}
....... // Check other filters here etc.
if(clientBobAndCoCheckBox.isSelected()){
for(Job job : filteredList){
if(!job.getClient.equals(clientBobAndCoCheckBox.getText())){
filteredList.remove(job);
}
}
}
Even if clientBobAndCoCheckBox is selected, no jobs with that client will show in the final list, because we already removed them all because another client was already selected. Now, we could add to the list instead but we would face similar problems of having add stuff that shouldn't be there etc.
This is obviously possible, because this type of filtering system is common practice (example, excel). Although this is more of a design question, how can I achieve this?
Here's a short (and raw!) example of how you could organize your logic. It's in the context of SwingX (which supports sorting/filtering of a JList just the same way as a JTable) because I'm lazy - but you can apply it to your own environment easily.
Think of your criteria as a collection of filters which can be on or off, and then combine them with OR (if one or more is selected) or turn off if none is selected. The sole "trick" is to evaluate all of the checkboxes' states wheneven one of them is changed:
final JXList list = new JXList(new DefaultComboBoxModel(Locale.getAvailableLocales()));
list.setAutoCreateRowSorter(true);
final List<RowFilter> filters = new ArrayList<>();
filters.add(new MyRowFilter("de"));
filters.add(new MyRowFilter("ar"));
final List<JCheckBox> boxes = new ArrayList<>();
ActionListener l = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
List<RowFilter<Object, Object>> orCandidates = new ArrayList<>();
for (int i = 0; i < boxes.size(); i++) {
if (boxes.get(i).isSelected())
orCandidates.add(filters.get(i));
}
RowFilter<Object, Object> or = orCandidates.isEmpty() ? null :
RowFilter.orFilter(orCandidates);
list.setRowFilter(or);
}
};
JCheckBox first = new JCheckBox("de");
first.addActionListener(l);
boxes.add(first);
JCheckBox second = new JCheckBox("ar");
second.addActionListener(l);
boxes.add(second);
JComponent content = new JPanel();
content.add(new JScrollPane(list));
for (JCheckBox box : boxes) {
content.add(box);
}
showInFrame(content, "filters");
// just for completeness, the custom RowFilter
public static class MyRowFilter extends RowFilter {
private String text;
public MyRowFilter(String text) {
this.text = text;
}
#Override
public boolean include(Entry entry) {
Locale locale = (Locale) entry.getValue(0);
return locale.getLanguage().contains(text);
}
}

How to add a function in jTable that sorts the column?

I know that by using JTable the column is sorted when we click on the column heading, but what I want is that, when I right-click on the column name a function name 'sort' should be displayed. Any suggestion in doing it?
Start by adding a MouseListener to the table. See How to write mouse listeners
You will need to translate the click point to a column, see JTable#columnAtPoint.
You will then need to update the SortKey for the table. Check out Sorting and Filtering for an example
If I understand you correctly, you want to sort by some explicit action (triggered f.i. in a popup) instead of by the normal left-click.
If so, the tricky part is to force the ui-delegate to do nothing. There are two options:
hook into the default mouse listener installed by the ui delegate, as described in a recent QA
let the ui do its stuff, but fool it by a sorter implementation that doesn't follow the rules (beware: that's as dirty as the first approach!)
The mis-behaving sorter:
public class MyTableRowSorter extends TableRowSorter {
public MyTableRowSorter(TableModel model) {
super(model);
}
/**
* Implemented to do nothing to fool tableHeader internals.
*/
#Override
public void toggleSortOrder(int column) {
}
/**
* The method that really toggles, called from custom code.
*
* #param column
*/
public void realToggleSortOrder(int column) {
super.toggleSortOrder(column);
}
}
// usage
final JTable table = new JXTable(new AncientSwingTeam());
table.setRowSorter(new MyTableRowSorter(table.getModel()));
Action toggle = new AbstractAction("toggleSort") {
#Override
public void actionPerformed(ActionEvent e) {
JXTableHeader header = SwingXUtilities.getAncestor(
JXTableHeader.class, (Component) e.getSource());
Point trigger = header.getPopupTriggerLocation();
int column = trigger != null ? header.columnAtPoint(trigger) : -1;
if (column < 0) return;
int modelColumn = header.getTable().convertColumnIndexToModel(column);
((MyTableRowSorter) header.getTable().getRowSorter())
.realToggleSortOrder(modelColumn);
}
};
JPopupMenu menu = new JPopupMenu();
menu.add(toggle);
table.getTableHeader().setComponentPopupMenu(menu);
Yeah, couldn't resist throwing in some SwingX api, lazy me :-) With plain Swing, you'll have to write some lines more but the basics are the same: install the tricksy sorter and use its custom toggle sort to really sort whereever needed, f.i. in a mouseListener.

Change focus to next component in JTable using TAB

JTable's default behavior is changing focus to next cell and I want to force it to move focus to next component (e.g. JTextField) on TAB key pressed.
I overrided isCellEditable method of DefaultTableModel to always return false:
public boolean isCellEditable(int rowIndex, int columnIndex) {
return false;
}
But it still doesn't change focus to next component!
How should I make JTable change focus to next component instead of next cell?
The shift-/tab keys are used by default for transfering focus between components. JTable explicitly requests to handle the shift-/tab internally (by providing sets of focusTraversalKeys which doesn't include those).
Following the general rule (if there's specilized api available for a task, use that instead of rolling your own), the solution is to set traversal keys to again contain them:
Set<AWTKeyStroke> forward = new HashSet<AWTKeyStroke>(
table.getFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS));
forward.add(KeyStroke.getKeyStroke("TAB"));
table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, forward);
Set<AWTKeyStroke> backward = new HashSet<AWTKeyStroke>(
table.getFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS));
backward.add(KeyStroke.getKeyStroke("shift TAB"));
table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, backward);
If you really want this, you need to change the default behavior of the tables action map.
ActionMap am = table.getActionMap();
am.put("selectPreviousColumnCell", new PreviousFocusHandler());
am.put("selectNextColumnCell", new NextFocusHandler());
Then you need a couple of actions to handle the traversal
public class PreviousFocusHandler extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusPreviousComponent();
}
}
public class NextFocusHandler extends AbstractAction {
public void actionPerformed(ActionEvent evt) {
KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusNextComponent();
}
}
Another approach would be to disable the underlying Action...
ActionMap am = table.getActionMap();
am.get("selectPreviousColumnCell").setEnabled(false);
am.get("selectNextColumnCell").setEnabled(false);
(haven't tested this)
The benefit of this approach is can enable/disable the behaviour as you need it without needing to maintain a reference to the old Actions
default (implemented KeyBinding for JTable) is about next cell and from last cell to first,
you can to remove KeyBindings by setting to the null value
To reset to the standard keyboard bindings (typically TAB and SHIFT+TAB), simply specify null for the keystrokes parameter to Component.setFocusTraversalKeys:
table.setFocusTraversalKeys(KeyboardFocusManager.FORWARD_TRAVERSAL_KEYS, null);
table.setFocusTraversalKeys(KeyboardFocusManager.BACKWARD_TRAVERSAL_KEYS, null);

Categories