JPanel Obscures BufferedImage - java

I'm having trouble finding out why the following problem happens: In a program that uses "extends Frame" to create a window, I can use BufferedImage to draw to the graphics context of the Frame (not JFrame), and it looks just fine. However, the moment I declare a JPanel, all of the text drawn by BufferedImage becomes obscured (not completely, but semi-transparent and hard to read), even if I don't add the JPanel to the JFrame.
Here's a simplified version of the code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.Timer;
import java.awt.image.*;
import javax.swing.*;
public class MyProgram extends Frame {
static Frame f;
static Timer timer;
public static void main(String[] args) {
f = new Frame();
f.setSize(400, 200);
f.setResizable(false);
f.setVisible(true);
f.addKeyListener(new KeyAdapter() {
public void keyPressed(KeyEvent e) {
drawScreen();
}
});
drawScreen();
}
public static void drawScreen() {
BufferedImage off_i = new BufferedImage(f.getWidth(), f.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics g = off_i.createGraphics();
g.setColor(new Color(50, 50, 50));
g.drawString("Hit any key; why does this text change?", 15, f.getHeight() - 10);
Graphics off_g = f.getGraphics();
off_g.drawImage(off_i, 0, 0, null);
JPanel panel = new JPanel();
}
}
I could maybe understand seeing the problem arise if I had added the JPanel to the JFrame and didn't set any bounds to its visibility, but even creating the JPanel gives that issue, which seems weird to me.
Basically, what I'm trying to do here is take an existing program that I have that runs just fine without JPanel, and I want to add to it a JTextArea so that I can accept copy/paste text for modifying the displaying of the program.
My understanding of Java is kind of spotty, as I learned it mainly by hobby and not formally, but I'm always looking to learn more when I can. Thanks for the help!
Update: I discovered that this problem only happens when the draw function is called again, after the JPanel has been declared, though I still don't understand why it does that or how to get around it.

better would be put Image to the JLabel and how ot use Icon
please read Using Swing Components and how to LayoutManagers works
tons examples on java2s.com

Don't mix AWT components with Swing component. That is you should use a JFrame NOT a Frame.
Don't use getGraphics(). Custom Painting is done by overriding the paintComponent() method of a JPanel (or JComponent). You just use the Graphics object that is passed to the method. Then you add the panel to the frame.
As already mentioned using a JLabel is simpler because you don't have to do any custom painting. The tutorial also has a section on "How to Use Icons".

I tried to run your code. And although the effect that you are describing does not happen on my system I can recommend you something.
First try to create your panel before it is visualized. In this case java does not have to re-arrange the components that are already on screen.
Second, if you have to draw things on visible frame call validate() of the container when you are done. This makes java to re-arrange stuff.
Third, when you are using drawXXX() methods create your own class that extends Component, JComponent, Canvas and override its `paint(Graphics) method. In this case the system will call this method every time it needs (e.g. when window is resized) and your UI will be painted again.
BTW I have 2 questions:
why are you using drawText() instead
of Label or JLabel? Use them and
avoid such kind of problems.
Why do you extend your class from Frame and do not use this fact but create yet another instance of Frame?

As an answer to my original question:
It seems that initializing JPanel alongside awt draw() commands causes the text to be antialiased, which makes the text look harder to read, partially obscured, thinner, etc. Although I tried setRenderingHint() with VALUE_TEXT_ANTIALIAS_OFF, it did not solve the problem. But as other posters pointed out it's not best practice to mix the two components.
While this doesn't exactly solve my problem, it does answer the question of what is going on, that being text antialiasing as some result of JPanel (does that sound right?). Ideally I wouldn't want to rewrite all of the code just to add a single JTextArea into an already existing codebase. But perhaps it's good every now and then to revisit old code and revamp it where it may be faulty.
Thanks everyone for the comments, information, and resource links!

Related

Creating a method to handle all Types of Swing Components

In order to save some time when setting the size of Swing Components (JPanel, JLabel, etc.), I am trying to write a method that when called, will set that component's size using 3 of the standard size-defining functions.
I would like to be able to do this:
import CustomComponents;
...
JPanel xPanel = new JPanel();
CSize.setDefiniteSize(xPanel, 400, 300);
...
Or (and preferably)
...
JPanel xPanel = new JPanel();
xPanel.CSize.setDefiniteSize(400, 300);
...
Here's the code I have so far. The problem is, I want the method to be functional for all types of Swing components, and it seems to only work for the one I specify in the heading.
package CustomComponents;
import javax.swing.JComponent;
import java.awt.Dimension;
public class CSize
{
public static void setDefiniteSize(JComponent c, int h, int w)
{
Dimension k = new Dimension (h, w);
c.setMaximumSize(k);
c.setMinimumSize(k);
c.setPreferredSize(k);
}
}
As you can see, here I tried using JComponent, but it won't work, as whatever I use it with - say a JPanel - is a JPanel, not a JComponent (even though it's a subclass? I still don't get how this works)
Though what you're trying to do, you may think is crafty, but you may find it later to be discouraging trying to size everything
See this thread Should I avoid the use of set(Preferred|Maximum|Minimum)Size methods in Java Swing?
Instead you should make use of LayoutManagers. and .pack() the frame. Swing was made to be used with LayoutManager. You need to take into account that your program may look good on your development screen, but may look different on others. You will also find out that many LayoutManegers will not even respect your preferred size, so you need to learn which ones do and which ones don't. Here's a simple example
With layout managers, you let the layouts do all the sizing for you. Some layouts will stretch your components, some center them in open spaces. Your program will be a lot more fluid if you use them.
See Laying out Components Within a Container for more details on how you can use them.
You have to use java.awt.Container, because JFrame is not a direct subclass of JComponent!
So it should work with the following code:
package CustomComponents;
import java.awt.Container;
import java.awt.Dimension;
public class CSize {
public static void setDefiniteSize(Container c, int h, int w) {
Dimension k = new Dimension (h, w);
c.setMaximumSize(k);
c.setMinimumSize(k);
c.setPreferredSize(k);
}
}
Look at the hierarchy of the components:
java.awt.Component
|--java.awt.Container
|--java.awt.Window
| |--java.awt.Frame
| |--javax.swing.JFrame
|--javax.swing.JComponent
|--javax.swing.JLabel
Even better would be using java.awt.Component, since this class is defining all methods you want to use (setMinimumSize, setMaximumSize and setPreferredSize).

Why isn't my string being displayed in the window?

I am trying to create a simple application that shows a red circle that when clicked displays different messages under it. I believe that this part of the code:
g.drawString("DO NOT PRESS", 100, 100);
is coded correctly but no text is displayed on the window that opens. Here is the full code so far:
import java.awt.Graphics;
import javax.swing.JFrame;
public class BigRedButton extends JFrame {
public BigRedButton() {
setTitle("Big Red Button");
setSize(500, 500);
setResizable(false);
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public void graphics(Graphics g) {
g.drawString("DO NOT PRESS", 100, 100);
}
public static void main(String[] args){
new BigRedButton();
}
}
There is no such method graphics in JFrame, so nothing is calling it.
You should avoid painting directly to top level containers, apart from everthing else, they're not double buffered and will flicker when painted. You should, instead, create a custom component (extending from something like JPanel) and override it's paintComponent method.
You should take the time to read through Performing Custom Painting, Painting in AWT and Swing and 2D Graphics
Also, while your reading up, you should have a read through Initial Threads
Amendment
As pointed out by Andrew, you should use the #Override annotation to ensure that the method you think you are overriding is actually the method being overridden in the first place. This would stop the program from being compiled and save lots of lost time trying to figure out why things aren't working the way you expect them.

Invisible JFrame/JTable how much faster?

I have a swing app. with a jframe with lots of internal frames containing large JTable.
Those jtables get updated continuously so there is lots of repainting going on.
in some circumstances I can simply keep the JFrame invisible. (frame.setVisible(false))
I was wondering if anybody knows if I will gain something in terms of performance
(something considerable or not)
such as 50% gain or you would only get 2% gain...
and maybe some sort of explaination on what to expect.
thanks
p.s.
Another way to rephrase the question is:
Are swing components clever enough not to repaint/reflow theirselves if not visible ???
Take a look at the Swing Painting Guidelines which have some useful tips on painting efficiency. For example:
On components with complex output,
repaint() should be invoked with
arguments which define only the
rectangle that needs updating, rather
than the no-arg version, which causes
the entire component to be repainted.
Components which render complex output
should make smart use of the clip
rectangle to narrow the drawing
operations to those which intersect
with the clip area.
It's also quite easy to prove that non-visible components are not painted. The following code hides a panel and prints out if paint is called.
public static void main(String args[]) throws Exception {
JFrame f = new JFrame();
final JPanel p = new JPanel(){
public void paint(Graphics g){
super.paint(g);
System.out.println("IN PAINT");
g.fillRect(10, 10, 20, 20);
}
};
f.setLayout(new BorderLayout());
f.add(p, BorderLayout.CENTER);
JButton b = new JButton("OK");
b.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("PRESSED");
p.setVisible(!p.isVisible());
}
});
f.add(b, BorderLayout.SOUTH);
f.setSize(100,100);
f.setVisible(true);
}
I believe you should temporarily disconnect the TableModel from the JTable while you are not displaying the JTable.
The biggest problem you face is that the JTable reacts to model change events and continuously repaints itself.
It is also possible that the JTable is intelligent enough to not repaint itself while not visible (but has model changes) but I wouldn't put my money on this.
The best way to solve this question is to test both. If you profile both options and see no gain then you try a different route like sola suggested. Just make sure you are not optimizing the wrong part of your application.
I can't give you any numbers on how much performance gain you will see, this depends on too many factors and is best left up to your own profiling. However, if your component is not visible, then the paintComponent() (or repaint()) method won't be called either as far as I know.
If your JTable is in a JScrollPane it seems it will repaint at each line adition (and call to fireTableRowsInserted of your model). Thus because the scrollpane will update it scollbars and then its contained component (JTable). If your table is not in a scrollpane then it will redraw only the shown lines. Therefore if your lines are not shown you will not trigger a repaint... Stupide. I'm looking for a way to avoid this.

Changing The Underlying Background Color Of A Swing Window

As discussed here, when resizing a Swing application in Vista (and Windows 7, which is what I'm using) you get a black background in the right/bottom corner while Swing's repaint catches up to the changes.
Playing with other applications (Windows Explorer (Native), Firefox (C++?), and Eclipse (Java)) I notice that they all have this same problem - contrary to what the folks in the link above say - but they minimize the problem by having a grey fill color, which is far less visually jarring than the black that appears in Swing.
I'm wondering if there's some way to change this so that Swing behaves like these other applications? I tried setting the background color of the JFrame, but to no avail.
Additional Info
Jonas discovered (see their informative answer below) that this is an issue with JFrames, but not AWT Frames - maybe that will help someone figure this out.
I have noticed the same problem. This color is gray at IE, in Opera it's black, and in Eclipse it's gray. It seam to be more visible in Swing, because it seam to be little slower at repainting and the color is as you said, black. This problem is more visible if you use the upper left corner to resize.
I coded an example and tried to understand where this black color is defined. A JFrame has many layers, so I set a different background on every layer.
import java.awt.Color;
import javax.swing.JFrame;
public class BFrame {
public static void main(String[] args) {
new JFrame() {{
super.setBackground(Color.CYAN);
this.getRootPane().setBackground(Color.BLUE);
this.getLayeredPane().setBackground(Color.RED);
this.getContentPane().setBackground(Color.YELLOW);
this.setSize(400,340);
this.setVisible(true);
}};
}
}
But this example didn't help. And maybe the color is set by a superclass to Frame.
java.lang.Object
java.awt.Component
java.awt.Container
java.awt.Window
java.awt.Frame
My teory is that, since Swing paints itself, but uses a native Window, then is the native background painted before the resize, and the background of Swing is painted after the resize. But for native applications, the background is painted before the resize.
UPDATE: I tried with a Frame now, and it's not having the same problem. The background seam to be painted before the resize.
import java.awt.Color;
import java.awt.Frame;
public class B2Frame extends Frame {
public static void main(String[] args) {
new Frame() {{
setBackground(Color.YELLOW);
setSize(400,340);
setVisible(true);
}};
}
}
The frame is responsible for painting its background so you need to make sure you let it do its job.
You demonstrate this by setting:
System.setProperty("sun.awt.noerasebackground", "true");
This will cause the background to always be black on resize expansions. (So don't do it.)
The following worked for me:
(AWT only) Set up double buffering using createBufferStrategy(2) - wrapped in addNotify() otherwise you'll run into exceptions during Frame creation
(Step 1 is only necessary in AWT as Swing is double buffered by default.)
Always (important) call super() in your Frame.paint() implementation
Set the background colour with setBackground() then the background should always be the same colour when expanding the frame
Sample code:
class InnerFrame extends Frame {
public void addNotify() {
super.addNotify();
// Buffer
createBufferStrategy(2);
strategy = getBufferStrategy();
}
public void paint(Graphics g) {
super(g);
//...
}
//...
}
I also noticed this. For me this issue was solved with changing layout manager (I've used Free Form Layout before) and it worked pretty well (system color painting).
But eventually I switched back to FFL. Also some well known apps face this problem (f.e. SKYPE), but I actually don't mind it ...

only JLabel not showing up

I am writting a simple application which has a button that opens a new window then display a simple GUI/Text to acccept inputs from a user. but for some reason, I can get JLabel to be displayed on the new window. The application has following structure:
+mainFrame - JFrame
+newFrame - JFrame
-+newPanel - JPanel
----title - JLabel
----submitButton -JButton
...
Buttons and textfields all display fine, but Jlabels won't show up at all. I have tried using different layouts and all but I still can't get it shown. JLabels inside mainFrame tree, works fine.. so it seems like the problem is due to newFrame declaration or something, but then button should not be displayed either. Well, I am kindda lost and can someone suggest me what I should check?
Thanks : )
Make sure you do frame.pack() before you make it visible.
It can also help to set borders on different components (in different colours) for debugging just to see which components are/aren't turning out with size 0, in order to narrow down your problem. Logging, or breakpointing the component's setSize method, can help too.
Apart from that, maybe post some sample code? At the moment, you're question is fairly vague to answer.
Firstly, do you know about JDialog, and JOptionPane - these classes are often a better way of showing another popup window. It is quite rare to use 2 JFrames, (though sometimes a sensible thing to do).
Secondly have you done pack() and setVisible(true)?
The code below works fine for me. Either this breaks for you and it is something about your Java implementation, or you must be doing something different, in which case can you tell us what it is:
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
public class JLabelShower {
public static void main(String [] args) {
JFrame mainFrame = new JFrame("main frame");
JButton popup = new JButton("start new frame");
mainFrame.getContentPane().add(popup);
mainFrame.pack();
mainFrame.setVisible(true);
popup.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
JFrame newFrame = new JFrame("new frame");
JPanel newPanel = new JPanel();
JLabel title = new JLabel("title");
newPanel.add(title);
newFrame.setContentPane(newPanel);
newFrame.pack();
newFrame.setVisible(true);
}
});
}
}
In case you are using the JLabel as a placeholder, i.e. initialize it with an empty string and set the text later:
Since the JLabel's size gets calculated when the panel gets layed out (i.e. early on) and is based on the contained text, you'll probably end up with a label thinking it has a preferred size of (0, 0).
In this case you should tell the label what size it should ask for by calling setPreferredSize with an appropriate value.
And another cause might be the layoutmanager you are using in the surrounding panel. Maybe you are adding the label and the button in the same place, e.g. BorderLayout.CENTER. That would explain why only one of the two gets displayed.
Set the opacity of the JLabel object to true using title.setOpaque(true) . It will paint every pixel within bound of the JLabel object. This solved my problem of same type.

Categories

Resources