Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
HomeAnnouncementsWhite Papers
Discussion GroupsFirst AidDatabasesJavaBeansGUIJava 3DVirtual MachineCORBASecurityToolsGeneral
Java DirectoryOpen Source ProjectsSample Book ChaptersUser GroupsWeb Resources
Related Topics
Databases.NETMore Topics ...

Java Forum / GUI / January 2004

Tip: Looking for answers? Try searching our database.

CellEditor based on JSpinner

Thread view: 
Rhino - 16 Jan 2004 00:38 GMT
I am trying to make a CellEditor based on a JSpinner for a JTable.
Unfortunately, I'm having some problems and haven't been able to find
answers in either Google searches (newsgroups and the web), the API, the
Java Tutorial, or by trial-and-error.

Here is what I have so far. My questions follow the code, which has most of
the comments removed to keep it short.

package rte;

import java.awt.Component;

import java.awt.event.MouseEvent;

import java.util.Calendar;

import java.util.Date;

import java.util.EventObject;

import javax.swing.JFrame;

import javax.swing.JScrollPane;

import javax.swing.JSpinner;

import javax.swing.JTable;

import javax.swing.SpinnerDateModel;

import javax.swing.event.CellEditorListener;

import javax.swing.event.ChangeEvent;

import javax.swing.event.ChangeListener;

import javax.swing.event.EventListenerList;

import javax.swing.table.TableCellEditor;

import javax.swing.table.TableColumn;

public class IsoDateEditor extends JSpinner implements TableCellEditor {

   protected EventListenerList listenerList = new EventListenerList();

   protected ChangeEvent changeEvent = new ChangeEvent(this);

   public static String IsoDateFormat = "yyyy-MM-dd";

   SpinnerDateModel spinnerDateModel = null;

   int clickCountToStart = 0;

/**

* Method main() is used to test this editor.

*/

public static void main(String[] args) {

   Object[][] data = new Object[10][4];

   for (int ix = 0; ix < 10; ix++) {

       data[ix][0] = "Date " + ix; //arbitrary label

       data[ix][1] = getDate(ix); //arbitrary date

       data[ix][2] = getDate(ix); //another arbitrary date

       if (ix%2 == 0) { //if the row number is even

           data[ix][3] = new Boolean(true); //true

           }

       else { //the row number is odd

           data[ix][3] = new Boolean(false); //false

           }

       }

   Object[] columnNames = new Object[] {"Identifier", "Date", "Another
Date", "Fulltime"};

   EditorTableModel model = new EditorTableModel(data, columnNames);

   JTable table = new JTable(model);

   table.setCellSelectionEnabled(true);

   table.setRowHeight(25);

/* Create the renderer for the dates. */

   IsoDateRenderer myDateRenderer = new IsoDateRenderer();

/* Create the Spinner date model for the editor. */

   SpinnerDateModel spinnerDateModel = new SpinnerDateModel();

/* Create the editor. */

   IsoDateEditor myDateEditor = new IsoDateEditor(spinnerDateModel);

   myDateEditor.setClickCountToStart(2);

/* If the following line is un-commented, it will assign IsoDateRenderer as
the renderer

for all dates in the table, as perceived by the table model's
getColumnClass() method. */

   table.setDefaultRenderer(java.util.Date.class, myDateRenderer);

/* If the following line is un-commented, it will assign IsoDateEditor as
the editor

for all dates in the table, as perceived by the table model's
getColumnClass() method. */

   table.setDefaultEditor(java.util.Date.class, myDateEditor);

   TableColumn dateColumn = table.getColumn("Date");

/* If the following line is commented, a default cell renderer will be used
on the "Date"

column, i.e. the specific column whose name is 'Date' but not the column
whose name

is 'Another Date'.

If the default cell renderer thinks that this column contains dates - as a
result

of the table model's getColumnClass() method - it will render the date as a
localized

date using whatever format is appropriate for that locale. In Canada, that
is D-M-Y,

e.g. 15-Jan-2004. */

   dateColumn.setCellRenderer(new IsoDateRenderer());

/* If the following line is commented, a default cell editor will be used on
the "Date"

column, i.e. the specific column whose name is 'Date' but not the column
whose name

is 'Another Date'.

If the default cell editor thinks that this column contains dates - as a
result of

the table model's getColumnClass() method - it will edit the date using
whatever

format is appropriate for that locale. In Canada, that is D-M-Y, e.g.
15-Jan-2004. */

   dateColumn.setCellEditor(myDateEditor);

   JFrame frame = new JFrame("IsoDateEditor");

   frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

   frame.setContentPane(new JScrollPane(table));

   frame.setSize(400,300);

   frame.setVisible(true);

}

public static Date getDate(int index) {

   Date date = new Date();

   Calendar calendar = Calendar.getInstance();

   calendar.setTime(date);

   calendar.add(Calendar.DATE, 2 * index); //add an arbitrary number of
days to make dates different

   return calendar.getTime();

}

public IsoDateEditor(SpinnerDateModel spinnerDateModel) {

   super(spinnerDateModel);

   this.spinnerDateModel = spinnerDateModel;

   this.addChangeListener(new ChangeListener() {

       public void stateChanged(ChangeEvent changeEvent) {

       /* Question 1 */

       }

       });

}

public void addCellEditorListener(CellEditorListener listener) {

   listenerList.add(CellEditorListener.class, listener);

}

public void removeCellEditorListener(CellEditorListener listener) {

   listenerList.remove(CellEditorListener.class, listener);

}

protected void fireEditingStopped() {

   CellEditorListener listener;

   Object[] listeners = listenerList.getListenerList();

   for (int ix=0; ix<listeners.length; ix++) {

       if (listeners[ix] == CellEditorListener.class) {

       listener = (CellEditorListener) listeners[ix+1];

       listener.editingStopped(changeEvent);

       }

   }

}

protected void fireEditingCanceled() {

   CellEditorListener listener;

   Object[] listeners = listenerList.getListenerList();

   for (int ix=0; ix<listeners.length; ix++) {

   if (listeners[ix] == CellEditorListener.class); {

       listener = (CellEditorListener) listeners[ix+1];

       listener.editingCanceled(changeEvent);

       }

   }

}

public void cancelCellEditing() {

   fireEditingCanceled();

}

public boolean stopCellEditing() {

   fireEditingStopped();

   return true;

}

public void setClickCountToStart(int clickCountToStart) {

   this.clickCountToStart = clickCountToStart;

}

public int getClickCountToStart() {

   return clickCountToStart;

}

public boolean isCellEditable(EventObject event) {

   if (((MouseEvent)event).getClickCount() < clickCountToStart) {

       return false;

       }

   else {

       return true;

       }

}

public boolean shouldSelectCell(EventObject event) {

   return true;

}

public Object getCellEditorValue() {

/* Question 2 */

   System.out.println("getCellEditorValue() = " + ((JSpinner.DateEditor)
this.getEditor()).getTextField().getText());

   return ((JSpinner.DateEditor)
this.getEditor()).getTextField().getText();

   // System.out.println("getCellEditorValue() = " + value);

   // return value;

}

public Component getTableCellEditorComponent(JTable table, Object value,
boolean isSelected, int row, int column) {

   System.out.print("row = " + row + "; column = " + column + "; value = "
+ value.toString());

   if (value instanceof java.util.Date) System.out.println("; value is a
java.util.Date");

    else System.out.println("value is NOT a java.util.Date");

   if (value instanceof java.util.Date && isSelected) {

       setToolTipText("Modify this date as you see fit. Row/column = " +
row + "/" + column); //set the tooltip

       JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(this,
IsoDateFormat); //set the format of the spinner in the editor

       setEditor(dateEditor); //tell the spinner to use the editor

       spinnerDateModel.setValue(value); //set the date model to the value
in this cell of the table

       }

   return this;

}

}

These are my questions:

1. What event do I listen for to tell me to stop editing? My best guess is
that I need to wait until the user clicks in some other cell of the table;
if that happens, I assume that I am finished editing in the current cell.
However, that raises the question of how to distinguish between a "cancel"
and a "stop editing because you're done" and I'm not sure how to handle that
issue.

2. Is getEditorCellValue() doing the right thing? I think I want it to get
the value that is currently in the cell, which should be a java.util.Date,
and hand it to the cell editor's JSpinner but my getEditorCellValue() is
written as if the data is already in a JSpinner. This feels wrong but I'm
not sure what I should be doing.

3. Lastly, can anyone point me to a really good tutorial on CellRenderers
and CellEditors? I found
http://www-106.ibm.com/developerworks/library/j-jtable/ very useful in
developing my CellRenderer but it is not as strong on CellEditors so an even
better article/tutorial on the ins and outs of Renderers and Editors would
be handy for understanding what is really going on.

Signature

Rhino
---
rhino1 AT sympatico DOT ca
"If you want the best seat in the house, you'll have to move the cat."

Christian Kaufhold - 17 Jan 2004 21:51 GMT
Hello!

> I am trying to make a CellEditor based on a JSpinner for a JTable.
> Unfortunately, I'm having some problems and haven't been able to find
> answers in either Google searches (newsgroups and the web), the API, the
> Java Tutorial, or by trial-and-error.


Your code does not compile (broken lines, missing EditorTableModel, Iso-
DateRenderer).


> public class IsoDateEditor extends JSpinner implements TableCellEditor {

Subclass AbstractCellEditor and use a JSpinner instance variable to avoid
duplicating of the cell editor listener code.

>    protected EventListenerList listenerList = new EventListenerList();
>
>    protected ChangeEvent changeEvent = new ChangeEvent(this);
>
>    public static String IsoDateFormat = "yyyy-MM-dd";


>    SpinnerDateModel spinnerDateModel = null;

There is no need to store the model twice. You can do everything on the
spinner.


> public IsoDateEditor(SpinnerDateModel spinnerDateModel) {
>
[quoted text clipped - 7 lines]
>
>        /* Question 1 */


There is no need to listen to these changes, see below.


> public boolean isCellEditable(EventObject event) {
>
>    if (((MouseEvent)event).getClickCount() < clickCountToStart) {
>
>        return false;

You should handle the case of 'event' not being a MouseEvent.

> public Object getCellEditorValue() {
>
> /* Question 2 */

    return getValue();

> public Component getTableCellEditorComponent(JTable table, Object value,
> boolean isSelected, int row, int column) {
[quoted text clipped - 8 lines]
>
>    if (value instanceof java.util.Date && isSelected) {
                                           ^^^^^^^^^^
                                           ^^^^^^^^^^

??? If 'isSelected' is false you will not set the value of the cell and the
spinner will start with the value from the previous editing session.


>        setToolTipText("Modify this date as you see fit. Row/column = " +
> row + "/" + column); //set the tooltip

That is little use as the spinner will be covered (almost) completely by its
editor.

>        JSpinner.DateEditor dateEditor = new JSpinner.DateEditor(this,
> IsoDateFormat); //set the format of the spinner in the editor
>
>        setEditor(dateEditor); //tell the spinner to use the editor

There is no need to create a new, equivalent editor each time.


>        spinnerDateModel.setValue(value); //set the date model to the value
> in this cell of the table

You can just use setValue(value);

> These are my questions:
>
[quoted text clipped - 4 lines]
> and a "stop editing because you're done" and I'm not sure how to handle that
> issue.

You just wait until stopCellEditing or cancelCellEditing are called.
If you want to finish editing explicitly, you can call them yourself
(for example, I would suggest that hitting enter (action) on the tet
field should commit immediately).

> 2. Is getEditorCellValue() doing the right thing? I think I want it to get
> the value that is currently in the cell, which should be a java.util.Date,
> and hand it to the cell editor's JSpinner but my getEditorCellValue() is
> written as if the data is already in a JSpinner. This feels wrong but I'm
> not sure what I should be doing.

It is used to read the edited value out of the cell after editing has
stopped, so in your case it can just take the value from the spinner.

import javax.swing.*;

import javax.swing.table.TableCellEditor;

import java.util.EventObject;
import java.awt.Component;
import java.awt.event.MouseEvent;

import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

import java.text.ParseException;

public class IsoDateEditor2
   extends AbstractCellEditor
   implements TableCellEditor
{
   // can pass in constructor, can even pass in any DefaultEditor in
   // constructor as this does not contain any Date-specific code anymore.
   public static String IsoDateFormat = "yyyy-MM-dd";

   private JSpinner spinner;

   private int clickCountToStart;

   public IsoDateEditor2(SpinnerDateModel data)
   {
       spinner = new JSpinner(data);

       JSpinner.DefaultEditor d = new JSpinner.DateEditor(spinner, IsoDateFormat);

       spinner.setEditor(d);

       d.getTextField().addActionListener(new ActionListener()
       {
           public void actionPerformed(ActionEvent e)
           {
               stopCellEditing();
           }
       });
   }

   public Component getTableCellEditorComponent
       (JTable t, Object value, boolean selected, int row, int column)
   {
       spinner.setValue(value); // make sure it's valid!

       return spinner;
   }

   public boolean stopCellEditing()
   {
       try
       {
           // must commit, or a hand-edited value will be lost
           spinner.commitEdit();
       }
       catch (ParseException e)
       {
           // if invalid, refuse to stop (could also ignore and stop)
           return false;
       }

       return super.stopCellEditing();
   }

   public Object getCellEditorValue()
   {
       return spinner.getValue();
   }

   public void setClickCountToStart(int clickCountToStart)
   {
       this.clickCountToStart = clickCountToStart;
   }

   public int getClickCountToStart()
   {
       return clickCountToStart;
   }

   public boolean isCellEditable(EventObject e)
   {
       return (!(e instanceof MouseEvent)) || ((MouseEvent)e).getClickCount() >= clickCountToStart;
   }
}

Christian
Signature

The cat in the hat came back, wrecked a lot of havoc on the way
Always had a smile and a reason to pretend

                                R.E.M., The Sidewinder Sleeps Tonight

Andrew Thompson - 17 Jan 2004 22:01 GMT
| > I am trying to make a CellEditor based on a JSpinner for a JTable.
....
| Your code does not compile (broken lines, missing EditorTableModel, Iso-
| DateRenderer).

See http://www.physci.org/codes/sscce.jsp
for tips on creating an example others can
easily work with..

--
Andrew Thompson
* http://www.PhySci.org/ PhySci software suite
* http://www.1point1C.org/ 1.1C - Superluminal!
* http://www.AThompson.info/andrew/ personal site
Rhino - 19 Jan 2004 14:44 GMT
First of all, thank you *very* much for your help with this cell editor. I
read several articles and spent quite a few hours finding and examining
examples in an attempt to figure out how to create the editor but only got
it working very poorly, as you can imagine from the code I posted.

Second, my apologies for the broken code. I meant to include the
EditorTableModel - which was trivial - and to comment out the
IsoDateRenderer but forgot.

Lastly, I would like to ask a few followup questions to make sure I
understand how the editor works.
The code you've suggested works very well for my prototype but when I use it
in my real application, I run into a problem. My real application displays
and edits data from a database table. When I double click on a date cell in
the JTable that displays the database data, I get my JSpinner just fine and
the editing proceeds without a problem. However, when I press Enter to end
the edit, the JTable model sends the edited value to the database to be
stored and this fails because the date isn't in ISO format. The error
messages shows the actual value received by the SQL Update statement and it
shows the date as 'Thu Jan 02 00:00:00 EST 2003'. The CellEditor displayed
the date as '2003-01-02', which is exactly the format I want to store in the
database.

1. Am I right in assuming that the CellEditor is working the way it should
but that I need to add some code in my table model to reformat the date in
the table's data model so that it is in the ISO format ('2003-01-02') rather
than the java.util.Date format ('Thu Jan 02 00:00:00 EST 2003')? Or does the
CellEditor need to be changed? Or is it the JTable's data model which needs
to be changed so that the dates are not stored as java.util.Dates but as
Strings in ISO format?

2. Your comment in the getTableCellEditorComponent() method says that I
should 'make sure it's valid'. What kind of code do you envision here? Are
you saying that I should verify that the value is an instance of a
java.util.Date or do you mean something else? What should I be doing if the
incoming value is *not* valid? I still need to return a Component of some
kind so what should I return if the value isn't valid?

3. Am I right in thinking that it is better to pass the date model to the
constructor of the editor from outside so that we only need one instance of
the date model rather than creating a separate date model for each column of
the table? Is this a very significant performance advantage? If not, I'm
tempted to create the data model within the editor so that it is more
independent of the code outside of the editor.

4. How would a user cancel an edit if he had already started editing and
then realized he was in the wrong cell? Am I right in guessing that he
should press the 'Esc' key and then click in a different cell? When I start
an edit, then press 'Esc', my cursor goes to the start of the spinner
(before the year) but the value does not change back to the old value. If I
then *single* click on another cell, the editor on the first cell, the one
that I was editing when I hit 'Esc', goes back to normal and shows its old
pre-edit value. However, I am now in edit mode on the new cell, even though
I only clicked on it once. This is *almost* the behaviour I want except that
I want a single click on another cell to end the edit on the first cell but
NOT start an edit on the new cell. Why is this happening? Where do I need to
make changes?

If I can get to the bottom of these mysteries, I will feel a lot more
comfortable with editors and renderers.

Rhino
Christian Kaufhold - 19 Jan 2004 16:58 GMT
> The code you've suggested works very well for my prototype but when I use it
> in my real application, I run into a problem. My real application displays
[quoted text clipped - 15 lines]
> to be changed so that the dates are not stored as java.util.Dates but as
> Strings in ISO format?

I would store them in the TableModel as Dates and convert them when
needed in the format needed. So if you need the Date as an ISO format,
use a SimpleDataFormat that formats it in that way.

2. Your comment in the getTableCellEditorComponent() method says that I
> should 'make sure it's valid'. What kind of code do you envision here? Are
> you saying that I should verify that the value is an instance of a
> java.util.Date or do you mean something else? What should I be doing if the
> incoming value is *not* valid? I still need to return a Component of some
> kind so what should I return if the value isn't valid?

You just need to setup the editor so that it is only used on columns
where the given Date values are in the range allowed by the spinner's
model. Otherwise setValue will throw IllegalArgumentException, so if therte
is a possibility that the TableModel contains invalid values, you have to
choose some default (or make the SpinnerModel allow that value).

> 3. Am I right in thinking that it is better to pass the date model to the
> constructor of the editor from outside so that we only need one instance of
> the date model rather than creating a separate date model for each column of
> the table? Is this a very significant performance advantage? If not, I'm
> tempted to create the data model within the editor so that it is more
> independent of the code outside of the editor.

Yes, you can do that (There is no efficiency difference and a SpinnerDate-
Model probably does not use that much memory). OTOH, the editor now is
almost generic for any JSpinner so maybe there is no need to have a date-
specific editor at all - just setup the generic editor with the given
SpinnerModel.


> 4. How would a user cancel an edit if he had already started editing and
> then realized he was in the wrong cell? Am I right in guessing that he
> should press the 'Esc' key and then click in a different cell? When I start
> an edit, then press 'Esc', my cursor goes to the start of the spinner
> (before the year) but the value does not change back to the old value. If I

Not reproducible.

> then *single* click on another cell, the editor on the first cell, the one
> that I was editing when I hit 'Esc', goes back to normal and shows its old
> pre-edit value. However, I am now in edit mode on the new cell, even though
> I only clicked on it once. This is *almost* the behaviour I want except that

Not reproducible.

> I want a single click on another cell to end the edit on the first cell but
> NOT start an edit on the new cell. Why is this happening? Where do I need to
> make changes?

Please post your code.

Christian
Signature

And in short, I was afraid.

Rhino - 21 Jan 2004 16:31 GMT
Christian,

Thank you very much for your help with this but something has come up that
is going to occupy virtually all of my time for the next few days so I can't
spend any more time on the CellEditor until the weekend or maybe early next
week.

Your answers have been very useful and I suspect that the problem with
cancelling edits that I mentioned is likely in my code and the way I plugged
your code into mine. I'll go over that very carefully in a few days and if I
still can't get the CellEditor to behave correctly, I'll post back here with
a runnable code fragment.

I really wish I could do all that right now and maintain the momentum of
this conversation but it just isn't possible. I'll post again if I can't
solve the problem on my own.

Thanks again for all your help.

Rhino

> > The code you've suggested works very well for my prototype but when I use it
> > in my real application, I run into a problem. My real application displays
[quoted text clipped - 68 lines]
>
> Christian


Free Magazines

Get these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...

Oracle MagazineNetwork ComputingComputer WorldBio-IT WorldeWeekInformation WeekInfosecurity
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2009 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.