In Java2D when you use setOpaque I am a little confused on what the true and false does.
For example I know that in Swing Opaque means that when painting Swing wont paint what is behind the component. Or is this backwards? Which one is it?
Thanks
The short answer to your question is that "opaque" is defined in English as completely non-transparent. Therefore an opaque component is one which paints its entire rectangle, and every pixel is not at all translucent to any degree.
However, the Swing component opacity API is one of those mis-designed and therefore often mis-used APIs.
What's important to understand is that isOpaque is a contract between the Swing system and a particular component. If it returns true, the component guarantees to non-translucently paint every pixel of its rectangular area. This API should have been abstract to force all component writers to consider it. The isOpaque API is used by Swing's painting system to determine whether the area covered by a given component must be painted for components which overlap it and which are behind it, including the component's container and ancestors. If a component returns true to this API, the Swing system may optimize painting to not paint anything in that area until invoking the specific component's paint method.
Because of contractual implication of isOpaque, the API setOpaque should not exist, since it is actually incorrect for anything external to call setOpaque since, in turn, the external thing can't know whether the component in question will (or even can) honor it. Instead, isOpaque should have been overridden by each concrete component to return whether it actually is, in fact, opaque given its current properties.
Because the setOpaque API does exist, many components have mis-implemented it (quite understandably) to drive whether or not they will paint their "background" (for example JLabel and JPanel filling with their background color). The effect of this is to create an impression with users of the API to think that setOpaque drives whether or not that background should paint, but it doesn't.
Furthermore, if, say, you wish to paint a JLabel with a translucent background you need to set a background color with an alpha value, and do setOpaque(true), but it's not actually opaque - it's translucent; the components behind it still need to paint in order for the component to render properly.
This problem was exposed in a significant way with the Java 6's new Nimbus Look & Feel. There are numerous bug reports regarding transparent components filed against Nimbus (see stack overflow question Java Nimbus LAF with transparent text fields). The response of the Nimbus development team is this:
This is a problem [in] the orginal design of Swing and how it has been confusing for years. The issue is setOpaque(false) has had a side effect in [existing] LAFs which is that of hiding the background which is not really what it is [meant] for. It is [meant] to say that the component may have transparent parts and [Swing] should paint the parent component behind it.
So, in summary, you should not use setOpaque. If you do use it, bear in mind that the combination of some Look & Feels and some components may do "surprising" things. And, in the end, there is actually no right answer.
javadoc says : If true the component paints every pixel within its bounds. Otherwise, the component may not paint some or all of its pixels, allowing the underlying pixels to show through.
try this example program too...
http://www.java2s.com/Code/JavaAPI/javax.swing/JPanelsetOpaquebooleanisOpaque.htm
I think that the following also needs to be added:
The term opaque has different meanings in Java 2D and in Swing.
In Java 2D opacity is a rendering concept. It is a combination
of an alpha value and the Composite mode. It is a degree to
which the pixel colours being drawn should be blended with pixel
values already present. For instance, we draw a semi-transparent
rectangle over an existing oval shape. The oval is therefore
partially visible. This concept is often compared to light
going trough glass or water.
In Swing, an opaque component paints every pixel within its
rectangular bounds. A non-opaque component paints only a subset of
its pixels or none at all, allowing the pixels underneath it to
show through. The opaque property was set for efficiency reasons; Swing
does not have to paint areas behind opaque components.
Source: Java docs and Filthy Rich Clients
package com.zetcode;
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import static javax.swing.SwingConstants.CENTER;
import net.miginfocom.swing.MigLayout;
class DrawingPanel extends JPanel {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
doDrawing(g);
}
private void doDrawing(Graphics g) {
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.green);
g2d.fillOval(20, 20, 100, 100);
g2d.setColor(Color.blue);
g2d.setComposite(AlphaComposite.getInstance(
AlphaComposite.SRC_OVER, 0.1f));
g2d.fillRect(0, 0, 150, 150);
}
}
class MyLabel extends JLabel {
public MyLabel(String text) {
super(text, null, CENTER);
}
#Override
public boolean isOpaque() {
return true;
}
}
public class OpaqueEx2 extends JFrame {
public OpaqueEx2() {
initUI();
}
private void initUI() {
JLabel lbl1 = new JLabel("Java 2D opacity");
JLabel lbl2 = new JLabel("Swing opaque");
DrawingPanel dpanel = new DrawingPanel();
MyLabel mylbl = new MyLabel("isOpaque()");
mylbl.setBackground(Color.decode("#A9A9A9"));
createLayout(lbl1, lbl2, dpanel, mylbl);
setTitle("Opaque");
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
private void createLayout(JComponent... arg) {
JPanel pnl = new JPanel(new MigLayout("ins 10"));
pnl.add(arg[0], "w 150");
pnl.add(arg[1], "w 150, wrap");
pnl.add(arg[2], "w 150, h 150");
pnl.add(arg[3], "w 150, h 150");
add(pnl);
pack();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
OpaqueEx2 ex = new OpaqueEx2();
ex.setVisible(true);
}
});
}
}
In the code example, we have two components. The component on the left is a panel which uses AlphaComposite to paint a highly translucent rectangle over an oval. The component on the right is a label. Labels are non-opaque in most look and feels. We overwrite the label's isOpaque() method to set a gray background.
Related
I have a problem using transparent backgrounds in Swing.
There are a lot of artefacts produced as swing is not repainting changed regions.
As far as I can tell there are 2 out-of-the-box ways to use transparent backgrounds:
an opaque component with transparent color set as background (left txt field)
Problem: the transparent part of the background is never refreshed -> Artefacts.
an non-opaque component with transparent color set as background (right txt field)
Problem: background is not being drawn at all.
What I do not want to do:
to use timers to auto repaint the frame (super awful)
to override paintComponent method (which actually works, but is really really awful)
I am running on Win7 x64
Aaaand here is my SSCCEEE:
Update 1: init with invokeLater (still won't work)
public class OpacityBug {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new OpacityBug();
}
});
}
static final Color transparentBlue = new Color(0f, 1f, 0f, 0.5f);
JFrame frame;
JPanel content;
JTextField txt1;
JTextField txt2;
public OpacityBug() {
initFrame();
initContent();
}
void initFrame() {
frame = new JFrame();
frame.setSize(300, 80);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void initContent() {
content = new JPanel();
content.setDoubleBuffered(true);
content.setBackground(Color.red);
frame.getContentPane().add(content);
txt1 = new JTextField() {
#Override
public void setBorder(Border border) {
super.setBorder(null); //nope border
}
};
txt1.setText("Hi! I am Buggy!");
txt1.setOpaque(true);
txt1.setBackground(transparentBlue);
content.add(txt1);
txt2 = new JTextField() {
#Override
public void setBorder(Border border) {
super.setBorder(null); //nope border
}
};
txt2.setText("And I have no BG!");
txt2.setOpaque(false);
txt2.setBackground(transparentBlue);
content.add(txt2);
content.revalidate();
content.repaint();
}
}
Update 2
As some of you noticed, it seems Swing that swing is unable to paint transparent backgrounds.
But it is not (yet) clear to me why, I searched for the piece of code responsible for drawing the background of the component and have found the following code in ComponentUI.java :
public void update(Graphics g, JComponent c) {
if (c.isOpaque()) {
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(),c.getHeight());
}
paint(g, c);
}
As you can see, it assumes that if a component is not opaque the background doesn't need to be repainted. I say that this is a very vague assumption.
I would propose following implementation:
public void update(Graphics g, JComponent c) {
if(c.isOpaque() || (!c.isOpaque() && c.isBackgroundSet())) {
g.setColor(c.getBackground());
g.fillRect(0, 0, c.getWidth(), c.getHeight());
}
paint(g, c);
}
I simply check if the background was set at all when the component is not opaque.
This simple addition would allow us to use transparent backgrounds in swing.
At least I do not know any reasons why it should not be done that way.
By using a transparent background you are breaking Swings painting rules. Basically when the component is opaque you promise to paint the background of the component. But because the background is transparent there is nothing to paint.
Check out Backgrounds With Transparency for more information and a couple of simple solutions.
You should respect Swing's threading policy and have the GUI initialized on the GUI thread:
SwingUtilities.invokeLater(() -> new OpacityBug());
I can't tell whether this is all it will take to correct the behavior on your side, but it has done just that on mine (OS X).
I cannot stress this enough, I see a ton of people not doing this while it is vital to ensure correct behavior from Swing; ALL GUI instances must run on the EDT(Event Dispatch Thread)
Please read the below article and adjust your code and report back with the effects.
https://docs.oracle.com/javase/tutorial/uiswing/concurrency/dispatch.html
You should use setOpaque(false) on the component that has a problem, and do the same with all the parents !
For example, if you have a JList 'jList' inside a JScrollPane 'scrollPane', the whole thing being inside a JPanel 'jPanel', you should use :
jList.setOpaque(false);
scrollPane.setOpaque(false);
scrollPane.getViewport().setOpaque(false);
jPanel.setOpaque(false);
And yes, if you have a JScrollPane, you should set its viewport's opacity to false as well.
This will prevent painting problems on your components with transparent backgrounds.
I am creating a wrapper for Notch's "Prelude of the Chambered", and I wish to make it so that you can resize the window. I can not figure out how I could make the contents scale, I have tried to override onPaint. I can not modify the Jar to get this done.
onPaint attempt:
package com.gudenau.pc.poc;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class JScaledFrame extends JFrame {
private static final long serialVersionUID = 4044340683411982494L;
public JScaledFrame(String title) {
super(title);
}
#Override
public void paint(Graphics graphics){
Dimension min = getMinimumSize();
Dimension size = getSize();
BufferedImage image = new BufferedImage(min.width, min.height, BufferedImage.TYPE_4BYTE_ABGR);
Graphics subGraphics = image.getGraphics();
super.paint(subGraphics);
subGraphics.dispose();
graphics.drawImage(image, 0, 0, size.width, size.height, null);
}
}
Scaling the content is very difficult. What you want to is change the scaling context of the Graphics context painting the underlying component.
Typically, what you would do, is override the paint method of the offending component and apply the scaling factor you want...
public class MyExtendedClass extends ... {
#Override
public void paint(Graphics g) {
Graphics2D g2d = (Graphics2D)g.create();
double scaleFactor = 1d;
// Calculate the scaling factor to apply
// based on the "default" size and the
// current size...
g2d.scale(scaleFactor, scaleFactor);
super.paint(g2d);
g2d.dispose();
}
}
This could introduce all kinds of weirdness and frankly a more robust solution might be to use JXLayer/JLayer, for example...
Zooming JLayeredPane via JLayer and the LayerUI
How to add MouseListener to item on Java Swing Canvas
You may also find using an AffineTransform easier...
Do not paint on top level container such as JFrame. Use JComponent or JPanel. Override paintComponent() for painting rather than paint() and don't forget to call super.paintComponent(g).
Do painting in paintComponent nothing else. Avoid putting extra program logic or unnecessary allocations into that method. Painting operations should be fast and optimized for better performance and user experience.
Also, avoid null image observers when calling drawImage. JPanel for instance implements ImageObserver, so you can pass this if extending JPanel.
See Performing Custom Painting for more information. Also see Painting in AWT and Swing.
Since it seems as though this will be hard to do with normal Java I will implement some dark magic with the ASM libraries. I was hoping it would not need to be done this way, but it seems as though scaling a canvas that is loaded from an external jar is not possible.
I have a JScrollPane displaying (as its viewport view) MyPanel, a subclass of JPanel.
MyPanel implements custom painting by overloading paintComponent. The total size of the displayable content of MyPanel is generally quite wide (meaning 50x to 200x wider than the size of the JScrollPane viewport) and using a Timer, I scroll horizontally to view different sections of the underlying MyPanel. I also allow using the scroll bar thumb to manually seek to a specific area of MyPanel.
In my paintComponent implementation I am currently finding the portion of MyPanel that is currently visible in the view port using JViewport#getVisibleRect, and just painting that portion each time the view port position is changed.
This works fine - but I end up repainting a significant percentage of the visible portion of MyPanel over and over as the timed scrolling only moves the view port 1/50 of the view port width at a time. Also, I generally end up scrolling through the entire horizontal extent of MyPanel, so I have to paint it all at least once anyway.
That leads me to think about painting the entire contents of MyPanel just once (to a BufferedImage?) and then letting JScrollPane (or JViewport) handle clipping and blitting only the needed area of the BufferedImage.
Intuitively this seems to me to be the most efficient way of handling this, and something that would be relatively common.
As I investigate the Swing tutorials and other sources, I learn that Swing is already double buffered. If I try to force this on my own brute-force, independent of Swing functionality, it sounds like I'll end up with triple-buffering.
I haven't found the recipe (if it exists) to exploit JScrollPane to do this for me.
Is there an example available, or some direction as to how to do this (if possible)?
Swing automatically paints only the smallest necessary area of components for you. repaint(4 args) is only useful when the component is partially changed and you don't want the entire visible area to be repainted. And in your practical, it has the same effect as repaint(no-args).
The auto-clipped area is already small enough for visibility concerns as you described in your question. And you can configure it in your program.
Also, you don't need to worry about the scrolling -- JScrollPane invokes repaint of its children automatically.
You can easily experiment on these:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
public class Test extends JFrame {
private Random rnd = new Random();
private Color c = Color.WHITE;
public Test () {
final JPanel pnl = new JPanel() {
#Override
public void paintComponent (Graphics g) {
super.paintComponent(g);
g.setColor(c);
g.fillRect(0, 0, getWidth(), getHeight());
Rectangle r = g.getClipBounds();
System.out.println(r.width + ", " + r.height);
}
};
pnl.setPreferredSize(new Dimension(10000, 10000));
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(400, 400);
setLocationRelativeTo(null);
add(new JScrollPane(pnl));
setVisible(true);
new Thread() {
#Override
public void run () {
while (true) {
c = new Color(rnd.nextInt(0xffffff));
pnl.repaint();
try {
Thread.sleep(100);
} catch (InterruptedException e) {}
}
}
}.start();
}
public static void main (String args[]) {
new Test();
}
}
Ok I'm wondering why the code below will not display the JLabel.
MyPanel is getting added correctly to a JFrame and everything because it all displays but will not draw the JLabel. Any help is appreciated.
public class MyPanel extends JPanel {
private Root root;
...
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
root.paint(g2);
}
}
class Root {
private Node1 node1;
...
public void paint(Graphics g) {
node1.paint(g);
}
}
class Node1 {
...
public void paint(Graphics g) {
JLabel jtp = new JLabel();
jtp.setLocation((int) x, (int) y);
jtp.setSize((int) width, (int) height);
jtp.setLocation(40, 40);
jtp.setSize(40, 40);
jtp.setText("Hello world");
//jtp.setVisible(true);
jtp.paint(g);
}
}
I suggest that you don't add Components to a Container in a paint method as 1) you do not have absolute control when or even if a paint method will be called and 2) paint and paintComponent have to be as blazing fast as possible, and this is not the time or place to update a GUI. 3) Since paint is often called many times, you will be adding components many times to your container, and all out of your direct control.
Also, while you're adding a component into Root (whatever Root is since it doesn't extend JComponent, JPanel, or similar) in the paint method, the Root object is never added to anything else that I can tell, and so it makes sense that nothing "added" to a component that is not added eventually to a top-level window will be visible.
Bottom line: I think you need a gui re-design as your solution. If you tell us more about it we can help you with it. Next we'll need to talk about use of layout managers and why setting absolute position and sizes of components is usually frowned on.
If anything I say is confusing, please ask for clarification, or if anything is wrong, please help me correct it!
You should not create your JLabel inside the paint method - instead, create it once when initializing your MyPanel. Your label is kind of a renderer component for your nodes, which in principle is a good thing. You may look how the renderers for JTable, JList, JTree work.
In your case, don't set the location of your label (it does not change anything, since it's paint-method expects its graphics object to be oriented by its own upper left corner), instead translate the Graphics-context:
Graphics copy = g.create((int)x, (int)y, (int)width, (int)height);
jtp.paint(copy);
(Graphics2D has some more fancy methods for shifting, rotating, scaling the context, too.)
Other than this, I don't see any problems. Make sure your Node1.paint() method gets actually called by putting some System.out.println() in there.
Is there a way to create a JButton with your own button graphic and not just with an image inside the button?
If not, is there another way to create a custom JButton in java?
When I was first learning Java we had to make Yahtzee and I thought it would be cool to create custom Swing components and containers instead of just drawing everything on one JPanel. The benefit of extending Swing components, of course, is to have the ability to add support for keyboard shortcuts and other accessibility features that you can't do just by having a paint() method print a pretty picture. It may not be done the best way however, but it may be a good starting point for you.
Edit 8/6 - If it wasn't apparent from the images, each Die is a button you can click. This will move it to the DiceContainer below. Looking at the source code you can see that each Die button is drawn dynamically, based on its value.
Here are the basic steps:
Create a class that extends JComponent
Call parent constructor super() in your constructors
Make sure you class implements MouseListener
Put this in the constructor:
enableInputMethods(true);
addMouseListener(this);
Override these methods:
public Dimension getPreferredSize()
public Dimension getMinimumSize()
public Dimension getMaximumSize()
Override this method:
public void paintComponent(Graphics g)
The amount of space you have to work with when drawing your button is defined by getPreferredSize(), assuming getMinimumSize() and getMaximumSize() return the same value. I haven't experimented too much with this but, depending on the layout you use for your GUI your button could look completely different.
And finally, the source code. In case I missed anything.
Yes, this is possible. One of the main pros for using Swing is the ease with which the abstract controls can be created and manipulates.
Here is a quick and dirty way to extend the existing JButton class to draw a circle to the right of the text.
package test;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import javax.swing.JButton;
import javax.swing.JFrame;
public class MyButton extends JButton {
private static final long serialVersionUID = 1L;
private Color circleColor = Color.BLACK;
public MyButton(String label) {
super(label);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Dimension originalSize = super.getPreferredSize();
int gap = (int) (originalSize.height * 0.2);
int x = originalSize.width + gap;
int y = gap;
int diameter = originalSize.height - (gap * 2);
g.setColor(circleColor);
g.fillOval(x, y, diameter, diameter);
}
#Override
public Dimension getPreferredSize() {
Dimension size = super.getPreferredSize();
size.width += size.height;
return size;
}
/*Test the button*/
public static void main(String[] args) {
MyButton button = new MyButton("Hello, World!");
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 400);
Container contentPane = frame.getContentPane();
contentPane.setLayout(new FlowLayout());
contentPane.add(button);
frame.setVisible(true);
}
}
Note that by overriding paintComponent that the contents of the button can be changed, but that the border is painted by the paintBorder method. The getPreferredSize method also needs to be managed in order to dynamically support changes to the content. Care needs to be taken when measuring font metrics and image dimensions.
For creating a control that you can rely on, the above code is not the correct approach. Dimensions and colours are dynamic in Swing and are dependent on the look and feel being used. Even the default Metal look has changed across JRE versions. It would be better to implement AbstractButton and conform to the guidelines set out by the Swing API. A good starting point is to look at the javax.swing.LookAndFeel and javax.swing.UIManager classes.
http://docs.oracle.com/javase/8/docs/api/javax/swing/LookAndFeel.html
http://docs.oracle.com/javase/8/docs/api/javax/swing/UIManager.html
Understanding the anatomy of LookAndFeel is useful for writing controls:
Creating a Custom Look and Feel
You could always try the Synth look & feel. You provide an xml file that acts as a sort of stylesheet, along with any images you want to use. The code might look like this:
try {
SynthLookAndFeel synth = new SynthLookAndFeel();
Class aClass = MainFrame.class;
InputStream stream = aClass.getResourceAsStream("\\default.xml");
if (stream == null) {
System.err.println("Missing configuration file");
System.exit(-1);
}
synth.load(stream, aClass);
UIManager.setLookAndFeel(synth);
} catch (ParseException pe) {
System.err.println("Bad configuration file");
pe.printStackTrace();
System.exit(-2);
} catch (UnsupportedLookAndFeelException ulfe) {
System.err.println("Old JRE in use. Get a new one");
System.exit(-3);
}
From there, go on and add your JButton like you normally would. The only change is that you use the setName(string) method to identify what the button should map to in the xml file.
The xml file might look like this:
<synth>
<style id="button">
<font name="DIALOG" size="12" style="BOLD"/>
<state value="MOUSE_OVER">
<imagePainter method="buttonBackground" path="dirt.png" sourceInsets="2 2 2 2"/>
<insets top="2" botton="2" right="2" left="2"/>
</state>
<state value="ENABLED">
<imagePainter method="buttonBackground" path="dirt.png" sourceInsets="2 2 2 2"/>
<insets top="2" botton="2" right="2" left="2"/>
</state>
</style>
<bind style="button" type="name" key="dirt"/>
</synth>
The bind element there specifies what to map to (in this example, it will apply that styling to any buttons whose name property has been set to "dirt").
And a couple of useful links:
http://javadesktop.org/articles/synth/
http://docs.oracle.com/javase/tutorial/uiswing/lookandfeel/synth.html
I'm probably going a million miles in the wrong direct (but i'm only young :P ). but couldn't you add the graphic to a panel and then a mouselistener to the graphic object so that when the user on the graphic your action is preformed.
I haven't done SWING development since my early CS classes but if it wasn't built in you could just inherit javax.swing.AbstractButton and create your own. Should be pretty simple to wire something together with their existing framework.