log4j in a JTextPane - java

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);

Related

JTextPane not triggering UndoableEditListener events

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

JavaFX : After setting text in textArea, setting scroll to bottom in separate thread is not working

I created one JavaFX application where I'm updating log with one background process. So I'm setting log text in TextArea and setting scroll to bottom using logs.setScrollTop(Double.MAX_VALUE). but scrollbar is set to little bit up from the bottom.
I also tried TextFlow inside ScrollPan and setting scroll to bottom using logDisplay.setVvalue(1.0). It is also giving the same result.
Platform.runLater(() -> {
logs.setText([setting log text]);//TextArea logs
logs.setScrollTop(Double.MAX_VALUE));
});
//For TextFlow inside ScrollPane
Platform.runLater(() -> {
logs.setText([setting log text]);//Text logs
logDisplay.setVvalue(1.0);
});
I also tried to run code in separate thread like
new Thread() {
public void run(){
System.out.println("called set test");
logs.setText([setting log text]);//Text logs
logDisplay.setVvalue(1.0);
}
}.start();
But nothing is working :(
Can you help me what's wrong in this?
Thanks
--Edit--
Looks like the problem is because of threading issue. Scrollbar value is updating to the previous text value. Actually while retrieving scroll value it's not retrieving latest value but it's getting older value so scrollbar set to end of the previous message, not actual last line.
I don't know the actual problem of this issue, but I found an alternative solution.
I'm setting caret's position at end of text using length of text.
logs.setText(logText);
logs.positionCaret(logText.length());
It is working for me. :)

How do I update the appearance of my JButton when it's clicked?

I'm working on a Java7 Swing "wizard" type of project that needs to validate a web address before continuing on to the next step of the wizard. The validation requires accessing a URL over the internet to verify that expected resources are available. In some cases, this can take a few seconds which can be long enough to confuse a user.
As a quick solution to this, I would like to disable the "next" button and change the display text while the validation is running so the user knows that the wizard is working and not hung up on anything. The problem is that when I add the code to modify the JButton, none of the changes happen until after the validation has completed. This is the case even if I change the button and call revalidate() before I execute the validation methods.
Here is an excerpt of what I've tried:
// create next button
next = new JButton("Next", new ImageIcon(getClass().getResource("/navigate_right.png")));
next.setActionCommand("MYACTION");
next.addActionListener(new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
System.out.println("Is this the event dispatch thread? "
+ javax.swing.SwingUtilities.isEventDispatchThread());
System.out.println("Changing button");
next.setEnabled(false);
next.setText("Testing Connection");
next.getRootPane().revalidate();
System.out.println("Validating Service");
boolean isValidLocation = ServiceValidator.serviceExists(locationField.getText());
// ...etc...
When I run this code, the lines "Changing button" and "Validating Service" both get printed before the actual button changes in the display. How can I get the JButton to change before System.out.println("Validating Service"); is printed?
The problem is that when I add the code to modify the JButton, none of the changes happen until after the validation has completed.
Your code is executing on the EDT, so you long running code prevents the GUI from repainting itself until the task is finished executing. You need to use a separate Thread for the long running task, maybe a SwingWorker. Read the section from the Swing tutorial on Concurrency for more information.

A proper way to output text immediately in Java Swing GUI

Such a piece of code:
private void log(String message) {
LogBox.append(message + "\n");
}
private void log(Exception e) {
log(e.getMessage());
}
private void ConvertButtonActionPerformed(java.awt.event.ActionEvent evt) {
String url = UrlBox.getText();
if (url.isEmpty()) {
log("Empty URL");
return;
}
LogBox.setText("");
try {
log("URL "+url+" accepted. Trying to download...");
String content = URLConnectionReader.getText(url);
log("Downloaded. Parsing the content...");
//
} catch (Exception e) {
log(e);
}
}
should output each message to the LogBox (JTextArea) immediately after each log call, but outputs URL ... accepted only when URLConnectionReader.getText(url) finishes.
There were several ways do do an immediate output:
Application.DoEvents in Visual Basic 6 and .NET
Application.ProcessMessages in Delphi
Is there some simple way to do an immediate output? I was studying questions about the DoEvents and how to do this in Java, but I think that starting to learn Java from multi-threading isn't a right approach.
Create a SwingWorker to do the download: http://docs.oracle.com/javase/tutorial/uiswing/concurrency/worker.html
The role of an ActionListener is just that: to listen for user action, and initiate a response to that action by the program.
Sometimes, the program's response is very quick, and only involves the GUI. For example, in a calculator app, you could have a listener attached to an "equals" button that calculates the result of the current expression and writes it to a textbox. This can all be done within the listener method (although you might want to separate behavior for testing).
If the response to an user action is to initiate some long-running process, like downloading and parsing the file, then you don't want to do this within the listener body, because it will freeze the UI. Instead, gather any information (in your case, the URL value) from within the listener, and spin up a SwingWorker to handle the program's response.
In my comment, I suggested moving everything after the getText() into a SwingWorker. This is because, to me, the response is "download a file if you have a valid URL, and log the progress." And as I see it, testing for an empty string is part of that response. If you want to leave the empty-string test inside the listener, that's fine, but imo it's less testable.
You must leave the call to getText() inside the body of the listener, because you are only allowed to access Swing components from the event dispatch thread. If you moved that call into the worker, then it might access the textbox at the same time the textbox is updating itself, resulting in corrupt data.
Read up on Concurrency.
You should probably use a SwingWorker for the long running task, then you can publish results to the GUI as they become available.

How show my application delay with JProgressBar?

I wrote a simple application and I want show delay of it with JProgressBar Plese help me ;
I want show JProgressBar with Joptionpane , with a cancel button and it should be modal
this is my source code :
class CustomFrame extends JFrame {
private JProgressBar progressBar;
public CustomFrame() {
long start = System.currentTimeMillis();
myMethod();
this.getContentPane().setLayout(null);
this.setSize(200, 200);
//JOptionPane. ?????
this.setTitle("JFrame");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setVisible(true);
long end = System.currentTimeMillis();
System.out.print("\nTime: " + (end - start));
}
public void myMethod(){
try {
java.io.File file = new java.io.File("i://m.txt");
BufferedReader input =
new BufferedReader(new FileReader(file));
String line;
while ((line = input.readLine()) != null) {
if (line.indexOf("CREATE KGCGI=") != -1 ){
System.out.println(line);
}
}
input.close();
}
catch(Exception e){
e.printStackTrace();
}
}
Thanks ...
There are a couple things that you will need to do to get this to work:
You should be aware of threading issues in Swing. Your GUI painting should be done on the EventDispatchThread and disk I/O should be done in a worker thread. See this tutorial, the SwingWorker JavaDoc, and SwingUtilities.invokeLater for more detail
You will then want to get the size of your file (file.length())to determine how to scope your progress bar (myProgressBar.setMaximum(length))
When you iterate over the lines in your file, you will want to trigger an update to your progress bar (myProgressBar.setValue(myProgressBar.getValue()+lineLength)).
A couple points by way of critique:
your constructor shouldn't go off and do all of your work (ie load your file and pop up an option pane with the ability to cancel. the constructor should just do the work needed to create the object. you might want to consider having your constructor create your class, and then have the work that needs to be done to be called separately, or within something like an init() method.
It isn't clear what you are doing with the JFrame as superclass. JOptionPane is a class that will pop up a very basic modal dialog with some text, maybe an icon or input field. It isnt a panel that is embedded in a dialog.
As JOptionPane is a very basic construct for creating a basic message dialog, it might be easier to use a JDialog, which can also be made modal. JDialog will allow you to add buttons as you please, where as a standalone JOptionPane will require you to use Yes/No, or Yes/No/Cancel or OK/Cancel etc.
If you still want to use JOptionPane, and only show a cancel button, you can instantiate a JOptionPane (as opposed to using the utility show* methods), with the progressbar as the message, and the JOptionPane.CANCEL_OPTION as the optionType param. You will still need to put this into a JDialog to make it visible. See this tutorial for more details:
JOptionPane (constructor)
Creates a JOptionPane with the specified buttons, icons, message, title, and so on. You must then add the option pane to a JDialog, register a property-change listener on the option pane, and show the dialog. See Stopping Automatic Dialog Closing for details.

Categories

Resources