How to Add Resize Functionality to Visual Applications in Java?
Join the DZone community and get the full member experience.
Join For FreeAt the end of the previous article, it was clear that (a) the NetBeans Visual Library is a very powerful framework for creating graphical widgets (for example, to make a widget move, you simply add one line of code), (b) one doesn't need the NetBeans Platform even though one is using one of its libraries and (c) one doesn't even need NetBeans IDE, even though one could do so, of course, but only if one wanted to do so. Simply put, you just download NetBeans IDE once, remove two of its JARs and then, if that's how you feel, you can remove NetBeans IDE completely. Next, simply put those two JARs on your classpath and you're good to go.
At the end of that article, we had created Chuk, one of the Sun evangelists, as a movable object with an editable label text field. We then also created Gregg, who is also a Sun evangelist, as another instance of the same object:
However, there was one problem with the above scenario, at least, according to Gregg: "Hey, how come Chuk's picture is larger?" That's the message he left in my blog, where I had also discussed this scenario in detail. So, this time, we're going to help Gregg. (So, all of this is for you, Gregg!) We're going to do two things, both of which are really cool, mostly undocumented, and illustrative of a whole host of things, as you'll discover if you stick with this article until the very end.
At the end, when your mouse moves over a widget, its borders will become resizable. Then, when you drag the handles on the border, the image will resize itself according to the movement of the mouse. So, you'll be able to make Chuk a whole lot smaller. And, Gregg can be made bigger (or smaller still):
Let's begin by asking ourselves how to create that border with the handles, i.e., the one that, when you see it, you think: "I am now able to resize something". Probably lots of code, right? Wrong. The Visual Library extends the borders offered by the JDK's Border classes. It provides a package called org.netbeans.api.visual.border.BorderFactory and another called org.netbeans.api.visual.border.Border. (I learned about all of this from Fabrizio Giudici's Creative Uses of the Visual Library, a document which I highly recommend.) Here we begin by declaring two constants, one for a normal border and one for resizing:
private static final Border RESIZE_BORDER =
BorderFactory.createResizeBorder(8,Color.BLACK,true);
private static final Border DEFAULT_BORDER =
BorderFactory.createEmptyBorder(8);
Even though our default border is empy, we set its thickness to 8, so that the area reserved for our border is the same for both. (If you experiment later, you'll see that if you don't set a thickness, strange effects result when the resize border is enabled/disabled.) Now that we have our borders, we need to specify when they should be shown.
To do this, we create a class that extends IconNodeWidget, unlike the last time where we were extending GraphScene and then creating new nodes in attachNodeWidget. Creating a separate class gives us more room to maneouvre, allowing us to add a lot of behavior to an individual widget. Here we call our widget PhotoWidget, we expect to receive the scene, an image, and a name. We assign those to the widget's label and image. We then add, as before, the MoveAction, so that the widget can move. (Just one line of code and no listeners!) We also, for the first time, add the HoverAction, which will make the widget sensitive to our mouse, as it hovers over it:
private static final class PhotoWidget extends IconNodeWidget {
public PhotoWidget(Scene scene, Image pic, String name) {
super(scene);
setLabel(name);
setImage(pic);
setPreferredLocation(new Point(10, 20));
getActions().addAction(ActionFactory.createMoveAction());
getActions().addAction(scene.createWidgetHoverAction());
}
@Override
public void notifyStateChanged(ObjectState previousState, ObjectState newState) {
super.notifyStateChanged(previousState, newState);
getImageWidget().setBorder(
newState.isSelected() ? (
newState.isHovered() ? RESIZE_BORDER : DEFAULT_BORDER) : (
newState.isHovered() ? RESIZE_BORDER : DEFAULT_BORDER));
}
}
Finally, notice that we have overridden IconNodeWidget.notifyStateChanged, which will determine whether our border will be shown! And that depends on our calculations in the setBorder method, which returns a border, as calculated above. If the widget is selected or hovered over, the border changes.
Currently, however, even though the border changes, the image doesn't resize when we drag the border's handles. To be able to do that, we need to add four lines of additional code, just lines 7-10 below:
public PhotoWidget(Scene scene, Image pic, String name) {
super(scene);
setLabel(name);
setImage(pic);
setPreferredLocation(new Point(10, 20));
//Add lines 7-10 below:
setLayout(LayoutFactory.createVerticalFlowLayout(LayoutFactory.SerialAlignment.JUSTIFY, 1));
setChildConstraint(getImageWidget(), 1);
setCheckClipping(true);
getImageWidget().getActions().addAction(ActionFactory.createResizeAction());
getActions().addAction(ActionFactory.createMoveAction());
getActions().addAction(scene.createWidgetHoverAction());
}
It is very important that the ResizeAction appear BEFORE the MoveAction, otherwise the MoveAction will consume the event, which will result in your resize gesture causing a move action instead. Also note that we don't resize the WHOLE widget, but just the image. The other three lines change the layout and grab the image, such that they can be resized. We set the clipping check to true, so that the image is clipped as it resizes, otherwise bits of it would remain as we resized.
At this point, I should reveal that the above doesn't actually work, because of a known issue in the Visual Library. Guided by David Kaspar, the creator of the library, I hacked the sources of the library to make it work. You can do the same. Just attach the sources to your project, instead of the JAR. Then find ImageWidget.paintWidget and change this line:
gr.drawImage(image, 0, 0, observer);
...to these lines:
Rectangle bounds = getBounds();
gr.drawImage(image, bounds.x, bounds.y, bounds.width, bounds.height, 0,
0, width, height, observer);
Now that your image has bounds, it can be resized. You'll have to make this change in the sources yourself too, until the issue is fixed, hopefully soon.
Just to recap, starting from last time, we have a JFrame with a JScrollPane. We've added a Visual Library "scene" to the JScrollPane. Then we added a LayerWidget to which we added two IconNodeWidgets. Both can move, they're sensitive to hovering, they have names, and images. And now the IconNodeWidgets get new resize borders when hovered over. And the handles on those borders can be dragged to resize the widget, including its image.
For the record, here is our constructor, together with our declarations at the top of the class:
private static final Border RESIZE_BORDER =
BorderFactory.createResizeBorder(8, Color.BLACK, true);
private static final Border DEFAULT_BORDER =
BorderFactory.createEmptyBorder(8);
private LayerWidget mainLayer;
private Image CHUK = Utilities.icon2Image(
new ImageIcon(getClass().getResource("/demo/chuk.png")));
private Image GREGG = Utilities.icon2Image(
new ImageIcon(getClass().getResource("/demo/gregg.png")));
public DemoVisualFrame() {
Scene scene = new Scene();
initComponents();
jScrollPane1.setViewportView(scene.createView());
mainLayer = new LayerWidget(scene);
scene.addChild(mainLayer);
mainLayer.addChild(new PhotoWidget(scene, CHUK, "Chuk"));
mainLayer.addChild(new PhotoWidget(scene, GREGG, "Gregg"));
}
Hope you're happy now Gregg!
Opinions expressed by DZone contributors are their own.
Comments