Okay, this is a hard one to explain but I'll try my best.
I have a JTextField and a JComboBox in a JTable, whose getCellEditor method has been overriden as follows:
public TableCellEditor getCellEditor( int row, int column ) {
if ( column == 3 ) {
// m_table is the JTable
if ( m_table.getSelectedRowCount() == 1 ) {
JComboBox choices = new JComboBox();
choices.setEditable( true );
choices.addItem( new String( "item 1" ) );
return new DefaultCellEditor( choices );
}
return super.getCellEditor( row, column );
}
Here are the behavioral differences (NOTE that from this point on, when I say JTextField or JComboBox, I mean the CELL in the JTable containing either component):
When I click once on a JTextField, the cell is highlighted. Double clicking brings up the caret and I can input text. Whereas, with a JComboBox, single clicking brings up the caret to input text, as well as the combo drop down button.
When I tab or use the arrow keys to navigate to a JTextField and then start typing, the characters I type automatically get entered into the cell. Whereas, when I navigate to a JComboBox the same way and then start typing, nothing happens apart from the combo drop down button appearing. None of the characters I type get entered unless I hit F2 first.
So here's my question: What do I need to do have JComboBoxes behave exactly like JTextFields in the two instances described above?
Please do not ask why I'm doing what I'm doing or suggest alternatives (it's the way it is and I need to do it this way) and yes, I've read the API for all components in question....the problem is, it's a swing API.
Further Googling led me to the following article:
http://www.jroller.com/santhosh/entry/keyboard_handling_in_tablecelleditor
Although it doesn't describe the same issue as mine, it definitely shares some common traits.
Using some of the suggestions in that article, I was able to (atleast) resolve the keyboard bit of my problem (the one described in point 2 of my question). I did this by overriding the processKeyBinding method of JTable as follows:
protected boolean processKeyBinding( KeyStroke key_stroke, KeyEvent e, int condition, boolean pressed ) {
Object source = e.getSource();
if ( source instanceof JTable ) {
JTable table = (JTable) source;
Component comp = table.getEditorComponent();
if ( comp instanceof JComboBox ) {
JComboBox combo_box = (JComboBox) comp;
// this bit is quite hacky. Since I want comboboxes to behave exactly like textfields,
// simply check to see how a textfield would handle this event.
JTextField tmp_field = new JTextField();
InputMap input_map = tmp_field.getInputMap( condition );
ActionMap action_map = tmp_field.getActionMap();
if( input_map != null && action_map != null && isEnabled() ) {
Object binding = input_map.get( key_stroke );
Action action = ( binding == null ) ? null : action_map.get( binding );
if( action != null ) {
combo_box.requestFocus();
ComboBoxEditor combo_editor = combo_box.getEditor();
JTextField text_field = (JTextField) combo_editor.getEditorComponent();
if ( e.getKeyChar() == ' ' ) { // backspace
String cur_val = text_field.getText();
if ( ! cur_val.equals( "" ) )
text_field.setText( cur_val.substring( 0, cur_val.length() - 1 ) );
}
else
text_field.setText( text_field.getText() + e.getKeyChar() );
return false;
}
}
}
}
return super.processKeyBinding( key_stroke, e, condition, pressed );
}
This method seems like bit of a hack to me but then again it's swing, so it might be reasonable.
The problem of getting JComboBoxes to behave like JTextFields in the scenario described in point 1 of my question (i.e when using a mouse) is still open, if someone want's to have a go at it.
Ash
An alternative for bullet 2 is to move all the passing-on of the keyEvents into a custom JComboBox implementation: in there, implement its processKeyBinding similar to table.processKeyBinding. Something like:
public static class JTableEditorComboBox extends JComboBox {
#Override
protected boolean processKeyBinding(KeyStroke ks, KeyEvent e,
int condition, boolean pressed) {
boolean result = super.processKeyBinding(ks, e, condition, pressed);
if (!result)
result = processKeyBindingForTextField(ks, e, condition, pressed);
return result;
}
private boolean processKeyBindingForTextField(KeyStroke ks, KeyEvent e,
int condition, boolean pressed) {
// sanity check: really used as cellEditor
if (!Boolean.TRUE.equals(getClientProperty("JComboBox.isTableCellEditor"))
|| !isEditable()
|| !(getEditor().getEditorComponent() instanceof JTextField)) return false;
JTextField field = (JTextField) getEditor().getEditorComponent();
// basically c&p JComponent.processKeyBinding (it's protected so
// can't call directly from here)
InputMap map = field.getInputMap(WHEN_FOCUSED);
ActionMap am = field.getActionMap();
if(map != null && am != null && isEnabled()) {
Object binding = map.get(ks);
Action action = (binding == null) ? null : am.get(binding);
if (action != null) {
return SwingUtilities.notifyAction(action, ks, e, field,
e.getModifiers());
}
}
return false;
}
}
Then, use an instance of that in your cellEditor, like:
JTable table = new JTable(new AncientSwingTeam()) {
#Override
public TableCellEditor getCellEditor(int row, int column) {
if (column == 1) {
// m_table is the JTable
JComboBox choices = new JTableEditorComboBox();
choices.setEditable(true);
choices.addItem(new String("item 1"));
DefaultCellEditor editor = new DefaultCellEditor(choices);
editor.setClickCountToStart(2);
return editor;
}
return super.getCellEditor(row, column);
}
};
Related
I have a simple problem. I suggest a list of objects to the user to choose from using p:autocomplete. At the same time the user can create new objects 'on the fly' by typing names that are not in the suggestion. The whole setup is very similar to the vanilla POJO showcase, but I'm posting the code here:
<p:autoComplete id="player" value="#{entry.player}"
completeMethod="{abean.complete}"
var="s1" itemLabel="#{s1.name}" itemValue="#{s1}"
converter="PlayerConverter">
<p:ajax event="itemSelect" listener="#{abean.playerSelected}"/>
</p:autoComplete>
The converter:
#FacesConverter(forClass=Player.class, value = "PlayerConverter")
public class PlayerConverter implements Converter
{
#Override
public Object getAsObject(FacesContext ctx, UIComponent cmp, String value)
{
Player player = null;
if ( !(value==null || value.trim().isEmpty()))
{
// Autocomplete did find a match, the appropriate
// player is returned
try
{
Integer id = Integer.valueOf(value);
player = TeamService.getInstance().getPlayer(id);
}
catch (NumberFormatException e)
{
// Autocomplete found no match, the entered String
// is given. Create an ad-hoc player as response
player = new Player();
player.setName(value);
player.setAdHoc(true);
}
}
return player;
}
#Override
public String getAsString(FacesContext ctx, UIComponent cmp, Object value)
{
String result = null;
if (value instanceof String)
{
result = (String) value;
}
else if (value instanceof Spieler)
{
Integer id = ((Spieler)value).getId();
result = (id == null) ? null : id.toString();
}
return result;
}
}
The problem I am facing seems simple: how do I coerce this construction into allowing the user to simply erase a value? When the user deletes the content of the input field, I would expect to get a call to PlayerConverter with a null or empty value, but that doesn't happen.
How do I get my hands on empty input?
I found a workaround. Use a converter and add a flag to the objects you are displaying in the p:autocomplete. Set the flag to 'false' in the converter's getAsString method. Set it to 'true' in the converter's getAsObject method. The latter is not called when the user emptied the autocomplete field. Therefore the flag remains 'false'. And that you can check in the action method of your command button.
I hope it helps someone.
In ActionScript 3, is there a clean way to define a function that accepts an optional boolean argument ? As you may know, this is invalid :
public function test(param:Boolean = null):void {
trace(param);
}
This triggers the following error: VerifyError: Error #1102: Illegal default value for type Boolean. Since, Boolean is a primitive, I guess it makes sense that it cannot be set to null. The only workaround I found is to cast the parameter to an object :
public function test(param:Object = null):void {
trace(Boolean(param));
}
However, this does not feel very clean, particularly if you are developing libraries. ASDoc will generate API documentation that says the expected parameter is an Object whereas what is really needed is a Boolean.
Is there a better approach ?
When you say optional, I assume that you mean if there isn't a value supplied then something different should happen compared to if you had a default value of true or false.
You could make your own object to handle the three states that you need and maintain code readability by using a class like this:
public class Condition
{
private var _value:* = null;
public function Condition(initial:* = null)
{
value = initial;
}
public function set value(n:*):void
{
if(_value === null || _value === false || _value === true)
{
_value = n;
}
}
public function get value():*{ return _value; }
}
And then your function could be:
function test(param:Condition = null):void
{
if(param && param.value != null)
{
trace(param.value);
}
}
test( new Condition() );
test( new Condition(true) );
As you said Boolean can not be set to null value.
Therefore, you should specify a default value that is either true or false.
public function test(param:Boolean = false):void {
trace(param);
}
But because you need the third case where nothing is set, one option could be to accept any Object but throw an exception if it is not null and not a boolean:
public function test(param:* = null):void
{
if (param != null)
{
if ((param == true) || (param == false))
{
trace(Boolean(param).toString());
}
else
{
throw new CustomError("param should be a boolean");
}
}
else
{
// Do nothing
}
}
Note that this solution also accept objects or primitives that can be compared to true or false such as 0, 1, or [].
From the good suggestions and discussion above I think that, in a library scenario and for simplicity's sake, the best way remains to type the parameter as Object with a default value of null but to request a Boolean in the API documentation :
/**
* #param param Boolean object or null
*/
public function test(param:Object = null):void {
trace(Boolean(param));
}
This allow the user of the library to pass a either a Boolean or nothing at all. Thanks everyone.
There was a tonne of discussion on my previous answer, but this is the correct way to have a function that accepts one of three states. My previous answer attempted to retain the use of a Boolean value like you were requesting, but that is not the right way to go about it.
Create a class that defines three values:
class State
{
public static const EMPTY:int = -1;
public static const FALSE:int = 0;
public static const TRUE:int = 1;
}
Your function will accept an int (the type of each of the three properties within your State class). It will deal with the three possible values. You can use concise commenting to notify the developer of what thee values the function is expecting, referencing the State class. The default value can be -1 aka State.EMPTY.
/**
* Function description.
* #param state One of three states, defined by State.
*/
function test(state:int = -1):void
{
switch(state)
{
case State.EMPTY:
// No value given.
break;
case State.TRUE:
// True.
//
break;
case State.FALSE:
// False.
//
break;
default:
throw new ArgumentError("Unsupported value for test()");
break;
}
}
My XML:
<destinations>
<destination>
<fav>1</fav>
<cheapest>140</cheapest>
</destination>
<destination>
<fav>0</fav>
<cheapest>150</cheapest>
</destination>
</destinations>
I am creating XMLListCollection for my spark List component.
var dataprovider:XMLListCollection = new XMLListCollection(xml.destination);
I am trying to sort this XMLListCollection using fav and cheapest element.
var sort:Sort = new Sort();
sort.fields = [new SortField("fav" , true , true)];
sort.fields.push(new SortField("cheapest" , false , true));
dataprovider.sort = sort;
dataprovider.refresh();
Everything works fine till I update value of fav:
xml.destination.(id == String(destId))[0].fav = 0;
XML structure looks exactly the same after the update but I get thrown error from my itemrenderer object :
override public function set data( value:Object ) : void {
dest_name.text = value.text;
}
Error stating that value is null. How can value be null in the first place? I get no error when I remove fav from sort fields or update cheapest element instead.
Does anyone have any idea about this anomaly?
You have to take into account that your itemrenderers are recycled, for example, if order of items in your collection changes(when you change value of sort field). When renderers are recycled, null can be passed to set data function.
That means your function
override public function set data( value:Object ) : void {
dest_name.text = value.text;
}
shall be changed like that:
override public function set data( value:Object ) : void {
if(value){
dest_name.text = value.text;
}
}
You should always keep this in mind when implementing item renderers.
My JComboBox model contains item like item1, item2, item1. My problem is when I select third item (item1) in JComboBox and check getSelectedIndex() it always returns 0.
If the item is same in my model how can I get index of each item differently? Like:
item1 returns 0
item 2 returns 1
item1 returns 2
It returns index = 0. Because the method getSelectedIndex() use .equals on objects that are in the JComboBox and compare it with the selected one. In your case because item1 is also at index 0 it finds the condition true and returns 0. If you want to get different index then you have to override the getSelectedIndex() method.
An outline of default getSelectedIndex() method of JComboBox found at Java2s:
public int getSelectedIndex() {
Object sObject = dataModel.getSelectedItem();
int i, c;
Object obj;
for (i = 0, c = dataModel.getSize(); i < c; i++) {
obj = dataModel.getElementAt(i);
if (obj != null && obj.equals(sObject))
return i;
}
return -1;
}
You should have something [may be itemName if item object has a name or anything else] different in 2 entries to get desired result. Override getSelectedIndex() and compare the thing that is meant to be differ in all. If both entries are completely same then whats the point of adding it twice?
If two entries in the JComboBox correspond to the same Object, then even if you click item 3 the actual item that is selected will be the first entry of that object (i.e. the one with the lowest index)
I don't think that this will work for the same objects.
A JList has no problems with identical items.
import javax.swing.event.*;
import javax.swing.*;
class TestList {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
final String[] items = {"item1", "item2", "item1"};
final JList list = new JList(items);
final JTextField output = new JTextField(15);
JPanel gui = new JPanel();
gui.add(list);
gui.add(output);
list.addListSelectionListener(new ListSelectionListener(){
public void valueChanged(ListSelectionEvent lse) {
int index = list.getSelectedIndex();
String outputText =
"Index: " +
index +
" Value: " +
items[index];
output.setText(outputText);
}
});
JOptionPane.showMessageDialog(null, gui);
}
});
}
}
My jTable is loaded with data and this is where I call my Pop up functionality on jTable.
jTable.addMouseListener(new TablePopupListener(jTable));
displayTable();
So basically, if I right-click a row, a popup(credit check) comes up and if I click it is setting a value to the last cell in that row. Now, based on this column cell value I have to define the color of a row. Let's say if the cell value fails then turn the row to red else to green. I have tried customCellRenderer and defined my condition but there is no change in row color. The custom cell renderer worked great for a button functionality that I had to write, though. The below code uses prepare cellRenderer which I felt is easy but I don't see any change in row color.
I am missing some connection, plz provide me help.
Thanks in advance.
class TablePopupListener extends MouseAdapter implements ActionListener {
JPopupMenu popup;
JTable table;
int[] selRows;
TableModel model;
ArrayList rowValueList = new ArrayList();
JMenuItem creditCheck = new JMenuItem("Credit Check");
public TablePopupListener(JTable jTable) {
this.table = jTable;
model = table.getModel();
popup = new JPopupMenu();
JMenuItem creditCheck = new JMenuItem("Credit Check");
creditCheck.addActionListener(this);
popup.add(creditCheck);
}
public void mousePressed(MouseEvent me) {
firePopup(me);
}
public void mouseReleased(MouseEvent me) {
firePopup(me);
}
public void firePopup(MouseEvent me) {
/*
* The popup menu will be shown only if there is a row selection in the
* table
*/
// popup.show(table, me.getX(), me.getY());
if (me.isPopupTrigger() && table.getModel().getRowCount() != 0
&& table.getSelectedRow() != -1) {
// popup.show(table,me.getX(),me.getY());
if (me.isPopupTrigger()) {
JTable source = (JTable) me.getSource();
int row = source.rowAtPoint(me.getPoint());
int column = source.columnAtPoint(me.getPoint());
if (!source.isRowSelected(row))
source.changeSelection(row, column, false, false);
popup.show(table, me.getX(), me.getY());
}
}
}
public void actionPerformed(ActionEvent ae) {
if (ae.getActionCommand().equals("Credit Check")) {
System.out.println("you have clicked creditCheckpopup");
selRows = table.getSelectedRows();
if (selRows.length > 0) {
for (int i = 0; i < selRows.length; i++) {
// get Table data
for (int j = 1; j < (table.getColumnCount()) - 1; j++) {
rowValueList.add(model.getValueAt(selRows[i], j));
}
System.out.println("Selection : " + rowValueList);
}
} else {
System.out.println("you have clicked something idiot");
}
int result = new COpxDeal(rowValueList).CheckCredit();
if (result == 1)
rowValueList.add("pass");
else
rowValueList.add("fail");
String aValue = (String) rowValueList.get(14);
for (int i = 0; i < selRows.length; i++) {
model.setValueAt(aValue, selRows[i], 15);
}
// inserted comment (Kleopatra): where are we? that's outside of the TablePopup?
// okay, nothing like copying the code into an IDE and let that do the formatting, silly me ;-)
// this is indeed _inside_ the popup, that is the table is recreated
table = new JTable(model) {
public Component prepareRenderer(TableCellRenderer renderer,
int row, int column) {
Component c = super.prepareRenderer(renderer, row, column);
JComponent jc = (JComponent) c;
// if (!isRowSelected(row)){
// c.setBackground(getBackground());
// System.out.println(isRowSelected(row));
// }
int modelRow = convertRowIndexToModel(row);
String strTestValue = "fail";
String strTblValue = (String) getModel().getValueAt(
modelRow, 15);
System.out.println("result :" + strTblValue);
if (strTblValue == null || strTblValue.equals(""))
System.out.println("there is nothing in strTblValue");
else if (strTestValue.equals(strTblValue)) {
jc.setBackground(Color.RED);
} else {
jc.setBackground(Color.green);
}
return c;
}
};
}
}
}
after some formatting (believe me, it's important for code to be readable ;-) seems like you instantiate a new table inside your popupMenu and only that table has the custom renderer. Which you can do, but doesn't have any effect on the your real table.
Move the prepareRenderer into your real table (the one you pass into the popup as parameter) and you should see the coloring. Beware: due to a bug in DefaultTableCellRenderer, you have to set the color always, that is
if (nothingToDo) {
setBackground(normal)
} else if ... {
setBackground(one)
} else {
setBackground(other)
}
Edit: trying to explain the changes in code structure, pseudo-code snippets
Current state, that's what you are doing:
JTable table = new JTable();
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
....
// in the listener guts, the reference is replaced
table = new JTable() {
#Override
Component prepareRenderer(...
}
Change to, that's what you should do:
JTable table = new JTable() {
#Override
Component prepareRenderer(...
};
table.addMouseListener(new TablePopupListener(table));
// keep listener-local reference to table
JTable table = table;
// don't replace ever, it's for reading only
edit 2:
- changed the pseudo-code to actually register the listener)
- the code indented below the addMouseListener is mean as an outline of the code inside the TablePopupListener