Skip to main content

JXLayer for a ZoomPanel: odd painting behavior...

3 replies [Last post]
mmo18
Offline
Joined: 2008-07-20
Points: 0

I tried to use JXLayer to create a generic ZoomPanel, but I get rather odd painting behavior. When I move my mouse over the display the components disappear and reappear almost at random.

Any idea, why?

Michael

My little test program:

package test;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

import javax.swing.JApplet;

@SuppressWarnings("serial")
public class TestJXLayer extends JApplet
{
@Override
public void init() {
Container mainPanel = getContentPane();
mainPanel.setLayout(new BorderLayout());

this.setLayout(new BorderLayout());

JPanel viewer = new JPanel();
viewer.setLayout(new GridLayout(4,4));
for (int i = 0; i < 16; i++) {
JButton button = new JButton("button " + i) {
{
this.addActionListener(new ActionListener()
{
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(getText() + " pressed");
}
});
}

};
viewer.add(button);
}

JXLayer jxlayer = new JXLayer(viewer, new ZoomPanelUI());
mainPanel.add(new JScrollPane(jxlayer,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED),
BorderLayout.CENTER);
}

static class ZoomPanelUI extends AbstractLayerUI
{
// private double scalingFactor = 1.0; // works
private double scalingFactor = 0.5; // does not work...

double getScalingFactor() {
return this.scalingFactor;
}
void setScalingFactor(double scalingFactor) {
this.scalingFactor = scalingFactor;
}
// override paintLayer(), not paint()
@Override
protected void paintLayer(Graphics2D g2, JXLayer layer) {
AffineTransform oldTransform = g2.getTransform();
g2.scale(this.scalingFactor, this.scalingFactor);
super.paintLayer(g2, layer);
g2.setTransform(oldTransform);
}
// I am not dealing with mouse events, yet...
}
}

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
alexfromsun
Offline
Joined: 2005-09-05
Points: 0

Hello Michael

When you apply a transform to the Graphics2D, you see scaled components,

but actually their real bounds are unchanged, so when you move the mouse over "empty" space

you actually move it over the button, which is unscaled,

the button repaints itself and together with custom transform it produces the strange effect

To disable mouseEvents you can add an empty mouseListener to the

layer's glassPane in the installUI() method, using LockableUI is even better in this case:

[code]
new LockableUI() {
protected AffineTransform getTransform(JXLayer l) {
if (isLocked()) {
return AffineTransform.getScaleInstance(.5, .5);
}
return super.getTransform(l);
}
};
[/code]


If you need to transform the mouseEvents as well, Scenegraph is only option I am aware of

Thanks

alexp

pietblok
Offline
Joined: 2003-07-17
Points: 0

Hi Michael,

True what Alex says.

However, since I assune that you do NOT want your buttons to be disabled, you may process all AWTEvents and set the LayerUI to dirty.

Also, when you scale your Graphics object, you should also scale the clipping area. When you don't, strange artifacts may appear.

Below some small changes to your code, marked with // pb

The next problem of course is to scale down your mouse events to get to the correct button, but I have no ready solution.

You may notice that, when you resize the window, your contents will resize with it when the available space is larger than the preferred size of your contents.

This is caused by the Scrollable implementation of JXLayer. When the contents itself does not implement Scrollable, it makes a best guess. The tracking of viewport width and height returns true if the contents is smaller than the view. If you don't want this to happen, you may implement Scrollable on your contents component, or, wrap your contents in a component that implements Scrollable to your taste.

I just mention this because especially in your test you may find the Scrollable implementation in JXLayer somewhat confusing.

FYI: you may wrap your code in square bracket tags like this:

[ code ]
your code
[ / code ]

to get a more readable presentation on the forum. Omit the spaces.

Piet

[code]
package test;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.SwingUtilities;

import org.jdesktop.jxlayer.JXLayer;
import org.jdesktop.jxlayer.plaf.AbstractLayerUI;

import javax.swing.JApplet;

@SuppressWarnings("serial")
public class TestJXLayer extends JApplet {
@Override
public void init() {
Container mainPanel = getContentPane();
mainPanel.setLayout(new BorderLayout());

// pb this.setLayout(new BorderLayout());

JPanel viewer = new JPanel();
viewer.setLayout(new GridLayout(4, 4));
for (int i = 0; i < 16; i++) {
JButton button = new JButton("button " + i) {
{
this.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println(getText() + " pressed");
}
});
}

};
viewer.add(button);
}

JXLayer jxlayer = new JXLayer(viewer,
new ZoomPanelUI());
mainPanel.add(new JScrollPane(jxlayer,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED),
BorderLayout.CENTER);
}

static class ZoomPanelUI extends AbstractLayerUI {

public ZoomPanelUI() {
setDirty(true);
}

// private double scalingFactor = 1.0; // works
private double scalingFactor = 0.5; // does not work...

double getScalingFactor() {
return this.scalingFactor;
}

void setScalingFactor(double scalingFactor) {
this.scalingFactor = scalingFactor;
}

// override paintLayer(), not paint()
@Override
protected void paintLayer(Graphics2D g2, JXLayer layer) {
// pb AffineTransform oldTransform = g2.getTransform();
// pb g2.scale(this.scalingFactor, this.scalingFactor);
// pb super.paintLayer(g2, layer);
// pb g2.setTransform(oldTransform);

/*
* Replaced by this code. We must not only apply the scalingFactor,
* but also change the clip.
*/
Rectangle innerArea = new Rectangle();
SwingUtilities.calculateInnerArea(layer, innerArea);
innerArea.setFrame(innerArea.getX() / scalingFactor, innerArea
.getY()
/ scalingFactor, innerArea.getWidth() / scalingFactor,
innerArea.getHeight() / scalingFactor);
Graphics2D myGraphics = (Graphics2D) g2.create();
myGraphics.setClip(innerArea);
myGraphics.scale(this.scalingFactor, this.scalingFactor);
super.paintLayer(myGraphics, layer);
}

// I am not dealing with mouse events, yet...

/**
* pb: But your buttons do and cause repaint only of themselves, not
* their container I guess. They may do this in fact on all type of
* events.
*/
@Override
public void eventDispatched(AWTEvent e, JXLayer l) {
setDirty(true);
}
}
}

[/code]

pietblok
Offline
Joined: 2003-07-17
Points: 0

Hi,

Posted my reply somewhat to soon:

1. the constructor for the ZoomPanelUI should be removed. A leftover from an experiment.

2. eventDispatched() should call its super implementation prior to setDirty(true)

3. To get a "clean" scrolling behavior, you could of course wrap your contents in a plain JPanel, and set that plain JPanel as the view for the JXLayer

But, even in the quick and dirty version, it seems to do what you expect it to do.

Thanks
Piet