CellEditorListener has "editingStopped" and "editingCancelled". But how might I implement a piece of code which needs to run when a cell editing session starts?
A typical example might be where you want the text of a JTextField editor component to go selectAll() when you start editing. I'm tempted to think the thing to do is override one of the methods of DefaultCellEditor, such as getTableCellEditorComponent or getCellEditorValue or getComponent, but none of these explicitly says they are called at the start of an edit session.
Conversely, we do know that JTable.getCellEditor returns the editor if we are editing, but null if not. This is because the component is made into a child object of JTable when editing starts. It also seems to get the focus at the beginning of an editing session, so you might perhaps think of add a FocusListener to the editor component (JTextField, etc.). But is this guaranteed? Also, maybe focus can be lost and then return during an editing session.
There's a way of listening for the "addition" (as a child object) of this editor component: ContainerListener. Unless someone tells me different this appears to be the most direct and rational way of getting a call that an editing session has begun. But it seems odd that there isn't a more direct way...
A typical example might be where you want the text of a JTextField editor component to go selectAll() when you start editing.
You can override the editCellAt(...) method of JTable to select the text once editing has started:
#Override
public boolean editCellAt(int row, int column, EventObject e)
{
boolean result = super.editCellAt(row, column, e);
final Component editor = getEditorComponent();
if (editor != null && editor instanceof JTextComponent)
{
((JTextComponent)editor).selectAll();
if (e == null)
{
((JTextComponent)editor).selectAll();
}
else if (e instanceof MouseEvent)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
((JTextComponent)editor).selectAll();
}
});
}
}
return result;
}
Rob Camick's answer is great, but I have another one for "completists" to peruse.
I use Jython but it should be simple enough for Java people to understand. In Python/Jython you can add arbitrary "attributes" to (almost) any object. So in editCellAt I add an attribute "start_editing" which then signals to the caret listener added to the JTextField editor component that a session has just begun. If the caret dot and mark are equal (collapsed), if the click-count-to-start-editing == 2, and if the editor has the "start_editing" attr, you select-all (again), and you also remove the "start_editing" attr... it works (!), without spawning a new Runnable.
class DatesTable( javax.swing.JTable ):
def editCellAt(self, row, column, event_obj ):
result = self.super__editCellAt( row, column, event_obj )
if self.editorComponent:
self.editorComponent.requestFocus() # explanation below
self.editorComponent.selectAll()
if isinstance( event_obj, java.awt.event.MouseEvent ):
self.cellEditor.start_editing = None
return result
class DatesTableCellEditor( javax.swing.DefaultCellEditor ):
def __init__( editor_self, table, *args, **kvargs ):
jtf = javax.swing.JTextField()
class JTFCaretListener( javax.swing.event.CaretListener ):
def caretUpdate( self, caret_event ):
if hasattr( editor_self, 'start_editing' ):
del editor_self.start_editing
if caret_event.dot == caret_event.mark and editor_self.clickCountToStart == 2:
caret_event.source.selectAll()
jtf.addCaretListener( JTFCaretListener())
javax.swing.DefaultCellEditor.__init__( editor_self, jtf, **kvargs )
A similar result could be achieved in Java, clearly, using a private field of the editor, or something along those lines.
NB why have I put "requestFocus" in there? If you press key F2 on an editable table cell the editing starts and the editor component (typically a JTextField) gets focus automatically. However you can also start editing just by typing into the table cell. It took me much head-scratching to work out that, oddly, if you do this you do indeed start editing, but the editor component does not get focus automatically. So this is a way to "resolve this anomaly".
NB2 One final point about my solution. In a real-world implementation you also need to set a timer to remove the attribute "start_editing" after a certain finite time (e.g. 0.5 s). Otherwise you might click once, which would not start an editing session (if click-to-start == 2), and then click again 10 seconds later, after having started editing by another means, to find that selectAll happens confusingly and inexplicably. This attribute must be made to be like the tape in Mission Impossible: self-destruct after x seconds...
Related
I added an UndoManager to a JTextPane in my application, but I can't get it work:
UndoManager undoManager = new UndoManager();
textpane.getDocument().addUndoableEditListener(undoManager);
When I manually type into the text pane, then try to undo the changes, nothing ever happens undoManager.canUndo() always returns false.
I also tried another way of adding the manager as follows:
textpane.getDocument().addUndoableEditListener(new UndoableEditListener()
{
#Override
public void undoableEditHappened( UndoableEditEvent e )
{
System.out.println("UndoableEditEvent");
undoMgr.addEdit(e.getEdit());
}
});
With the above code I can see in the output window that the undoableEditHappened( UndoableEditEvent e ) is called once at the start (most likely by a read call which loads the test file). When I make changes (via keyword) or insertText(...) calls, there are no further listener calls.
I found some similar questions here in StackOverflow, but the solutions were always alongs the lines that they had custom input methods for the JTextPane, I don't ... not that I know of.
What might I have overlooked?
I found out why the UndoableEditListener wasn't triggering.
I was calling JTextPane.read(Reader reader, Object object) after I had setup the Document listeners - What I didn't know was that calling the read(...) method creates and adds a new Document model to the JTextPane, which basically removed anything I had previously done to the old Document.
Solution
Work with the Document model after calling JTextPane.read(...)
This is my code:
comboBoxInstance.setInputPrompt("Something...");
comboBoxInstance.setNullSelectionAllowed(false);
Cookie comboCookie = getCookieByName("combo");
comboBoxInstance.select((comboCookie != null) ? comboCookie.getValue() : null);
final TextField textFieldInstance = new TextField("Textfield");
textFieldInstance.setInputPrompt("Something...");
Cookie tfCookie = getCookieByName("tf");
textFieldInstance.setValue((tfCookie != null) ? tfCookie.getValue() : null);
The problem is that the textfield works pretty well with the "Cookie setup". Only the combobox is refusing to work like it should.
The output is like this:
I've tried to use .setValue() instead of .select() but this has pretty much the same effect. I've also made sure that both the Cookie itself and the correct value are provided.
It may help to have a look at the part where the cookie is generated:
Cookie comboCookie = new Cookie("combo", comboBoxInstance.getValue().toString());
cookieProcessing(costcentreCookie); //<- sets maxage and vaadin related stuff (like adding the cookie)
Edit:
A few points to the data flow.
I'm generating a ComboBox with a SimpleJDBCConnectionPool's SQLContainer as the data container (coming from a TableQuery). Here's the initialization (executed in the constructor) in the combobox class:
private void init() throws SQLException {
this.setContainerDataSource(generateContainer());
this.setItemCaptionPropertyId("something");
}
The private method generateContainer() returns the SQLContainer of course.
This happens if I click on a particular button which opens up a dialog. This dialog is the fragment shown in the picture above. The combobox - of course - is part of it.
What one is supposed to do now is setting his data (get an item of the ComboBox) and hit save. The save button executes the routine to store the cookies. It's the code already mentioned above (Cookie comboCookie = new Cookie(...).
Okay, now the user is going to open up the dialog again. It's not important whether he reloads the application or just reopens the dialog (or does something else). It's basically the same in the app.
The dialog opens up and initializes the combobox (and the textfield) once again. However, this time it's supposed to gather the data out of the stored cookies. This is were the issue happens. This works well for the textfields (there are two but I've omitted one for shortening reasons) but not for the combobox, even tough it should've the exact same data as before. Hold in mind that it's the exact same class with the exact same initialization as when we stored the cookies in the first place.
I've the vague presumption, that it has to do something how the code is stacked. Maybe it hasn't finished loading the datacontainer while trying to set the appropriated value which then can't be found.
Edit2:
I've finally managed to reveal something. The ComboBox is indeed empty when the ".select()" is executed. However, this means, that the ComboBox is left untouched (it's only kind of "linked" to the datacontainer) until someone drops down the items. As soon as this happens, the items are there and I can possibly select them.
Is it supposed to work like this? O.o Am I able to fully initialize the combobox before I do something else? Something like:
private void init() throws SQLException {
this.setContainerDataSource(generateContainer());
this.setItemCaptionPropertyId("something");
this.gatherTheItems();
}
Edit3 - Test with ".setImmediate(true)"
I've changed the init to:
private void init() throws SQLException {
this.setContainerDataSource(generateContainer());
this.setItemCaptionPropertyId("SOMETHING");
this.setImmediate(true);
}
This didn't change anything. The combobox is still empty:
Finally! At first I've found a workaround which was like this:
for (Iterator it_IDS = combobox.getItemIds().iterator(); it_IDS.hasNext();) {
Object id = (Object) it_IDS.next();
if(id.toString().equals(cookie.getValue().toString())){
combo2.select(id);
break;
}
}
However, I couldn't believe that this was working since it doesn't change anything at the core problem. So I've investigated, that the RowID is built via a BigDecimal and voilĂ :
if(cookie != null) {
combobox.select(new RowId(new BigDecimal(cookie.getValue())));
}
I'm so happy right now :) Thanks for your patience kukis.
In case you came here because you're experiencing the same issue using a BeanItemContainer as datasource, bear in mind that you must implement both equals()and hashCode() methods on the underlying class for ComboBox's select() or setValue() methods to work.
You have plenty examples on Vaadin Forum on how to implement these methods:
ComboBox select value problem
Select or ComboBox does not Show Selected Property
Combobox select/setValue
In a CTabFolder, I'd like to check the content for unsaved data before the user can switch from one tab to another. SWT does not provide a PreSelection event, as stated here.
I found a workaround, suggesting to switch back to the old tab when a selection is triggered, validate the data and then perform the desired switch again, if data is valid.
I do understand the general idea of this workaround, however, it is not working for me. oldPageIndex and newPageIndex do always have the same value, though I did not click on the same tab.
this.tabContainer.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent event) {
int oldPageIndex = tabContainer.getSelectionIndex();
int newPageIndex = tabContainer.indexOf((CTabItem)event.item);
// Here: oldPageIndex == newPageIndex
...
}
});
Is this workaround still working or is there anything I could possibly be doing wrong? Or maybe, has there been any fix for a real PreSelection event in the meantime? I tried using event.doit, but the SelectionEvent is fired, when the tabs have been switched already.
You can use the selection listener but as you have found the getSelectionIndex() does not give you the old tab. So you will have to maintain the old tab index yourself.
This is the technique used by the Eclipse FormEditor.
I am trying to find out an elegant way to make JTable stop cell editing (cancel it actually) when user closes the main application window. I know something like this can be done using the WindowAdapter but for this to work properly I need a reference to the window. Problem is I sometimes do not have it.
An abrupt exit may need to be handled at several levels.
Assuming that the user must navigate out of the table to click on an Exit control, you should get the desired result by setting the table's "terminateEditOnFocusLost" property.
table.putClientProperty("terminateEditOnFocusLost", true);
If you have to resort to a WindowListener or similar, you can stop editing explicitly as shown here.
CellEditor cellEditor = table.getCellEditor();
if (cellEditor != null) {
if (cellEditor.getCellEditorValue() != null) {
cellEditor.stopCellEditing();
} else {
cellEditor.cancelCellEditing();
}
}
Because the host owns the frame decorations, some platforms require special handling to intercept the close control, as discussed here.
Preferences makes a best effort to persist updated values.
So far, this is the solution I am most satisfied with:
All my JTable objects install themselves as HierarchyListener(s) with the following method to handle the HierarchyEvent:
public void hierarchyChanged(HierarchyEvent e) {
Window win = SwingUtilities.getWindowAncestor(this);
if ((win != null) && (!win.equals(topLevelWindow))) {
topLevelWindow = win;
topLevelWindow.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
handleWindowClosing();
}
});
}
}
private void handleWindowClosing() {
if (table.isEditing()) {
table.getCellEditor().cancelCellEditing();
}
}
In the case of the project I work on, cancelling the editing when the application window closes is crucial because it sends the notification that the record is no longer in the editing state...
Using hierarchy listener is also crucial because my JTable objects move from dockable to dockable (we use the excellent Docking Frames here) and can be in different windows at different times.
I have a strange issue: there is a SectionPart with composite, which is create from FormToolkit#createComposite(getSection()). Composite contains some number of widgets, which are positioned vertically one under other (as in a usual form). When the cursor is inside some widget, let's say input filed and I am clicking right between two fields on empty space, then focus automatically jumps to the first field in this composite.
I've tried to set SWT.NO_FOCUS style bit to the first widget in the form (usually it is a TableComboViewer) but it didn't helped (it seems, that this bit is not set on TableCombo, which is inside TableComboViewer).
So, have anybody faced something similar, or are there any workarounds for this problem or any clues what could it be?
Upd1: setting NO_FOCUS style helps for non TableComboViewer widgets (in this case they are not receiving focus). In case of TableComboViewer TableCombo widget contains Text widget, which receives focus, but even, if I add NO_FOCUS bit, it is not applied to Text style. I've checked source of TableCombo and there is a method checkStyle, which does following:
private static int checkStyle (int style) {
int mask = SWT.BORDER | SWT.READ_ONLY | SWT.FLAT | SWT.LEFT_TO_RIGHT | SWT.RIGHT_TO_LEFT;
return SWT.NO_FOCUS | (style & mask);
}
I am not actually sure what it does, cause I am not really good in bitwise operation, but seems, that this is the problem, why I can't set NO_FOCUS flag.
I don't understand though, why when I am clicking on Composite, it tries to set foxus on it's children, can I somehow suppress this?
Upd2: The reason is probably found, it is said, that:
When the view is activated, focus is transferred to the form, which passes it to the first control capable of accepting focus, our link in this case.
And it seems, that it is not possible to forbid this.
Thanks in advance,
AlexG
You problem lies in Composite.setFocus().. have a look at this:
public boolean setFocus () {
checkWidget ();
Control [] children = _getChildren ();
for (int i= 0; i < children.length; i++) {
if (children [i].setFocus ()) return true;
}
return super.setFocus ();
}
As you can see, this will try to set the focus on the first control in the composite that will allow for the focus...
[EDIT - the following is added to clarify...]
The above method would not be a problem if it wasn't for the MouseListener that is installed on all Composites in FormToolkit.adapt(Composite composite):
public void adapt(Composite composite) {
composite.setBackground(colors.getBackground());
composite.addMouseListener(new MouseAdapter() {
public void mouseDown(MouseEvent e) {
((Control) e.widget).setFocus();
}
});
if (composite.getParent() != null)
composite.setMenu(composite.getParent().getMenu());
}
I have solved this problem on a number of occasions by having my own FormToolkit.adapt(Composite composite) in a sub-class that does the right thing - I just exchange setFocus() with forceFocus(). Though that can occasionally give you other problems...