java - JTable loses focus when a dialog is shown from a cell editor component -
in main application, jtable losing focus when dialog shown cell editor component.
below simple sscce made see problem.
do these simples experiments:
- press f2 in first table column start editing. change column contents number 2 , press enter key. table lose focus , first field in form focus.
- press f2 in first table column start editing. change column contents number 2 , press tab key. table lose focus , first field in form focus.
the first field in form searchfield component. because not in jtable, behaves when change contente number 2 , commit edit (with enter or tab).
import java.awt.borderlayout; import java.awt.dimension; import java.beans.propertychangeevent; import java.beans.propertychangelistener; import java.text.numberformat; import java.text.parseexception; import java.util.objects; import javax.swing.borderfactory; import javax.swing.box; import javax.swing.boxlayout; import javax.swing.defaultcelleditor; import javax.swing.jformattedtextfield; import javax.swing.jframe; import javax.swing.joptionpane; import javax.swing.jpanel; import javax.swing.jscrollpane; import javax.swing.jtable; import javax.swing.jtextfield; import javax.swing.listselectionmodel; import javax.swing.swingutilities; import javax.swing.table.abstracttablemodel; import javax.swing.text.defaultformatterfactory; import javax.swing.text.numberformatter; public class sscce extends jpanel { private sscce() { setlayout(new borderlayout()); setborder(borderfactory.createemptyborder(5, 5, 5, 5)); jpanel pnlfields = new jpanel(); pnlfields.setlayout(new boxlayout(pnlfields, boxlayout.page_axis)); pnlfields.setborder(borderfactory.createemptyborder(0, 0, 10, 0)); searchfield field1 = new searchfield(); configurefield(field1); pnlfields.add(field1); pnlfields.add(box.createrigidarea(new dimension(0, 3))); jtextfield field2 = new jtextfield(); configurefield(field2); pnlfields.add(field2); add(pnlfields, borderlayout.page_start); add(new jscrollpane(createtable()), borderlayout.center); } private void configurefield(jtextfield field) { field.setpreferredsize(new dimension(150, field.getpreferredsize().height)); field.setmaximumsize(field.getpreferredsize()); field.setalignmentx(left_alignment); } private jtable createtable() { jtable table = new jtable(new customtablemodel()); table.setselectionmode(listselectionmodel.single_selection); table.setcellselectionenabled(true); table.gettableheader().setreorderingallowed(false); table.setpreferredscrollableviewportsize(new dimension(500, 170)); table.setdefaulteditor(integer.class, new searchfieldcelleditor(new searchfield())); return table; } private static void createandshowgui() { jframe frame = new jframe("sscce (jtable loses focus)"); frame.setdefaultcloseoperation(jframe.exit_on_close); frame.add(new sscce()); frame.pack(); frame.setlocationrelativeto(null); frame.setvisible(true); } public static void main(string[] args) { swingutilities.invokelater( new runnable() { @override public void run() { createandshowgui(); } } ); } } class customtablemodel extends abstracttablemodel { private string[] columnnames = {"column1 (search field)", "column 2"}; private class<?>[] columntypes = {integer.class, string.class}; private object[][] data = {{1, ""}, {3, ""}, {4, ""}, {5, ""}, {6, ""}}; @override public int getcolumncount() { return columnnames.length; } @override public int getrowcount() { return data.length; } @override public string getcolumnname(int col) { return columnnames[col]; } @override public object getvalueat(int row, int col) { return data[row][col]; } @override public class<?> getcolumnclass(int c) { return columntypes[c]; } @override public boolean iscelleditable(int rowindex, int columnindex) { return true; } @override public void setvalueat(object value, int row, int col) { data[row][col] = value; firetablecellupdated(row, col); } } class searchfieldcelleditor extends defaultcelleditor { searchfieldcelleditor(final searchfield searchfield) { super(searchfield); searchfield.removeactionlistener(delegate); delegate = new editordelegate() { @override public void setvalue(object value) { searchfield.setvalue(value); } @override public object getcelleditorvalue() { return searchfield.getvalue(); } }; searchfield.addactionlistener(delegate); } @override public boolean stopcellediting() { try { ((searchfield) getcomponent()).commitedit(); } catch (parseexception ex) { ex.printstacktrace(); } return super.stopcellediting(); } } class searchfield extends jformattedtextfield implements propertychangelistener { private object _oldvalue; searchfield() { setupformatter(); addpropertychangelistener("value", this); } private void setupformatter() { numberformat integerformat = numberformat.getintegerinstance(); integerformat.setgroupingused(false); numberformatter integerformatter = new numberformatter(integerformat) { @override public object stringtovalue(string text) throws parseexception { return text.isempty() ? null : super.stringtovalue(text); } }; integerformatter.setvalueclass(integer.class); integerformatter.setminimum(integer.min_value); integerformatter.setmaximum(integer.max_value); setformatterfactory(new defaultformatterfactory(integerformatter)); } @override public void propertychange(propertychangeevent evt) { object newvalue = evt.getnewvalue(); if (!objects.equals(newvalue, _oldvalue)) { _oldvalue = newvalue; // suppose value of 2 means data wasn't found. // display message user. if (new integer(2).equals(newvalue)) { joptionpane.showmessagedialog( null, "not found: " + newvalue + ".", "warning", joptionpane.warning_message); } } } }
so, there way solve problem? solution of issue important me.
thank you.
marcos
* update *
i think i've found solution, have opinion if trustworthy solution.
change stopcellediting method , test sscce again:
@override public boolean stopcellediting() { searchfield searchfield = (searchfield) getcomponent(); try { searchfield.commitedit(); } catch (parseexception ex) { ex.printstacktrace(); } component table = searchfield.getparent(); table.requestfocusinwindow(); return super.stopcellediting(); }
so, think solves problem or there flaw?
marcos
update 2
i've found little flaw. corrected these changes:
class searchfieldcelleditor extends defaultcelleditor { searchfieldcelleditor(final searchfield searchfield) { super(searchfield); searchfield.removeactionlistener(delegate); delegate = new editordelegate() { @override public void setvalue(object value) { searchfield.setvalue(value); } @override public object getcelleditorvalue() { return searchfield.getvalue(); } }; searchfield.addactionlistener(delegate); } @override public component gettablecelleditorcomponent( jtable table, object value, boolean isselected, int row, int column) { searchfield searchfield = (searchfield) getcomponent(); searchfield.setpreparingforedit(true); try { return super.gettablecelleditorcomponent( table, value, isselected, row, column); } { searchfield.setpreparingforedit(false); } } @override public boolean stopcellediting() { searchfield searchfield = (searchfield) getcomponent(); try { searchfield.commitedit(); } catch (parseexception ex) { ex.printstacktrace(); } component table = searchfield.getparent(); table.requestfocusinwindow(); return super.stopcellediting(); } } class searchfield extends jformattedtextfield implements propertychangelistener { private boolean _ispreparingforedit; private object _oldvalue; searchfield() { setupformatter(); addpropertychangelistener("value", this); } void setpreparingforedit(boolean ispreparingforedit) { _ispreparingforedit = ispreparingforedit; } private void setupformatter() { numberformat integerformat = numberformat.getintegerinstance(); integerformat.setgroupingused(false); numberformatter integerformatter = new numberformatter(integerformat) { @override public object stringtovalue(string text) throws parseexception { return text.isempty() ? null : super.stringtovalue(text); } }; integerformatter.setvalueclass(integer.class); integerformatter.setminimum(integer.min_value); integerformatter.setmaximum(integer.max_value); setformatterfactory(new defaultformatterfactory(integerformatter)); } @override public void propertychange(propertychangeevent evt) { final object newvalue = evt.getnewvalue(); if (!objects.equals(newvalue, _oldvalue)) { _oldvalue = newvalue; // suppose value of 2 means data wasn't found. // display message user. if (new integer(2).equals(newvalue) && !_ispreparingforedit) { joptionpane.showmessagedialog(null, "not found: " + newvalue + ".", "warning", joptionpane.warning_message); } } } }
have found more flaws too? have review.
marcos
update 3
another solution after suggestion kleopatra :
class searchfieldcelleditor extends defaultcelleditor { searchfieldcelleditor(final searchfield searchfield) { super(searchfield); searchfield.setshowmessageasynchronously(true); searchfield.removeactionlistener(delegate); delegate = new editordelegate() { @override public void setvalue(object value) { searchfield.setvalue(value); } @override public object getcelleditorvalue() { return searchfield.getvalue(); } }; searchfield.addactionlistener(delegate); } @override public component gettablecelleditorcomponent( jtable table, object value, boolean isselected, int row, int column) { searchfield searchfield = (searchfield) getcomponent(); searchfield.setpreparingforedit(true); try { return super.gettablecelleditorcomponent( table, value, isselected, row, column); } { searchfield.setpreparingforedit(false); } } @override public boolean stopcellediting() { searchfield searchfield = (searchfield) getcomponent(); try { searchfield.commitedit(); } catch (parseexception ex) { ex.printstacktrace(); } return super.stopcellediting(); } } class searchfield extends jformattedtextfield implements propertychangelistener { private boolean _showmessageasynchronously; private boolean _ispreparingforedit; private object _oldvalue; searchfield() { setupformatter(); addpropertychangelistener("value", this); } public boolean isshowmessageasynchronously() { return _showmessageasynchronously; } public void setshowmessageasynchronously(boolean showmessageasynchronously) { _showmessageasynchronously = showmessageasynchronously; } void setpreparingforedit(boolean ispreparingforedit) { _ispreparingforedit = ispreparingforedit; } private void setupformatter() { numberformat integerformat = numberformat.getintegerinstance(); integerformat.setgroupingused(false); numberformatter integerformatter = new numberformatter(integerformat) { @override public object stringtovalue(string text) throws parseexception { return text.isempty() ? null : super.stringtovalue(text); } }; integerformatter.setvalueclass(integer.class); integerformatter.setminimum(integer.min_value); integerformatter.setmaximum(integer.max_value); setformatterfactory(new defaultformatterfactory(integerformatter)); } @override public void propertychange(propertychangeevent evt) { final object newvalue = evt.getnewvalue(); if (!objects.equals(newvalue, _oldvalue)) { _oldvalue = newvalue; // suppose value of 2 means data wasn't found. // display message user. if (new integer(2).equals(newvalue) && !_ispreparingforedit) { if (_showmessageasynchronously) { swingutilities.invokelater( new runnable() { @override public void run() { showmessage(newvalue); } } ); } else { showmessage(newvalue); } } } } private void showmessage(object value) { joptionpane.showmessagedialog(null, "not found: " + value + ".", "warning", joptionpane.warning_message); } }
comments , suggestions last solution still appreciated. ultimate , optimal solution?
marcos
as commented: it's bit fishy change state of table in editor, if it's related focus brittle @ best. go great lengths avoid it.
the mis-behaviour feels similar incorrectly implemented inputverifier has side-effects (like grabbing focus) in verify vs. in shouldyieldfocus correct: in such context focusmanager gets confused, "forgets" natural last-focusowner-before.
the remedy might let manager job first, , show message when it's done. in example code can achieved wrapping invokelater:
if (needsmessage()) { swingutilities.invokelater(new runnable() { public void run() { joptionpane.showmessagedialog(null, "not found: " + newvalue + ".", "warning", joptionpane.warning_message); } }); }
Comments
Post a Comment