Using JSplitPane with an AWT component - java

I have an AWT canvas which I cannot convert to a Swing component (it comes from VTK). I wish to display a few of these canvases inside of a JSplitPane. I've read about mixing heavy and light weight components in Java and know that it's a pain in the butt, but I don't have a choice. If I wrap the AWT canvas inside of a JPanel and then put that on the split pane the split pane doesn't function at all. However, if I put the AWT canvas inside of a JPanel and then that inside of a JScrollPane and then those scroll panes on the JSplitPane the split pane does function, but the AWT canvas components don't resize properly. I'm lost about how to get the AWT canvas components to resize properly when the JSplitPane's divider is moved. I can catch the divider moving operation and operate on the AWT canvases at that time, but I don't know what to do. I've tried calling invalidate() then validate() then repaint(), but that didn't work.
Any ideas?
Here's a example of the problem
import javax.swing.*;
import java.awt.*;
public class SwingAWTError {
public static void main(String[] args) {
Canvas leftCanvas = new Canvas();
Canvas rightCanvas = new Canvas();
leftCanvas.setBackground(Color.RED);
rightCanvas.setBackground(Color.BLUE);
JPanel leftPanel = new JPanel();
JPanel rightPanel = new JPanel();
leftPanel.setLayout(new BorderLayout());
rightPanel.setLayout(new BorderLayout());
leftPanel.add(leftCanvas, BorderLayout.CENTER);
rightPanel.add(rightCanvas, BorderLayout.CENTER);
JScrollPane leftScroll = new JScrollPane();
JScrollPane rightScroll = new JScrollPane();
leftScroll.getViewport().add(leftPanel);
rightScroll.getViewport().add(rightPanel);
JSplitPane split = new JSplitPane();
split.setLeftComponent(leftScroll);
split.setRightComponent(rightScroll);
split.setDividerLocation(400);
JFrame frame = new JFrame();
frame.getContentPane().setLayout(new BorderLayout());
frame.getContentPane().add(split, BorderLayout.CENTER);
frame.setSize(800, 800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}

It is a dirty way but this will solve it:
When you call pack() without resizing the window, not much happens. So, when you first resize the window and then call pack(), your components are correcly drawn. This means you can put this dirty method in your divider moved listener method:
frame.setPreferredSize(frame.getSize()); // store the current size to restore it after packing.
frame.setSize(frame.getWidth() + 1, frame.getHeight()); // resize it!!
frame.pack();
I don't know what it is exactly but it is a strange behavour in Java...
Hope this helps until you've found a better solution...

You are kind of out of luck here. There's a pretty good article on this on the sun/oracle website:
http://java.sun.com/products/jfc/tsc/articles/mixing/
Essentially it boils down to this guideline (taken from that link, under the z-ordering heading):
Do not mix lightweight (Swing) and
heavyweight (AWT) components within a
container where the lightweight
component is expected to overlap the
heavyweight one.
Edit: I kept browsing that site and came across another link, and it would appear that the situation has improved slightly: http://java.sun.com/developer/technicalArticles/GUI/mixing_components/ But I think your case is one of those listed at the bottom in the limitations section:
Limitations
A few situations are not supported:
* Non-opaque lightweight components that have translucent
pixels (0 < alpha < 255) are not
supported. If a partially translucent
lightweight component overlaps a
heavyweight component, the heavyweight
component will not show through.
* Embedded heavyweight components must belong to the process that
created the frame or applet. The
heavyweight component must have a
valid peer within the main process of
the application (or applet).
* Advanced Swing key events, such as those events maintained in an
InputMap, might not work correctly
where lightweight and heavyweight
components are being mixed. There are
no known workarounds.

Related

Can I add a rectangle to a Jpanel in java swing and specify constraints for it using GridBagConstraints?

I'm pretty new to GUI and Java as a whole so I hope that I can explain this well enough and understand people's answers.
For a school project, I need to put a bunch of stuff on some rectangles but I'm having issues even adding one rectangle properly.
From researching online, this is what I have (the JPanel and GridBagConstraints are just there to show what I'd like to use):
public class GUI extends JPanel
{
public static void main (String [] args)
{
GUI g = new GUI();
JFrame window = new JFrame("Java Window");
window.setSize(1280, 960);
window.add(g);
window.setVisible(true);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel layout = new JPanel(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
}
#Override
public void paintComponent(Graphics g)
{
Color boxColour = new Color(194, 190, 190);
super.paintComponent(g);
g.setColor(boxColour);
g.fillRect(10, 10, 100, 100);
}
}
So right now, the rectangle appears in the window. But how can I add constraints to it? Is that possible? I would think that I should use JPanel to keep everything more organized since there will be many components so I tried adding this:
layout.add(g);
window.add(layout);
window.setVisible(true);
However, the rectangle no longer appeared. What am I doing wrong and how can I fix it and add constraints to my shapes? Thanks!
In your first scenario, the default layout manager of the frame is the BorderLayout and you are adding your "g" panel to the BorderLayout.CENTER. So based on the rules of the BorderLayout your "g" panel will take up all the space available in the frame. So you have plenty of space to paint your rectangle.
However, in your second scenario, your "layout" panel is using the default layout manager of a JPanel which is a FlowLayout which respects the preferred size of any component added to it.
The preferred size of the "g" panel is 10 x 10. So, when you add the "g" panel to the "layout" panel there is nothing to see because all you custom painting is done outside the bounds of the panel.
You need to override the getPreferredSize() method of your GUI panel to return a preferred size of (120, 120) so you can see your rectangle painted with a 10 pixel border around all the edges.
Read the section from the Swing tutorial on Custom Painting for more information and working examples demonstrating how to override the `getPreferredSize() method.
You will also need to read the Swing tutorial on How to Use GridBagLayout for example of using the constraints to add multiple components.

Swing layering - transparent component ignores underlying AWT element

First, to get it out of the way, I absolutely NEED to use heavyweight AWT component with a swing application. I need features from both of them.
The task is simple - render a heavyweight AWT Canvas (or any other element), render OpenGL scene directly onto it, then display Swing buttons above it for the UI.
My problem is that it works half-way.
I don't seem to have problems with Z-ordering. I am using jLayeredPanes for it, and I can move Canvas between layers and it actually works, popping on top or below other elements.
The problems are with transparency.
The thing is, Swing elements have Opaque parameter, and when it's set to false (non-opaque) - it should basically be transparent and you should see the next element below it. In my case, however, the AWT Canvas gets ignored, and you instead only see the next underlying SWING element.
Here are a couple of screenshots. They are taken from a standalone test project of mine. The canvas is stretched to the size of the frame, and in the upper left there is a JLayeredPane dummy element that is a simplified version of the menu.
On the first screenshot, the JLayeredPane's Opaque setting is set to true, and you can see that it's background property is set to Blue color.
On the second screenshot, everything is exactly the same but Opaque is set to false. Instead of displaying whatever is on the Canvas - what gets drawn in empty grey jFrame background.
Lastly, on the third screenshot I have put Canvas into a jPanel instead of leaving it on its own. As you can see, the Panel's orange color is seen through the transparent jLayeredPane, but the Canvas is yet again hidden.
Here's the code for the Frame layout. I would not post my rendering/context code right now
frame = new JFrame("AWT test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.setPreferredSize(new Dimension(width, height));
canvas = new Canvas();
canvas.setSize(width,height);
//this part exists only in the third example
JPanel p = new JPanel();
p.setSize(width,height);
p.setBackground(Color.orange);
p.add(canvas);
// third example end
JLayeredPane pane = new JLayeredPane();
JLayeredPane paneMenu = new JLayeredPane();
JButton button = new JButton();
button.setSize(20,20);
paneMenu.setSize(200,200);
paneMenu.add(button, new Integer(1));
paneMenu.setBackground(Color.BLUE);
paneMenu.setOpaque(false); //True for the first example
pane.add(p, new Integer(1)); // canvas for the first two examples
pane.add(paneMenu, new Integer(2));
pane.setOpaque(false);
frame.add(pane);
frame.pack();
frame.setVisible(true);
frame.transferFocus();
Could anyone please explain me what is going on and how to do what I need to do.
I will repeat again - I have to use heavyweight component as render target. I am aware of solutions like JOGL's GLPanel which is a lightweight Swing-compatible component. But I tried that method and the performance is really slow, because instead of directly rendering onto it as a context target - it reads FrameBuffer from memory, flips it, and then paints it as BufferedImage. This path is not fitting for the limited resources of an embedded system that I'll be running on.
c0der said: Please post minimal reproducible example
Errrrm.... Didn't I?
Here, you can have it in complete java class form if you want, but I literally changed some variables for constants.
import javax.swing.*;
import java.awt.*;
public class Main
{
public static void main(String[] args)
{
JFrame frame = new JFrame("AWT test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.setPreferredSize(new Dimension(500, 500));
Canvas canvas = new Canvas();
canvas.setSize(500,500);
canvas.setBackground(Color.RED);
//this part exists only in the third example
JPanel p = new JPanel();
p.setSize(500,500);
p.setBackground(Color.orange);
p.add(canvas);
// third example end
JLayeredPane pane = new JLayeredPane();
JLayeredPane paneMenu = new JLayeredPane();
JButton button = new JButton();
button.setSize(20,20);
paneMenu.setSize(200,200);
paneMenu.add(button, new Integer(1));
paneMenu.setBackground(Color.BLUE);
paneMenu.setOpaque(false); //True for the first example
pane.add(p, new Integer(1)); // canvas for the first two examples
pane.add(paneMenu, new Integer(2));
pane.setOpaque(false);
frame.add(pane);
frame.pack();
frame.setVisible(true);
frame.transferFocus();
}
}
A little update:
I initially suspected that because Swing elements delegate all their drawing to the underlying heavyweight element (In my case JFrame), then what happens is that the frame generates a single frameBuffer for itself and then displays on top of Canvas. Canvas itself is not handled in this generation and thus the frame "covers" over the canvas.
That doesn't seem to be the case. I tried making the JFrame undecorated, all panels non-opaque, and display the picture. The result - canvas is still "cut", and through the hole you can see the underlying IDE menu.
This makes me think that somewhere during Drawing, the Canvas itself detects that it is obscured by another element, and that it doesn't need to draw that area. So it "optimizes" itself and doesn't update these pixels.
Maybe I'm wrong. But here's another screenshot. This is the same example as before, but I took out 3d rendering and simply trying to display Canvas with background set to Red.
Once again, going to reply to my own question.
It turned out that I need to do
setComponentMixingCutoutShape(paneMenu, new Rectangle());
for the menu pane that lies underneath the button. That essentially tells java not to cut out the element from the heavyweight underlying component.

JSplitPane and Canvas

As part of an application I'm writing I need to mix the old (heavyweight) Canvas with swing components - specifically nesting them inside a JSplitPane. However, when I do this the divider refuses to resize anywhere as though neither canvas will accept a reduction in size. The code demonstrating the issue is thus:
JFrame frame = new JFrame();
JSplitPane pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, new Canvas(), new Canvas());
pane.setResizeWeight(0.5);
frame.add(pane);
frame.pack();
frame.setVisible(true);
I initially assumed this was a simple thing to solve, however after doing a fair bit of research the options seem to present themselves as:
Use a JPanel instead (not always an option as in my case where I'm using vlcj to render directly onto a canvas)
Insert x dodgy hack that might work in some cases
Is there a better way of doing this? Or is it really just a case of resorting to hacks? I've debated other options such as if an alternative SplitPane implementation might be available that works, but there's no heavyweight implementation and I'd be surprised if a lightweight one avoided the problem.
A JSplitPane uses the "minimum size" of the component to determine if the component can shrink when using the divider.
I've never used a Canvas before, but itt appears that the minimum size always defaults to the preferred size.
Override the getMinimumSize(...) size method of the Canvas to return a reasonable minimum.
For a quick text you can use:
Canvas canvas = new Canvas();
canvas.setMinimumSize( new Dimension(50, 50) );
JSplitPane pane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, canvas, new Canvas());
and you will be able to move the divider left, but never back to the right.

Why does BorderLayout overwrite components when new ones are added?

BorderLayout does something strange. If I add two panels to a Container with the same constraint (BorderLayout.CENTER for instance), then the first one goes away, even if the second one is deleted or made invisible
It seems as though it would make sense for it to "stack" each element on top of the previous ones.
Is this correct and by design? If so, is there some documentation on it?
Has anyone else been frustrated by it? Have you a solution, such as a custom LayoutManager?
Sample code:
JFrame frame = new JFrame();
frame.setSize(500, 500);
JPanel panel1 = new JPanel();
panel1.setBackground(Color.blue);
frame.getContentPane().add(panel1);
JPanel panel2 = new JPanel();
panel2.setBackground(Color.red);
frame.getContentPane().add(panel2);
panel2.setVisible(false); // Seems like it should allow us to see panel1.
frame.setVisible(true);
This creates and displays a 500x500 blank box.
BorderLayout was simply not designed to do what you want. Separation of responsibility. If you want that behavior you should compose: combine the BorderLayout with a CardLayout. Though for the actual stack behavior, you'll have to code something yourself (or find someone who already has.)
Is this correct and by design?
Yes.
You need to understand the basics of how layout managers work. One of the jobs of the layout manager is to set the "location" and "size" of the components added to the panel. In the case of a BorderLayout it only tracks 5 components so only the last component added to the CENTER is known by the layout manager.
Layout management is not done when components are added to the panel. It is done when the frame is packed, or made visible (or the revalidate() method is invoked) . In this case the blue panel is not part of the components managed by the BorderLayout so its size remains (0, 0), which means there is nothing to paint.
Try changing your code to:
JPanel panel1 = new JPanel();
panel1.setSize(200, 200);
and you will see the blue panel painted at the specified size.
Now try commenting out:
//panel2.setVisible(false);
and you will see both panels. This is because as components are added to the panel they are assigned a ZOrder. Basically the last component added is painted first, which is why the blue panel is painted on top of the red panel. Check out the setComponentZOrder() method of the Container class for more information.
The CardLayout is probably the layout manager you should be using, but you can check out the Overlap Layout as well.

Swing Overlapping components

I have two AWT components in a Frame, Panel A and Panel B. I would like panel A to be sized to the height width of the frame (and maintain that size on frame resize), but I would like panel B to overlap A. B will be at a fixed position (0,0 to make it easier) with a fixed height and width. I'm not sure what kind of layout manager I would need to make this work. If I use a null layout, I think I would have to manage the resizing of panel A myself, but it would make the sizing of panel B relatively easy. Any thoughts on how to accomplish this?
thanks,
Jeff
Take a look at JLayeredPanes. Here is a tutorial.
edit:
If panelA is an AWT component, you will be hard pressed to get panelB to overlap. From Sun's article entitled Mixing Heavy and Light Components:
Do not mix lightweight (Swing) and heavyweight (AWT) components within a container where the lightweight component is expected to overlap the heavyweight one.
However, if you are looking to have panelA fill the Frame completely, why not add panelB as a component of panelA?
Edit2:
If you can make panelB a heavyweight component, then you can use the JLayeredPane.
Here is a quick mockup that shows how:
public static void main(String[] args){
new GUITest();
}
public GUITest() {
frame = new JFrame("test");
frame.setSize(300,300);
addStuffToFrame();
SwingUtilities.invokeLater(new Runnable(){
public void run() {
frame.setVisible(true);
}
});
}
private void addStuffToFrame() {
Panel awtPanel = new Panel();
awtPanel.setBackground(Color.blue);
//here you can fool around with the pane:
//first, you can see how the layered pane works by switching the
//DEFUALT_LAYER and PALLETTE_LAYER back and forth between the two panels
//and re-compiling to see the results
awtPanel.setSize(200,300);
frame.getLayeredPane().add(awtPanel, JLayeredPane.DEFAULT_LAYER);
//next you comment out the above two lines and
//uncomment the following line. this will give you the desired effect of
//awtPanel filling in the entire frame, even on a resize.
//frame.add(awtPanel);
Panel awtPanel2 = new Panel();
awtPanel2.setBackground(Color.red);
awtPanel2.setSize(300,200);
frame.getLayeredPane().add(awtPanel2,JLayeredPane.PALETTE_LAYER);
}
Your best bet is to have your own LayoutManager. The easiest way is probably to extend or proxy BorderLayout, and have a specific case to layout panel B.
Maybe I'm missing something. If B is fixed size and is at (0,0) and A runs the full width what's the use of having B overlap A? You will never see anything that is placed under B.
I can't think of any of the default layout managers, but Orielly has this one you can use:
relative layout manager (see source code link) with documentation. I haven't used it in a long while, but it should beat managing the layout yourself.

Categories

Resources