JTextPane not triggering UndoableEditListener events - java

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(...)

Related

Combobox doesn't react to ".setValue()" nor ".select()"

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

Text Missing On Checkbox Component for Swing After Adding Action

I'm writing a simple Swing app. I tried adding a checkbox as listed below. Once I added the actionHandler loadPickers the name Foo disappeared from where it was sitting next to the right of chckbxNewCheckBox. I tried adding a call to setHideActionText(), but now nothing displays.
JCheckBox chckbxNewCheckBox = new JCheckBox("Foo");
chckbxNewCheckBox.setToolTipText("");
chckbxNewCheckBox.setName("");
chckbxNewCheckBox.setHideActionText(true);
chckbxNewCheckBox.setAction(loadPickers);
mainPanel.add(chckbxNewCheckBox, "flowy,cell 0 1");
If I change it to this it works properly. I see the text "Foo".
JCheckBox chckbxNewCheckBox = new JCheckBox("Foo");
chckbxNewCheckBox.setToolTipText("");
chckbxNewCheckBox.setName("");
chckbxNewCheckBox.setHideActionText(true);
chckbxNewCheckBox.setAction(loadPickers);
chckbxNewCheckBox.setText("Foo"); //THIS DOES NOT WORK IF IT COMES BEFORE SET ACTION
mainPanel.add(chckbxNewCheckBox, "flowy,cell 0 1");
I've included the action here for completeness. Why does it work this way? Am I missing something here? Currently I'm using the WindowBuilder plugin for Eclipse with the Mig layout system (which I really like). Unfortunately I haven't figure out if there's a way to make WindowBuilder use the .setText() method instead of using the constructor. Any help on what I'm doing wrong, any insight on why this behavior exists like this, or a good workaround for WindowBuilder would be great.
private class LoadPickers extends AbstractAction {
public LoadPickers() {
//putValue(NAME, "SwingAction_2");
putValue(SHORT_DESCRIPTION, "Some short description");
}
public void actionPerformed(ActionEvent e) {
}
}
As explained in the JavaDoc of AbstractButton.setAction:
Setting the Action results in immediately changing all the properties described in Swing Components Supporting Action. Subsequently, the button's properties are automatically updated as the Action's properties change.
So all the following properties can be impacted by setting an action:
enabled
toolTipText
actionCommand
mnemonic
text
displayedMnemonicIndex
icon (NA for JCheckBox)
accelerator (NA for JCheckBox)
selected

Reference a method from a different Java file

I'm new to Java programming and am facing a (most likely) easy problem that I don't seem to be able to get across nor understand.
I have three different java files, one where I create an interface (SimulatorGui.java), other where I am creating a panel to use on the jTabbedPanel created in the interface (CollisionPanel.java - CollisionPanel class) and a third one, where I run a code that will create the output needed (Collision.java - Colision class).
In the Collision.java main method, I am doing the following:
public static void main (String[] args) {
//<editor-fold defaultstate="collapsed" desc="Simulation start procedures">
Tally statC = new Tally ("Statistics on collisions");
Collision col = new Collision (100, 50);
col.simulateRuns (100, new MRG32k3a(), statC);
//</editor-fold>
new SimulatorGUI().setVisible(true);
CollisionPanel update = new CollisionPanel();
update.updatepanel();
The first block, will create the desired output. I then want to send that output to the updatepanel! I am not passing any arguments to the method as I am still trying to debug this. updatepanel method is created in the file CollisionPanel as following:
public void updatepanel(){
System.out.println ("debug");
jTextArea1.setText("update\n");
}
What happens then is that when I run the Collision.java file it will output the "debug" text but won't set the text to the jTextArea1 (append doesn't work aswell). I then created a button to try and do so and in that case it works. In CollisionPanel.java:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
updatepanel();
}
This does the trick! I've searched and tried different things but can't seem to understand why this won't work.
Thanks in advance for your help, hope I've made the problem clear!
Okay I think I've eventually got the problem, and it is because of the IDE, you see in your main method you initiate a new CollisionPanel, which is wrong, netbeans has already added and initiated that panel in the SimulatorGUI, so now what you need to do is add a get method in the SimulatorGUI to get the initiated panel, then call the update method on that panel.
So add this to SimulatorGUI:
public CollisionPanel getCollisionPanel1() {
return collisionPanel1;
}
replace your old updatePanel() method with:
void updatepanel(String str) {
System.out.println ("debug");
jTextArea1.setText(str);
// jTextArea1.revalidate();
jLabel1.setText("test");
}
after that change your main too look like this:
SimulatorGUI simulatorGUI = new SimulatorGUI();
simulatorGUI.setVisible(true);
CollisionPanel cp=simulatorGUI.getCollisionPanel1();
cp.updatepanel("Hi");
and dont forget to remove the old updatePanel() method call from your CollisionPanel constructor, because now you can simply call cp.updatePanel("text here"); in your SimulatorGUI class instead of calling it only in the constructor.
I hope this is easy to grasp, if you're unsure let me know
Where do you add your CollisionPanel to the main GUI? I fear that this is your problem, and that you need to do this for your code to work. In fact where do any of your three classes get a reference to the others? For different classes to work in a program, there must be some communication between them. Understand that if you create a CollisionPanel object inside the GUI, and create another CollisionPanel object inside of the main method, calling a method on one object will have no effect on the other since they are two completely distinct entities.
For example, this code:
new SimulatorGUI().setVisible(true);
CollisionPanel update = new CollisionPanel();
update.updatepanel();
It appears that you are in fact calling updatePanel() on a CollisionPanel, but it's not on any CollisionPanel that is visualized in your GUI.
Consider giving SimulatorGUI a method that allows one to pass the CollisionPanel into it so that it can use it. This may in fact be a constructor parameter:
CollisionPanel update = new CollisionPanel();
SimulatorGUI simulatorGUI = new SimulatorGUI(update);
update.updatePanel();
Meaning SimulatorGUI's constructor would have to look something like:
public SimulatorGUI(CollisionPanel update) {
this.update = update;
// add update to GUI somewhere
}
There are three different levels when developping a GUI:
The view: the graphical component
The Model: the code that you run
The controller: checks if are update on the model in order to refresh the view.
So When you first start your program, the view will have the value assigned in the code; for instance say you created your JTextArea with the initial value type here. The view will show the JTextArea with the text type here.
When a change is made to the model, the view is not aware of it, it is job of the controller to check for update on the model and then refresh the view.
So this:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
// TODO add your handling code here:
updatepanel();
}
Will generate an event that say a property has been modify. So the controller will update the view.
Other than that, the change will not appear on the view.
Hope this helps..

log4j in a JTextPane

I'm currently trying to make Log4J log into a JTextPane. I want to use a TextPane because I want basic highlighting (e.g. errors are red and infos are green).
I have two loggers set up, one (the root-logger) logs everything into a file and the other (guiLogger) logs only some errors and infos on the GUI in a JTextPane.
The problem I'm currently facing is, that I can't get appending to the TextPane to work. What I currently have looks something like this:
public class Log extends AppenderSkeleton{
private final JTextPane log;
private final StyledDocument doc;
public Log(){
super();
log = new JTextPane();
doc = log.getStyledDocument();
}
#Override
protected void append(LoggingEvent loggingEvent) {
try {
doc.insertString(doc.getLength(), "Hello World!", null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
public JTextPane getView(){
return log;
}
}
The Log4J config-file looks like this:
# The root-logger should log everything.
log4j.rootLogger = DEBUG, file
# Append the logs to a file.
log4j.appender.file = org.apache.log4j.RollingFileAppender
# [...]
# The logger which logs on the GUI (just some user-information).
log4j.logger.guiLogger = INFO, gui
# Append the logs to the GUI
log4j.appender.gui = mypackage.Log
# Formatting of the output:
log4j.appender.gui.layout = org.apache.log4j.PatternLayout
log4j.appender.gui.layout.ConversionPattern = %m%n
The append()-method gets called, and the insertString()-method performs clean (it does not enter the catch-block), but I don't see any content in the TextPane on the GUI.
What I have tried to fix this:
Execute the insertString()-method using SwingUtilities.invokeLater()
Execute the insertString()-method from a SwingWorker
Methods like validate(), revalidate() and repaint() on the JTextPane
Not using the global StyledDocument-object but directly getting it from the log-instance: log.getStyledDocument().insertString(0, "Hello World!", info_log);
The setText()-method of the JTextPane (only works in the constructor).
Since the JTextPane has no fireContentChanged()-method (or similar), I'm kind of lost here.
I have played around a bit more and found some other things:
The StyledDocument gets updated (calling getText() shows that the text has been inserted).
When I call the append() or insertString()-method directly from the compiler (after initializing the StyledDocument and the JTextPane), it all works fine.
Also, I checked which thread called the method by adding this to the append()-method body:
System.out.println("Thread: "+Thread.currentThread().getName());
It shows the following if I simply do two log-statements from somewhere in the code:
Thread: AWT-EventQueue-0
Thread: AWT-EventQueue-0
and when I call the append()-method directly from the constructor of the Log-class (plus the two logging-statements from above), it shows the following:
Thread: AWT-EventQueue-0
Thread: AWT-EventQueue-0
Thread: AWT-EventQueue-0
The first call appends the text probably. But the other two don't seam to work.
My GUI is build from the AWT-EventQueue by using SwingUtilities.invokeLater(). The two logging-calls are made in the same context (and therefore come from the EventQueue, too).
The text pane is private to the appender, and you don't have any getter for it. So I would guess that the GUI has a text pane, and the logger has another text pane it appends to.
That, or you're getting the text pane from a Log instance that is not the same as the Log instance that Log4j instantiates.
Also, the appender might be use by several threads, but the Swing components may only be accessed from the event dispatch thread. Every append to the text pane should be done inside a SwingUtilities.invokeLater() call.
Check whether yu don;t call setText() or setContentType() and may be some more methods which recreate Document. Instead of saving reference to the document get it from pane.
Not
doc.insertString(doc.getLength(), "Hello World!", null);
but
log.getStyledDocument().insertString(doc.getLength(), "Hello World!", null);

Drag&Drop with swing

I need some help. Is it possible to simulate a drag & drop without registering a component?
E.g. I click the mousekey anywhere on the window and hold the mousekey down, at this moment, I want to create or simulate a DragSourceEvent programmatically with Java.
Is this possible?
Update:
Regarding Bob's reply, at least I got it, I can create a listener for the drag & drop:
DragSource dragSource = new DragSource();
DragGestureListener listener = new DragGestureListener() {
public void dragGestureRecognized(DragGestureEvent event) {
event.startDrag (null, strSel) ;
...
}
}
listener.dragGestureRecognized(new DragGestureEvent(
new DragGestureRecognizer(dragSource, component) {
}, DnDConstants.ACTION_COPY, new Point(0,0), events ));
but unfortunately i get this exception:
java.lang.IllegalArgumentException:
source actions at
java.awt.dnd.DragSourceContext.(DragSourceContext.java:169)
at
java.awt.dnd.DragSource.createDragSourceContext(DragSource.java:454)
at
java.awt.dnd.DragSource.startDrag(DragSource.java:293)
at
java.awt.dnd.DragSource.startDrag(DragSource.java:403)
at
java.awt.dnd.DragGestureEvent.startDrag(DragGestureEvent.java:203)
any suggestions?
The question you asked:
I haven't tried it, but in theory you should be able to create the Event object and get a handle on the Swing Event Queue from one of the system classes. However without having a valid component, there may be problems when methods try to work with the event.
What you probably meant:
Registering events for a standard window -- you should be able to set up drag and drop support for an empty JPanel or JFrame, but it'll take some hacking. Drag & Drop is a pain to work with at this level when not built in -- I suggest using something like an invisible component or something instead.

Categories

Resources