处理 – 具有面向对象的编程问题

所以基本上,我的目标是创建像MS Paint软件这样的程序,我可以用鼠标拖动绘制形状并改变颜色。 这是我的一个很长的剧本。 我对OOP相当新,所以我在努力使所有function协同工作时遇到了困难。

主要

Button button1, button2, button3, button4, button5, button6, button7; Rect rect; Circle circle; int mode = 1; void setup() { size(900,600); smooth(); rect = new Rect(0,0,0,0, new PImage()); circle = new Circle(0,0,0,0, new PImage()); color gray = color(234); color black = color(0); color white = color(255); color red = color(255,0,0); color green = color(0,255,0); color blue = color(0,0,255); button1 = new Button(10, 60, 20, white, gray, black); //draw rectangle function button2 = new Button(10, 100, 20, white, gray, black); //draw circle function button3 = new Button(10, 140, 20, red, gray, black); //option of color red button4 = new Button(10, 160, 20, green, gray, black); //option of color green button5 = new Button(10, 180, 20, blue, gray, black); //option of color blue button6 = new Button(10, 220, 20, black, gray, black); //fill entire shape button7 = new Button(10, 240, 20, white, gray, black); //fill nothing } void draw() { button1.setp(); button2.setp(); } void mousePressed() { if (button1.press()) { mode = 1; } if (button2.press()) { mode = 2; } if (button3.press()) { mode = 3; } if (button4.press()) { mode = 4; } if (button5.press()) { mode = 5; } if (button6.press()) { mode = 6; } if (button7.press()) { mode = 7; } } void manageButtons() { button1.update(); button2.update(); button3.update(); button4.update(); button5.update(); button6.update(); button7.update(); button1.display(); button2.display(); button3.display(); button4.display(); button5.display(); button6.display(); button7.display(); } void mouseReleased() { button1.release(); button2.release(); button3.release(); button4.release(); button5.release(); button6.release(); button7.release(); } void mouseDragged() { //rect.drag(); } 

按钮类

 class Button { int x, y; // the x- and y-coordinate int size; // dimension (width & height) color baseGray; // Default gray value color overGray; // Value when the mouse is over color pressGray; // Value when the mouse is pressed boolean over = false; // true when the mouse is over boolean pressed = false;// true when pressed Button(int xp, int yp, int s, color b, color o, color p) { x = xp; y = yp; size = s; baseGray = b; overGray = o; pressGray = p; } void setp() { background(255); manageButtons(); //stroke(); if (mode == 1) { rect.drawing(); } else if (mode == 2) { circle.drawing(); } } void update() { if ((mouseX >= x) && (mouseX = y) && (mouseY <= y + size)) { over = true; } else { over = false; } } boolean press() { if (over) { pressed = true; return true; } else { return false; } } void release() { pressed = false; rect.release(); circle.release(); } void display() { if (pressed) { fill(pressGray); } else if (over) { fill(overGray); } else { fill(baseGray); } stroke(0); rect(x, y, size, size); } } 

圆圈类

 class Circle { int x, y; int xp, yp; PImage a; Circle(int dragx, int dragy, int movex, int movey, PImage image) { x = dragx; y = dragy; xp = movex; yp = movey; a = image; } void display() { smooth(); background(255); a = get(); stroke(0); fill(255); //255,255,10); } void drawing() { image(a, 0, 0); //background(a); float sizex = xp - x; float sizey = yp - y; if (mousePressed && mouseButton == LEFT) { ellipse(x, y, sizex, sizey); } } void press() { x = mouseX; y = mouseY; } void release() { xp = mouseX; yp = mouseY; noLoop(); a = get(); loop(); } void drag() { xp = 80 + mouseX; yp = 80 + mouseY; } } 

矩形类

 class Rect { int x, y; int xp, yp; PImage a; Rect(int dragx, int dragy, int movex, int movey, PImage image) { x = dragx; y = dragy; xp = movex; yp = movey; a = image; } void display() { smooth(); background(255); a = get(); stroke(0); fill(255); //255,255,10); } void drawing() { image(a, 0, 0); //background(a); float sizex = xp - x; float sizey = yp - y; if (mousePressed && mouseButton == LEFT) { rect(x, y, sizex, sizey); } } void press() { x = mouseX; y = mouseY; } void release() { xp = mouseX; yp = mouseY; noLoop(); a = get(); loop(); } void drag() { xp = mouseX; yp = mouseY; } } 

使用上面的处理脚本(java),我遇到了使用我创建的按钮使矩形和圆类正常工作的问题。 按钮1应该绘制矩形,按钮2应该绘制圆圈(到目前为止,只有这些function可以工作。我还需要为它们应用颜色)。

我知道矩形和圆形类正常工作,因为我在将所有内容放在一起之前已经单独测试过它们。 按钮(function)工作但不正确(我应该使用鼠标将形状拖动到任何所需的位置,而这个程序只允许它们出现在我放置它们的位置,而且只能从角落出现,就好像我只是从x和y位置(0,0)拉伸形状。 我唯一的问题是我似乎无法正确地将按钮加入到形状函数中,并使它们一起工作。

如果不看主要方法,很难评估代码的正确性。 您的“主”类的设置方式似乎是方法mousePressed()用于轮询按钮并根据其状态设置绘图模式。

但是,如果不查看主要方法,我无法确定您是否正确轮询按钮。

如果您希望使用面向对象的方法,那么您将需要使用Observer模式 。 本质上,您的按钮都将引用具有buttonClicked(Button btn)方法的“main”对象。 单击一个按钮时,它会运行’main’对象的buttonClicked(Button btn)方法。 提供的参数将是对单击按钮的引用,以便main可以选择要使用的适当模式。 演示代码如下:

在主类:

 //Give the button a reference to the main object button1 = new button(this); //receive notifications from the button public buttonClicked(Button btn) { if(btn.equals(button1)) mode = 1; if(...) ... } 

快速查看代码,看起来有些混淆使用绘图命令,编写类以及这些如何交互。 我会开始真正基本的:

  • 将所有内容拆分为简单易于理解的独立任务
  • 独立实施每项任务并进行测试
  • 如果需要,简化并概括代码
  • 开始将实现集中到一个主项目中,逐个进行测试。

例如,让我们使用鼠标以交互方式绘制矩形的任务。 如果在按下鼠标时存储鼠标的位置,则可以将它们与坐标之间的差异取为最近的鼠标坐标:矩形的尺寸:

 //an object to store current mouse coordiates PVector mouse = new PVector(); //...and the previous mouse coordinates PVector pmouse = new PVector(); void setup(){ size(400,400); } void draw(){ background(255); //compute width,height as difference between current and previous mouse positions float w = mouse.x - pmouse.x; float h = mouse.y - pmouse.y; //draw the shape according to it's mode rect(pmouse.x,pmouse.y,w,h); } //set both previous and current mouse coordinates - this helps reset coordinates and finalize a shape void mouseSet(){ pmouse.set(mouseX,mouseY); mouse.set(mouseX,mouseY); } void mousePressed(){ mouseSet(); } void mouseDragged(){//update only the current mouse position, leaving pmouse outdated mouse.set(mouseX,mouseY); } void mouseReleased(){ mouseSet(); } 

如果要渲染形状的预览和形状本身,使用图层(如在Photoshop中)可能很方便:一个图层渲染已绘制的图像,另一个图层临时渲染顶部的预览。

幸运的是,有一些类似于通过PGraphics处理。 初始化PGraphics实例后,您可以使用您习惯使用的相同绘图命令绘制它。 唯一的问题是你需要先调用beginDraw() ,然后调用endDraw() 。

关于PGraphics的另一个很酷的事情是它扩展了PImage ,这意味着你可以将它显示为一个(更不用说,做图像处理):

 PGraphics canvas; size(400,400); //create a PGrahpics layer canvas = createGraphics(width,height); //intialize drawing canvas.beginDraw(); //draw something, pretty similar you'd draw in Processing canvas.background(255); canvas.rect(200,200,150,100); //finish drawing canvas.endDraw(); //PGraphics extends PImage, hence it can drawn as one image(canvas,0,0); 

在您的代码中,按钮类保持对矩形和其他对象的引用。 理想情况下,您希望对象松散耦合 。 这个想法是让类处理自己的function,独立于主程序或其他类。 尝试将Button类复制到新草图中并立即使用。 目前你需要复制其他不相关的类以及编译。 目标是编写一个可以在任何草图中轻松重用的Button类。

回到主要function,您需要:

  1. 绘图形状模式(矩形或椭圆形)
  2. 绘图颜色(来自颜色列表)

这意味着有多种形状和多种颜色,但每种颜色一次使用。 将上述成分放在一起,如果更容易,您可以在没有GUI或类的情况下对function进行原型设计。 只需使用键盘快捷键替换此测试阶段的GUI按钮(使用1/2/3控制颜色,r用于矩形,c用于圆圈):

 PGraphics canvas;//a layer to persist shapes onto //shape modes int MODE_RECTANGLE = 0; int MODE_ELLIPSE = 1; //a reference to the currently selected shape mode int mode = MODE_RECTANGLE; //various colours color c1 = color(192,0,0); color c2 = color(225,225,0); color c3 = color(0,0,192); //a reference to the currently selected colour color current = c1; //an object to store current mouse coordiates PVector mouse = new PVector(); //...and the previous mouse coordinates PVector pmouse = new PVector(); void setup(){ size(400,400); //setup ellipse mode to draw from corner like the rect()'s default setting ellipseMode(CORNER); strokeWeight(3); //initialise the canvas - this allows you to draw with the same commands, but as a separate layer canvas = createGraphics(width,height); canvas.beginDraw(); //replicate ellipse mode and stroke weight in canvas layer as well (so what's being drawn matches preview) canvas.ellipseMode(CORNER); canvas.strokeWeight(3); canvas.background(255); canvas.endDraw(); } void draw(){ //draw the layer first image(canvas,0,0); //overlay the preview on top using 50% transparency (as a visual hint it's a preview) draw(g,127); } //a function that draws into a PGraphics layer (be it our canvas or Processing's) void draw(PGraphics g,int transparency){ g.fill(current,transparency); //compute width,height as difference between current and previous mouse positions float w = mouse.x - pmouse.x; float h = mouse.y - pmouse.y; //draw the shape according to it's mode if(mode == MODE_ELLIPSE) { g.ellipse(pmouse.x,pmouse.y,w,h); } if(mode == MODE_RECTANGLE) { g.rect(pmouse.x,pmouse.y,w,h); } } //set both previous and current mouse coordinates - this helps reset coordinates and finalize a shape void mouseSet(){ pmouse.set(mouseX,mouseY); mouse.set(mouseX,mouseY); } void mousePressed(){ mouseSet(); } void mouseDragged(){//update only the current mouse position, leaving pmouse outdated mouse.set(mouseX,mouseY); } void mouseReleased(){ //commit the shape to the canvas layer canvas.beginDraw(); draw(canvas,255); canvas.endDraw(); //set both mouse positions mouseSet(); } //use keys to test: 1,2,3 = colours, r/c = shape mode void keyPressed(){ if(key == '1') current = c1; if(key == '2') current = c2; if(key == '3') current = c3; if(key == 'r') mode = MODE_RECTANGLE; if(key == 'c') mode = MODE_ELLIPSE; } 

还记得以前我们如何使用PGraphics吗? 每个Processing小程序都有一个名为g ,因此这是一种使用单个函数绘制到多个PGraphics实例(Processing和我们的canvas )但使用不同透明度的快速而又脏的方法。

使用这种方法值得注意的是,不是为绘制的每个形状存储多个Circle / Rectangle实例,我们PGraphics将一次渲染到canvas中并让它(一个PGraphics )对象存储像素。 缺点是一旦绘制出来,就无法检索以哪种顺序绘制的形状以及具有什么坐标/尺寸,但如果您不需要这些细节则更简单。

如果您确实需要这些,可能值得查看PShape (特别是它的GROUP,RECT,ELLIPSE选项)。

现在让我们添加按钮! 安东尼的建议很棒,简化的按钮点击建议非常适合处理。 通常你会使用一个接口为按钮定义一个监听器,并让Processing sketch实现这个接口,但只需要一个函数负责处理一个很好的解决方法中的按钮。

这是一个基本的实现:

 Button a = new Button("Button A",5,5,90,20,color(200),color(0)); Button b = new Button("Button B",5,30,90,20,color(200),color(0)); void draw(){ background(255); //update button states based on mouse interaction a.update(mouseX,mouseY,mousePressed); b.update(mouseX,mouseY,mousePressed); //render buttons on screen a.draw(); b.draw(); } void onButtonClicked(Button btn){ println(btn.label + " was pressed"); } class Button{ float w,h,x,y;//width, height and position color bg = color(200);//background colour color fg = color(0);//foreground colour String label;//text displayed boolean isOver,wasPressed;//button states int pw = 10;//padding on width boolean outline;//draw an outline or not Button(String label,float x,float y,float w,float h,color fg,color bg){ this.x = x; this.y = y; this.w = w; this.h = h; this.label = label; this.fg = fg; this.bg = bg; } void update(int mx,int my,boolean md){ //check bounding box isOver = ((mx >= x && mx <= (x+w))&&(my >= y && my <= (y+h))); if(isOver && md){ //check if it was not previously pressed to call the onButtonClicked function only once (similar to debouncing) if(!wasPressed){ onButtonClicked(this); wasPressed = true; } }else wasPressed = false; } void draw(){ //pushStyle()/popStyle() isolates drawing styles (similar to how pushMatrix()/popMatrix() isolates coordinate transformations pushStyle(); if(outline){ strokeWeight(3); stroke(127); }else{ noStroke(); } fill(isOver ? fg : bg);//the ? : is a lazy one liner way of doing if/else: (booleanExpression ? doIfTrue : doIfFalse) rect(x,y,w,h); fill(isOver ? bg : fg); text(label,x+pw,y+h*.75); popStyle(); } } 

现在,使用键盘快捷键将此Button类添加到上一代码中是相当简单的:

 PGraphics canvas;//a layer to persist shapes onto //shape modes int MODE_RECTANGLE = 0; int MODE_ELLIPSE = 1; //a reference to the currently selected shape mode int mode = MODE_RECTANGLE; //various colours color c1 = color(192,0,0); color c2 = color(225,225,0); color c3 = color(0,0,192); //a reference to the currently selected colour color current = c1; //an object to store current mouse coordiates PVector mouse = new PVector(); //...and the previous mouse coordinates PVector pmouse = new PVector(); //UI //Button constructor: label, x,y, width, height, foreground, background Button rectMode = new Button("\u25A0",5,5,30,20,color(200),color(0));//beying lazy/having fun with text: the \u25A0 is using the unicode for a square shape as text http://www.fileformat.info/info/unicode/char/25a0/index.htm Button ellipseMode = new Button("\u25CF",5,30,30,20,color(200),color(0));//http://www.fileformat.info/info/unicode/char/25CF/index.htm Button color1 = new Button("",5,55,30,20,color(255,0,0),c1); Button color2 = new Button("",5,80,30,20,color(255,255,0),c2); Button color3 = new Button("",5,105,30,20,color(00,0,255),c3); Button[] gui = new Button[] {rectMode, ellipseMode, color1, color2, color3}; //reference to previous mode button and previous colour button Button prevMode = rectMode; Button prevColor = color1; void setup(){ size(400,400); //setup ellipse mode to draw from corner like the rect()'s default setting ellipseMode(CORNER); strokeWeight(3); //initialise the canvas - this allows you to draw with the same commands, but as a separate layer canvas = createGraphics(width,height); canvas.beginDraw(); //replicate ellipse mode and stroke weight in canvas layer as well (so what's being drawn matches preview) canvas.ellipseMode(CORNER); canvas.strokeWeight(3); canvas.background(255); canvas.endDraw(); //ui outline current options rectMode.outline = true; color1.outline = true; } void draw(){ //draw the layer first image(canvas,0,0); //overlay the preview on top using 50% transparency (as a visual hint it's a preview) draw(g,127); //update and draw UI for(int i = 0; i < gui.length; i++){ gui[i].update(mouseX,mouseY,mousePressed); gui[i].draw(); } } //a function that draws into a PGraphics layer (be it our canvas or Processing's) void draw(PGraphics g,int transparency){ g.fill(current,transparency); //compute width,height as difference between current and previous mouse positions float w = mouse.x - pmouse.x; float h = mouse.y - pmouse.y; //draw the shape according to it's mode if(mode == MODE_ELLIPSE) { g.ellipse(pmouse.x,pmouse.y,w,h); } if(mode == MODE_RECTANGLE) { g.rect(pmouse.x,pmouse.y,w,h); } } //set both previous and current mouse coordinates - this helps reset coordinates and finalize a shape void mouseSet(){ pmouse.set(mouseX,mouseY); mouse.set(mouseX,mouseY); } void mousePressed(){ mouseSet(); } void mouseDragged(){//update only the current mouse position, leaving pmouse outdated mouse.set(mouseX,mouseY); } void mouseReleased(){ //commit the shape to the canvas layer canvas.beginDraw(); draw(canvas,255); canvas.endDraw(); //set both mouse positions mouseSet(); } void onButtonClicked(Button b){ if(b == color1) current = c1; if(b == color2) current = c2; if(b == color3) current = c3; if(b == rectMode) mode = MODE_RECTANGLE; if(b == ellipseMode) mode = MODE_ELLIPSE; if(b == color1 || b == color2 || b == color3){ b.outline = true; if(prevColor != null) prevColor.outline = false; prevColor = b; } if(b == rectMode || b == ellipseMode){ b.outline = true; if(prevMode != null) prevMode.outline = false; prevMode = b; } } class Button{ float w,h,x,y;//width, height and position color bg = color(200);//background colour color fg = color(0);//foreground colour String label;//text displayed boolean isOver,wasPressed;//button states int pw = 10;//padding on width boolean outline;//draw an outline or not Button(String label,float x,float y,float w,float h,color fg,color bg){ this.x = x; this.y = y; this.w = w; this.h = h; this.label = label; this.fg = fg; this.bg = bg; } void update(int mx,int my,boolean md){ //check bounding box isOver = ((mx >= x && mx <= (x+w))&&(my >= y && my <= (y+h))); if(isOver && md){ //check if it was not previously pressed to call the onButtonClicked function only once (similar to debouncing) if(!wasPressed){ onButtonClicked(this); wasPressed = true; } }else wasPressed = false; } void draw(){ //pushStyle()/popStyle() isolates drawing styles (similar to how pushMatrix()/popMatrix() isolates coordinate transformations pushStyle(); if(outline){ strokeWeight(3); stroke(127); }else{ noStroke(); } fill(isOver ? fg : bg);//the ? : is a lazy one liner way of doing if/else: (booleanExpression ? doIfTrue : doIfFalse) rect(x,y,w,h); fill(isOver ? bg : fg); text(label,x+pw,y+h*.75); popStyle(); } } 

请注意,某些代码正在处理先前选择的颜色和形状模式按钮。 这实际上并不需要,但向用户显示当前选择的形状/颜色的一些反馈(在这种情况下以轮廓的forms)是很好的。

在UI方面,还有很多值得探索的内容。 例如,尽管在function方面存在多个按钮,但它们主要用作两个单选按钮组。 一旦掌握了OOP Basics,您可以考虑创建一个常见的GUIElement类,例如按钮/复选框/单选按钮/滑块等其他UI元素可以使用(例如draw()函数,x,y,width,height等)。 然后每个class级都会专攻这个超级class级。 例如,Button将扩展GUIElement,而ToggleButton将扩展Button。 也许HBox或VBox可以方便地在水平或垂直组中分组元素。 等玩得开心!

绘制矩形

绘制圆圈