"I want to draw a line, and it will highlight when I click it."
I am currently working on a project right now. My project is a structural analysis software for civil engineers. Our project is already at it's infant
stage of development. I am having trouble with how to implement drawing
on a canvas. Heres the specs:
-There are nodes on the drawing board illustrated by dots.
-Lines must be drawn connecting the nodes.
-Nodes must be a separate object where it could respond to mouse
events.
-Lines must also be a separate object where it could respond to mouse
events.
-Nodes and lines contain mathematical attributes, color information.
-I can instantly delete a line, or change its attributes by selecting
it.
Heres what I have imagined:
-I am going to draw my Nodes in a JPanel which implements a
mouseListener.
-Whenever I want to add nodes to the drawing board, I only have to add
the Node objects i created.
>> No problem with the nodes. Problem is in the lines i will be drawing
connecting one node to another.
-I tried manual live drawing of the lines as the mouse moves. Now its
flat on the drawing board. It can't be touched!
-I still don't have any idea how to implement the line that it could
respond to mouse events.
-If I try to draw it in a JPanel with mouseListener, a JPanel is square
so if I move my mouse to point in to the line drawn on the JPanel, it
will already respond before the mouse could reach the line image. One
thing also, it could overlap other JPanels.
-The JPanel idea came up to my mind coz I thought these objects could
be
selectable custom components.
-Now I realized JPanel is not the ANSWER!
In simple saying:
"I want to draw a line, and it will highlight when I click it."
Do you have a lightweight line component?
Off the top of my head:
A line is a lightweight component containing its two end points, description info and a paint() method to display it
There is a list of components for a panel.
A mouselistener calls all the components in the panel to see if the mouse location is "in" one of the components, if so it calls the component telling it about the mouse position/action.
The Component class has a contains() method. Is that called by the AWT engine?
Have the line component override that.
...you want to keep track of the objects in your JPanel. You can then
make an abstract class, say, VisibleObject that defines some
abstract methods. Anyway, here is code that does that
Btw, a JPanel is fine for drawing, I quit using (the "mouse deaf") Canvas
years ago, when I discovered that mouseclicks in a Canvas on a Mac
were completely silent, nothing happened.... It may be fixed now, but
the JPanel is my favourite canvas...
Code:
import javax.swing.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
class NodeLinePanel extends JPanel implements MouseListener {
ArrayList voList=new ArrayList();
public NodeLinePanel () {
addMouseListener(this);
}
public void addVisibleObject(VisibleObject vo) {
voList.add(vo);
repaint();
}
public void update(Graphics g) {
coverBackground(g, Color.white);
for (int i=0; i<voList.size(); i++) {
VisibleObject vo=(VisibleObject)voList.get(i);
vo.draw(g);
}
}
public void paint(Graphics g) {
update(g);
}
private void coverBackground(Graphics g, Color color) {
Color c=g.getColor();
g.setColor(color);
g.fillRect(0,0,getWidth(),getHeight());
g.setColor(c);
}
public void mousePressed(MouseEvent e) {
resetHits();
for (int i=0; i<voList.size(); i++) {
VisibleObject vo=(VisibleObject)voList.get(i);
if (vo.getHit(e.getPoint())) {
JOptionPane.showMessageDialog(this,"gotcha !"+vo.toString());
break;
}
}
repaint();
}
private void resetHits() {
for (int i=0; i<voList.size(); i++) {
VisibleObject vo = (VisibleObject) voList.get(i);
vo.setHit(false);
}
}
public void mouseClicked(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
}
abstract class VisibleObject {
public boolean hit=false;
Point start = null;
public abstract void draw(Graphics g);
public abstract boolean getHit(Point p);
public abstract String toString();
public VisibleObject(){}
public VisibleObject(Point start) {
this.start=start;
}
public static StraightLine connectNodes(Node n1, Node n2) {
StraightLine sL=new StraightLine(n1.getCenterPoint(),
n2.getCenterPoint());
return sL;
}
public boolean isHit() {
return hit;
}
public void setHit (boolean hit) {
this.hit=hit;
}
public Color getColor() {
if (hit) {
return Color.red;
} else {
return Color.blue;
}
}
}
class StraightLine extends VisibleObject {
static int HIT_DISTANCE=3; // 2 pixels fuzz...
Point end=null;
Line2D.Double l2D=new Line2D.Double();
public StraightLine(Point start, Point end) {
super(start);
this.end=end;
l2D.setLine((double)start.x,(double)start.y,
(double)end.x,(double)end.y);
}
public String toString() {
return "Line: "+start.x+","+start.y+" - "+
end.x+","+end.y;
}
public void draw(Graphics g) {
Color c=g.getColor();
g.setColor(getColor());
g.drawLine(start.x,start.y,end.x,end.y);
g.setColor(c);
}
public boolean getHit(Point p) {
double dist=l2D.ptLineDist((double)p.x, (double)p.y);
hit = dist < HIT_DISTANCE;
return hit;
}
}
class Node extends VisibleObject {
Rectangle r=null;
String name=null;
public Node (Point p, String name) {
super(p);
this.name=name;
}
public Point getCenterPoint() {
return new Point(start.x+6, start.y+6);
}
public String toString() {
return "Node "+name+": "+start.x+","+start.y;
}
public void draw(Graphics g) {
Color c=g.getColor();
g.setColor(getColor());
g.fillOval(start.x,start.y,13,13);
r=new Rectangle(start,new Dimension(13,13));
g.drawString(name,start.x+14,start.y);
g.setColor(c);
}
public boolean getHit(Point p) {
hit=r.contains(p);
return hit;
}
}
public class DrawFrame extends JFrame {
public DrawFrame() {
}
public static void main(String[] args) {
DrawFrame dF = new DrawFrame();
dF.getContentPane().setLayout(new GridLayout());
NodeLinePanel pan=new NodeLinePanel();
dF.getContentPane().add(pan);
dF.addWindowListener(new WindowAdapter() {
public void windowClosed(WindowEvent e) {
System.exit(0);
}
});
dF.setBounds(10,10,600,500);
dF.setVisible(true);
Node n1=new Node(new Point(20,20), "n1");
Node n2=new Node(new Point(400,400), "n2");
Node n3=new Node(new Point(100,300), "n3");
Node n4=new Node(new Point(260,200), "n4");
Node n5=new Node(new Point(310,180), "n5");
pan.addVisibleObject(n1);
pan.addVisibleObject(n2);
pan.addVisibleObject(n3);
pan.addVisibleObject(n4);
pan.addVisibleObject(n5);
pan.addVisibleObject(VisibleObject.connectNodes(n1,n2));
pan.addVisibleObject(VisibleObject.connectNodes(n2,n3));
pan.addVisibleObject(VisibleObject.connectNodes(n3,n4));
pan.addVisibleObject(VisibleObject.connectNodes(n4,n5));
pan.addVisibleObject(VisibleObject.connectNodes(n5,n1));
}
}
One more thing or two:
it will already respond before the mouse could reach the line image ??
what do you mean ?
overlap other JPanels .... what is the problem with that ?
You're so cool!!! Thanx a lot for that great wonderfull idea...
But this is my real problem: I have to implement it that it draws the line real time... like when I click on a node, the program will begin drawing a straight - one end on the node clicked and the other end is following the mouse. It will only stop following the mouse when it clicks on another node to connect it.
Here's an idea. When the mouse clicks on a node, save its Point and then in mouseDragged, save the point where the mouse has moved to and repaint().
Paint can test these values and draw a line from the clicked on node to the current mouse position.
In mouseDragged() ...
Code:
Point p = new Point(me.getX()-tX, me.getY()-tY);
// Set end drag line if started and not ended
if (selNode != NON_SELECTED) { //&& selEndNode == NON_SELECTED) {
selEndNode = NON_SELECTED; // clear old line
// Draw line from selected node to cursor position
dragPoint = p; // save for paint
repaint();
Here pts[selNode] is a Point which the mouse was initially clicked on
In mousePressed:
Code:
// Check if its over a node - for drawing a line
int i = inNode(p);
if (i >= 0) {
if(pts[i] == null) return; // exit if empty
// Problem if only "clicking" a node to show its label???
saveSel = selNode; // save in case of click
selNode = i; // remember
return; // found, exit
}
...it can serve as a basis for further development.
It has some useful basic features:
Popup menu:
-add node (and enter properties for it, currently just a name)
-remove node
-remove line
-nodes properties window that interacts w. the node drawing panel
CTRL+left mouse: draw connection line between nodes.
CTRL+right mouse: drag node & connected lines
All movement and drawing is done w. continuous screen update.
...especially since I discovered a couple of bugs. They are located in the
NodeLinePanel class in two different methods. You should replace them
with these two:
Code:
/**
* Bring up the properties frame for the selected object
* - at the correct location
*/
private void showObjectProperties() {
VisibleObject vO=checkForHit(clickPoint,true);
propFrame.setVisibleObject(vO, voList);
Point scp = imgScroller.getViewport().getViewPosition();
Point scp1 = imgScroller.getLocationOnScreen();
this.propFrame.setLocation((int)(scp1.x - scp.x + clickPoint.getX()),
(int)(scp1.y - scp.y + clickPoint.getY()));
propFrame.setVisible(true);
}
and this one
Code:
/**
* Scans all visibles for a mouse hit
* @return the visible objec or null
*/
public VisibleObject checkForHit(Point hitPoint, boolean select) {
if (select) resetHits();
for (int i = 0; i < voList.size(); i++) {
VisibleObject vO = (VisibleObject) voList.get(i);
/**
* If dragging then this node may be the dragged node,
* if so then ignore the hit. Omitting this test will
* sometimes allow the user to drag a node over another.
* The outcome depends on the visual objects sequence in
* the vOList.
*/
if (dragNode!=null && vO.equals(dragNode)) {
continue;
}
if (vO.getHit(hitPoint, select)) {
pFrame.showStatus("Clicked: " + vO.toString());
return vO;
}
}
return null;
}
I was interested in your version of a graph of nodes as I'd written a simple one long ago. I downloaded yours and ran it and noticed that the drawing lagged the mouse a lot. I'm on a 333MHz machine.
I tried using Swing several years ago, but it was so slow I gave up and have stayed with AWT. Not as fancy, but noticeably faster on my machine.
I attached a jar file(renamed to zip) with my simple example. It contains source, class and manifest. Rename to jar to execute.
I have had a look at your nodes program and it looks ok, as an exercise in
graphics interactions programming (I must include the hand-cursor !).
I assume you would experience some mouse lag if you tried it with some 20-
30 nodes and 50+ connection lines, and moving a node with 10+ lines
attatched to it.
However, I am unable to test that since I cannot compile it:
(one of many missing implementations for the 1.4 version of Shape)
"DrawGraph.java": Error #: 454 : class norms.GraphNode should be declared abstract; it does not define method getPathIterator(java.awt.geom.AffineTransform, double) in interface java.awt.Shape at line 157, column 1
... unless I revert to some older java version. I'm already behind in that
respect, as I haven't moved from 1.4 to 1.5, - can't afford the JBuilder 10
yet
As for awt I left that in the late 90's. I had to, in order to do my work. I
still code some applets using only awt when required, but they don't look very
good.
You have my sympathy for your 333mHz machine, those were hot stuff some
six years ago. On my 1.8 gHz machine my node app runs smoooothly. You
should be able to aquire an "old" 1,5(+)gHz machine for very little money.
Here in Norway you get one of those for 100-150 euro. I say this cause it's
a drag not being able to venture into the word of swing just because your
machine can't handle it.
Bookmarks