1 package demo; 2 3 4 5 import java.awt.BorderLayout; 6 import java.awt.Color; 7 import java.awt.FlowLayout; 8 import java.awt.Graphics; 9 import java.awt.Toolkit; 10 import java.awt.event.MouseAdapter; 11 import java.awt.event.MouseEvent; 12 import java.awt.event.MouseMotionAdapter; 13 import java.util.Arrays; 14 15 import javax.swing.JButton; 16 import javax.swing.JFrame; 17 import javax.swing.JPanel; 18 /** 19 * Java 直线、多段线画板 PaintJFrame (整理) 20 * 21 * 2016-1-2 深圳 南山平山村 曾剑锋 22 * 23 *24 *
一、软件声明:
25 *26 * 本软件是仿AutoCAD部分功能软件,最初设计之初的想法是:实现各种图形的绘制(直线、多段线、圆、写轮眼)。但是 27 * 当实现到了目前的状态的时候发现后面的程序开发只不过是对当前程序的复制、粘贴,而且最终绘制出来的图形看上去并不是 28 * 很精细、优雅,就如同当您在使用本软件的时候会发现,绘制出来的直线是那种很让人纠结的线,不够平滑的感觉,至少她让 29 * 我本人感觉不是太舒服的感觉。 30 *
31 *32 * 同时如果在软件中添加过多的东西,会让人觉的更难以阅读理解,不适合用来沟通交流,尤其是像这种只能通过文字沟通 33 * 的方式,所以本人不打算对此软件进行进一步的扩展。 34 *
35 *二、软件结构如下:
36 *
68 */ 69 public class PaintJFrame extends JFrame{ 70 private static final long serialVersionUID = 1L; 71 /** 72 * 73 * 主要用于保存所画图的类型,如:直线、多样线等内容,可以认为是一个容器,包含所有的图形的基本信息, 74 * 每次当前绘制的图形都保存在shapes的最后一个位置上,主要是为了便于查找。 75 */ 76 Shape[] shapes = {}; 77 /** 78 * 定义一个绘图面板,主要用于绘图 79 */ 80 PaintJPanel paintJPanel = new PaintJPanel(); 81 /** 82 * 定义一个按钮面板,主要用于放置按钮 83 */ 84 JPanel buttonJPanel = new JPanel(new FlowLayout()); 85 /** 86 * 主要用于保存动态的X轴坐标,通过鼠标移动监听器来获取 87 */ 88 int trendsX = 0; 89 /** 90 * 主要用于保存动态的Y轴坐标,通过鼠标移动监听器来获取 91 */ 92 int trendsY = 0; 93 /** 94 * 主要用于保存点击时的X轴坐标,通过鼠标移动监听器来获取 95 */ 96 int clickX = 0; 97 /** 98 * 主要用于保存点击时的Y轴坐标,通过鼠标移动监听器来获取 99 */100 int clickY = 0;101 /**102 * 用于放置各种按钮,主要用于凸现当前绘图时的按钮,方便查找,修改103 */104 PaintJButton[] paintJButtons = new PaintJButton[2];105 /**106 *- 所有类的继承关系如下: 37 *
53 *
- 绘图类继承关系: 38 * |--Shape 39 * |--|--SingleLine 40 * |--|--MultiLine 41 *
- 按钮类继承关系(括号内为添加的监听事件): 42 * |--JButton 43 * |--|--PaintJButton 44 * |--|--|--SingleJButton(MouseAdapter) 45 * |--|--|--MultiJButton(MouseAdapter) 46 *
- 窗口类继承关系: 47 * |--JFrame 48 * |--|--PaintJFrame 49 *
- 绘图面板继承关系(PaintJFrame的内部类,括号内为添加的监听事件): 50 * |--JPanel 51 * |--PaintJPanel(MouseAdapter,MouseMotionAdapter) 52 *
- 绘图类与按钮来的逻辑关系如下: 54 *
59 *
- SingleLine和SingleJButton是对应的; 55 *
- MultiLine和MultJButton是对应的; 56 *
- 当按下SingleJButton时,就能够在面板上绘制SingleLine; 57 *
- 当按下MultJButton时,就能够在面板上绘制MultiLine; 58 *
- GUI图形界面容器、组件的包含关系: 60 *
61 * |--PaintJFrame(窗口容器,位于main函数中,BorderLayout布局) 62 * |--|--buttonJPanel(JPanel容器,全局变量,FlowLayout布局) 63 * |--|--|--singleLineJButton(绘直线按钮,paintJButtons数组中下标为0的位置) 64 * |--|--|--multiLineJButton(绘多段线按钮,paintJButtons数组中下标为1的位置) 65 * |--|--paintJPanel(JPanel画板,全局变量) 66 *
67 *窗口构造函数功能如下:
107 *
111 */112 public PaintJFrame() {113 setButtonJPanel();114 setJFrame();115 paintJPanel.startRun();116 }117 /**118 *- setButtonJPanel();设置按钮面板,主要是添加一些需要的按钮在其中;108 *
- setJFrame();设置窗口属性,主要是窗口的一些基本属性设置;109 *
- paintJPanel.startRun();启动paintJPanel内部线程,主要用于对画板中图形的计算以及重绘110 *
设置按钮面板函数功能如下:
119 *
- 创建一个绘制直线的按钮,名字为:直线;120 *
- 创建一个绘制多段线的按钮,名字为:多段线;121 *
- 将直线按钮加入paintJButtons[0]中,主要是因为直线按钮对应PaintJButton.SINGLE_LINE;122 * 将多段线按钮加入paintJButtons[1]中,主要是因为直线按钮对应PaintJButton.MULTI_LINE123 *
- 将两个按钮添加进入buttonJPanel。124 *
125 */126 private void setButtonJPanel() {127 PaintJButton singleLineJButton = new SingleJButton("直线");128 PaintJButton multiLineJButton = new MultiJButton("多段线");129 paintJButtons[PaintJButton.SINGLE_LINE] = singleLineJButton;130 paintJButtons[PaintJButton.MULTI_LINE] = multiLineJButton;131 buttonJPanel.add(singleLineJButton);132 buttonJPanel.add(multiLineJButton);133 }134 /**135 *
321 */322 private void leftClickForSingleLine() {323 if (SingleLine.clickCount == 0) {324 shapes = Arrays.copyOf(shapes, shapes.length+1);325 SingleLine singleLine = new SingleLine();326 shapes[shapes.length-1] = singleLine;327 singleLine.setFirstPoint(clickX, clickY);328 singleLine.setSecondPoint(clickX, clickY);329 SingleLine.clickCount++;330 }else {331 SingleLine singleLine = (SingleLine)shapes[shapes.length-1];332 singleLine.setSecondPoint(clickX, clickY);333 SingleLine.clickCount = 0;334 }335 }336 /**337 *窗体设置函数功能如下:
136 *
144 */145 private void setJFrame() {146 this.setLayout(new BorderLayout());147 this.setTitle("Painting");148 this.setSize(1024, 600);149 this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);150 this.setLocationRelativeTo(null);151 this.setLocation( (int)((Toolkit.getDefaultToolkit().getScreenSize().getWidth()-1024)/2),152 (int)((Toolkit.getDefaultToolkit().getScreenSize().getHeight()-600)/2));153 this.add(BorderLayout.CENTER,paintJPanel);154 this.add(BorderLayout.NORTH,buttonJPanel);155 this.setVisible(true);156 }157 /**158 *- 设置窗体布局格式;137 *
- 设置窗体标题;138 *
- 设置窗体大小;139 *
- 设置窗体关闭模式;140 *
- 设置窗体位置;141 *
- 将paintJPanel,buttonJPanel加入窗体相应位置142 *
- 将窗体设置为可见;143 *
PaintJPanel类设计思路:
159 * 这是一个绘图面板,只与图形显示和鼠标点击、移动相关; 160 *
169 */170 class PaintJPanel extends JPanel {171 private static final long serialVersionUID = 1L;172 /**173 *- 在图形显示方面:
161 *164 *
- 重写paint()方法;162 *
- 添加线程来动态维护图形显示。163 *
- 需要在这个类中添加事件监听事件:165 *
168 *
- 鼠标点击,本类使用了MouseAdapter;166 *
- 鼠标移动事件,本类使用了MouseMotionAdapter。167 *
PaintJPanel构造方法功能如下:
174 *
177 */178 public PaintJPanel() { 179 this.addMouseListener(new MouseAdapter() {180 @Override181 public void mouseClicked(MouseEvent e) {182 clickX = e.getX();183 clickY = e.getY();184 dealWithClick(e);185 }186 });187 this.addMouseMotionListener(new MouseMotionAdapter() {188 @Override189 public void mouseMoved(MouseEvent e) {190 trendsX = e.getX();191 trendsY = e.getY();192 setDynamicPoint();193 }194 });195 }196 /**197 *- 为PaintJPanel添加MouseAdapter事件;175 *
- 为PaintJPanel添加MouseMotionAdapter事件。176 *
设置动态点函数功能如下:
198 *
207 */208 private void setDynamicPoint() {209 if (PaintJButton.currentButtonFlag == PaintJButton.SINGLE_LINE) {210 if (SingleLine.clickCount == 1) {211 SingleLine singleLine = (SingleLine)shapes[shapes.length-1];212 singleLine.setSecondPoint(trendsX, trendsY);213 }214 }if (PaintJButton.currentButtonFlag == PaintJButton.MULTI_LINE) {215 if (MultiLine.clickCount != 0) {216 MultiLine multiLine = (MultiLine)shapes[shapes.length-1];217 multiLine.pointX[MultiLine.clickCount] = trendsX;218 multiLine.pointY[MultiLine.clickCount] = trendsY;219 }220 }221 }222 /**223 *- 如果当前绘制的是直线199 *
202 *
- 判断是否当前是绘制直线的第二个点,因为,第一个点不需要动态效果;200 *
- 由于当前所绘制的图形信息保存在shapes最后一个位置上,所以快速找到那个对象;201 *
- 如果是,则设置直线的第二个点。
- 如果当前绘制的是多段线203 *
206 *
- 判断是否当前是绘制多段线非第一个点,因为,第一个点不需要动态效果;204 *
- 由于当前所绘制的图形信息保存在shapes最后一个位置上,所以快速找到那个对象;205 *
- 如果是,则设置将动态点加入多段线的pointX、pointY数组的末尾。
处理点击函数功能如下:
224 *
228 */229 private void dealWithClick(MouseEvent e) {230 if (e.getButton() == MouseEvent.BUTTON1) {231 leftClick();232 }else if (e.getButton() == MouseEvent.BUTTON2) {233 System.out.println("您点了鼠标中间( ^_^ )");234 }else if (e.getButton() == MouseEvent.BUTTON3) {235 rightClick();236 } 237 }238 /**239 *- 如果是鼠标左击事件,进入左击函数处理左击相关事务;225 *
- 如果是鼠标中键事件,直接处理;226 *
- 如果是鼠标右击事件,进入右击函数处理右击相关事务。227 *
右击函数
240 * 右击函数是用于结束当前绘图状态,相当于鼠标右键结束本次绘图环境: 241 *
250 */251 private void rightClick() {252 if (PaintJButton.currentButtonFlag == PaintJButton.SINGLE_LINE) {253 if (SingleLine.clickCount != 0) {254 shapes = Arrays.copyOf(shapes, shapes.length-1);255 }256 SingleLine.clickCount = 0;257 }else if (PaintJButton.currentButtonFlag == PaintJButton.MULTI_LINE) {258 if (MultiLine.clickCount != 0) {259 MultiLine multiLine = (MultiLine)shapes[shapes.length-1];260 multiLine.setXYLengthUpDown(-1);261 } 262 MultiLine.clickCount = 0; 263 }264 }265 /**266 *- 如果当前绘制的是直线:242 *
245 *
- 右击处理近针对非第一次点击,如果没有开始绘图,没必要结束本次绘图;243 *
- 因为是直线,取消绘图,相当于直接抛弃当前直线就行了;244 *
- 同时需要将直线的全局变量clickCount置0,为下一次绘图作准备。
- 如果当前绘图的是多段线:246 *
249 *
- 右击处理近针对非第一次点击,如果没有开始绘图,没必要结束本次绘图;247 *
- 因为是多段线,取消绘图,相当于直接抛弃最后一个点就行了;248 *
- 同时需要将多段线的全局变量clickCount置0,为下一次绘图作准备。
左击函数功能如下:
267 * 左击函数,主要用于给当前选定的图形添加坐标点相关信息: 268 *269 *
272 */273 private void leftClick() {274 if (PaintJButton.currentButtonFlag == PaintJButton.SINGLE_LINE) {275 leftClickForSingleLine();276 }else if (PaintJButton.currentButtonFlag == PaintJButton.MULTI_LINE) {277 leftClickForMutiLine();278 }279 }280 /**281 *- 如果当前绘制的图形是直线,进入直线左击函数处理相关的事务;270 *
- 如果当前绘制的图形是多段线,进入多段线左击函数处理相关的事务。271 *
多段线左击函数功能如下:
282 *
293 */294 private void leftClickForMutiLine() {295 if (MultiLine.clickCount == 0) {296 shapes = Arrays.copyOf(shapes, shapes.length+1);297 MultiLine multiLine = new MultiLine();298 shapes[shapes.length-1] = multiLine;299 multiLine.setFirstPoint(clickX, clickY);300 MultiLine.clickCount++;301 }else {302 MultiLine.clickCount++;303 MultiLine multiLine = (MultiLine)shapes[shapes.length-1];304 multiLine.setOtherPoint(clickX, clickY);305 }306 }307 /**308 *- 如果是第一次点击:283 *
288 *
- shapes长度+1;284 *
- 新建多段线对象;285 *
- 将新建的对象放入shapes最后;286 *
- 将当前鼠标点击的坐标放入多段线第一个坐标值中287 *
- 点击次数+1;
- 如果不是第一次点击:289 *
292 *
- 点击次数+1;290 *
- 从shapes最后一个位置取出多段线对象;291 *
- 将当前鼠标点击的坐标放入多段线对应坐标值中
直线左击函数功能如下:
309 *- 如果是第一次点击:310 *
316 *
- shapes长度+1;311 *
- 新建直线对象;312 *
- 将新建的对象放入shapes最后;313 *
- 将当前鼠标点击的坐标放入多段线第一个坐标值中314 *
- 将当前鼠标点击的坐标放入多段线第二个坐标值中,消除原点坐标影响315 *
- 点击次数+1;
- 如果不是第一次点击:317 *
320 *
- 从shapes最后一个位置取出直线对象;318 *
- 将当前鼠标点击的坐标放入直线对应坐标值中319 *
- 点击次数清零;
重写paint方法
338 *
342 */343 @Override344 public void paint(Graphics graphics) {345 super.paint(graphics);346 this.setBackground(Color.black);347 for (int i = 0; i < shapes.length; i++) {348 shapes[i].show(graphics, Color.white);349 }350 }351 /**352 *- 调用父类paint方法;339 *
- 设置背景色:黑色;340 *
- 将shapes数组中的图形绘出来。341 *
startRun方法功能如下:
353 * 创建一个线程用于维护PaintJPanel所需的一些动态效果: 354 *
358 */359 public void startRun() {360 new Thread(){361 public void run() { 362 while (true) {363 if (PaintJButton.preButtonFlag != PaintJButton.currentButtonFlag) {364 paintJButtons[PaintJButton.preButtonFlag].setBackground(Color.white);365 }366 try {367 Thread.sleep(50);368 } catch (InterruptedException e) {369 e.printStackTrace();370 }371 repaint();372 }373 };374 }.start();375 }376 }377 public static void main(String[] args) {378 new PaintJFrame();379 }380 }381 382 /**383 *- 用于改变当前选择绘制的图形按钮,高亮突出显示,容易辨别,只有当两个标志不相同时355 * 才改变颜色,相同表示同一个按钮多次点击,主要是为了解决对同一个按钮多次点击的问题;356 *
- 50ms将图形刷新一次。357 *
一、本类设计思路如下:
384 *
- 本类主要用于保存各种图形的基本形状信息;385 *
- 仅针对目前的需求,如直线、多段线,提供保存X,Y轴坐标信息,一些基本方法.386 *
387 */388 abstract class Shape {389 /** x轴数列,默认长度为1 */390 int[] pointX = { 0 };391 /** Y轴数列,默认长度为1 */392 int[] pointY = { 0 };393 public Shape() {394 }395 public Shape(int pointX,int pointY) {396 this.pointX[0] = pointX;397 this.pointY[0] = pointY;398 }399 /** 抽象方法,因为每个图形都应该有自己的绘图方法 */400 public abstract void show(Graphics graphics,Color color);401 @Override402 public String toString() {403 return "pointX:"+Arrays.toString(pointX)+"\npointY:"+Arrays.toString(pointY);404 }405 }406 407 /**408 *
本类设计思路如下:
409 *
415 */416 class SingleLine extends Shape{417 /** 主要用于解决在绘制单条线的时候,当前鼠标点击是第一次点击,还是第二次点击 */418 public static int clickCount = 0;419 public SingleLine() {420 this.pointX = Arrays.copyOf(this.pointX, this.pointX.length+1);421 this.pointY = Arrays.copyOf(this.pointY, this.pointY.length+1);422 }423 /**424 * 设置直线第一个点坐标425 */426 public void setFirstPoint(int pointX_0, int pointY_0) {427 pointX[0] = pointX_0;428 pointY[0] = pointY_0;429 }430 /**431 * 设置直线第二个点坐标432 */433 public void setSecondPoint(int pointX_1,int pointY_1) {434 pointX[1] = pointX_1;435 pointY[1] = pointY_1;436 }437 @Override438 public void show(Graphics graphics, Color color) {439 graphics.setColor(color);440 graphics.drawLine(pointX[0], pointY[0], pointX[1], pointY[1]);441 }442 @Override443 public String toString() {444 return "pointX"+Arrays.toString(pointX)+"\n"+"pointY"+Arrays.toString(pointY);445 }446 }447 448 /**449 *- 继承自Shape类;410 *
- 一条直线有两个坐标点;411 *
- 实现show方法;412 *
- static int clickcount = 0;用一个静态变量确定鼠标点击的次数,方便绘图时413 * 确定当前所处的绘图状态。414 *
本类设计思路如下:
450 *
456 */457 class MultiLine extends Shape{458 /** 主要用于解决在绘制单条线的时候,当前鼠标点击是第一次点击,还是第几次点击 */459 public static int clickCount = 0;460 public MultiLine() {461 }462 /** 在创建MultiLine以后,加入第一个点的时候,pointX,pointY长度加+1 */463 public void setFirstPoint(int pointX_0, int pointY_0) {464 this.pointX = Arrays.copyOf(this.pointX, this.pointX.length+1);465 this.pointY = Arrays.copyOf(this.pointY, this.pointY.length+1);466 pointX[0] = pointX_0;467 pointY[0] = pointY_0;468 pointX[1] = pointX_0;469 pointY[1] = pointY_0;470 }471 /**472 * 每次多段线上多出一个点时,数组长度+1,并赋予相应的值473 */474 public void setOtherPoint(int pointX_1,int pointY_1) {475 this.pointX = Arrays.copyOf(this.pointX, this.pointX.length+1);476 this.pointY = Arrays.copyOf(this.pointY, this.pointY.length+1);477 pointX[pointX.length-2] = pointX_1;478 pointY[pointY.length-2] = pointY_1;479 pointX[pointX.length-1] = pointX_1;480 pointY[pointY.length-1] = pointY_1;481 }482 /**483 * 增加或者截取pointX,pointY长度484 */485 public void setXYLengthUpDown(int x) {486 this.pointX = Arrays.copyOf(this.pointX, this.pointX.length+x);487 this.pointY = Arrays.copyOf(this.pointY, this.pointY.length+x);488 }489 @Override490 public void show(Graphics graphics, Color color) {491 graphics.setColor(color);492 for (int i = 0; i < pointX.length-1; i++) {493 graphics.drawLine(pointX[i], pointY[i], pointX[i+1], pointY[i+1]);494 }495 }496 @Override497 public String toString() {498 return "pointX"+Arrays.toString(pointX)+"\n"+"pointY"+Arrays.toString(pointY);499 }500 }501 502 /**503 *- 继承自Shape类;451 *
- 一条多段线有多个坐标点;452 *
- 实现show方法;453 *
- static int clickcount = 0;用一个静态变量确定鼠标点击的次数,方便绘图时454 * 确定当前所处的绘图状态。455 *
一、本类设计思路如下:
504 *
507 *- 通过选择不同的按钮来绘制不同的图形;505 *
- 通过改变当前按键的背景色来区分当前选中的按钮和未被选中的按钮.506 *
二、解决方案如下:
508 *
513 */514 class PaintJButton extends JButton{515 private static final long serialVersionUID = 1L;516 /** 用于保存上一次点击的按键的标志 */517 static int preButtonFlag = 0;518 /** 用于保存当前正在使用的按键标志 */519 static int currentButtonFlag = 0;520 /** 直线的标志 */521 public static final int SINGLE_LINE = 0;522 /** 多段线的标志 */523 public static final int MULTI_LINE = 1;524 public PaintJButton() {525 }526 public PaintJButton(String string) {527 super(string); 528 }529 }530 531 /**532 *- 使用"静态"的数据来保存当前所选择的按钮(currentButtonFlag);509 *
- 为了保证上一次的按钮能恢复到没有按下的状态的颜色,,所以又需要一个510 * "静态"的数据来保存前一次所选择的按钮(preButtonFlag);511 *
- 常量SINGLE_LINE表示直线,常量MULTI_LINE表示多段线.512 *
一、本类设计思路如下:
533 *
540 */541 final class SingleJButton extends PaintJButton {542 543 private static final long serialVersionUID = 1L;544 public SingleJButton() {545 addMouseAdepter();546 }547 public SingleJButton(String string) {548 super(string);549 addMouseAdepter();550 }551 /**552 *- 本类继承自PaintJButton;534 *
- 为本类添加按钮响应事件;535 *
539 *
- preButtonFlag = currentButtonFlag;将当前的按键标志赋给前一次的按键标志;536 *
- currentButtonFlag = SINGLE_LINE;并将当前的按键标志改成多段线对应的按键值;537 *
- setBackground(Color.red);改变当前按键的背景色。538 *
添加鼠标适配器
553 * 用于添加鼠标点击事件:554 *
559 */560 private void addMouseAdepter() {561 this.addMouseListener(new MouseAdapter() {562 @Override563 public void mouseClicked(MouseEvent e) {564 preButtonFlag = currentButtonFlag;565 currentButtonFlag = SINGLE_LINE;566 setBackground(Color.red);567 }568 }); 569 }570 }571 572 /**573 *- 如果curentButtonFlag保存的当前的按钮标志,所以当点击一个按钮时,currentButtonFlag555 * 保存的值边成了前一个按钮的值,而这个值应该由preButtonFlag来保存。556 *
- 将currentButtonFlag设置成SingleJButton对应的SINGLE_LINE值;557 *
- 将按钮颜色设置为红色。558 *
一、本类设计思路如下:
574 *
581 */582 final class MultiJButton extends PaintJButton{583 private static final long serialVersionUID = 1L;584 public MultiJButton() {585 addMouseAdapter();586 }587 public MultiJButton(String string) {588 super(string);589 addMouseAdapter();590 }591 /**592 *- 本类继承自PaintJButton;575 *
- 为本类添加按钮响应事件;576 *
580 *
- preButtonFlag = currentButtonFlag;将当前的按键标志赋给前一次的按键标志;577 *
- currentButtonFlag = MULTI_LINE;并将当前的按键标志改成多段线对应的按键值;578 *
- setBackground(Color.red);改变当前按键的背景色.579 *
添加鼠标适配器
593 * 用于添加鼠标点击事件:594 *
599 */600 private void addMouseAdapter() {601 this.addMouseListener(new MouseAdapter() {602 @Override603 public void mouseClicked(MouseEvent e) {604 preButtonFlag = currentButtonFlag;605 currentButtonFlag = MULTI_LINE;606 setBackground(Color.red);607 }608 });609 }610 }- 如果curentButtonFlag保存的当前的按钮标志,所以当点击一个按钮时,currentButtonFlag595 * 保存的值边成了前一个按钮的值,而这个值应该由preButtonFlag来保存。596 *
- 将currentButtonFlag设置成MultiJButton对应的MULTI_LINE值;597 *
- 将按钮颜色设置为红色。598 *