【问题标题】:processing - Having Object Oriented programming issues处理 - 有面向对象的编程问题
【发布时间】:2014-04-21 01:22:02
【问题描述】:

所以基本上,我的目标是创建一个类似于 MS Paint 软件的程序,我可以在其中通过鼠标拖动绘制形状并更改其颜色。这是我的一个很长的脚本。我对 OOP 还很陌生,所以我很难让所有功能协同工作。

主要

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 <= x + size) && (mouseY >= 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 应该绘制圆形(到目前为止,只有这些功能应该工作。我还需要对它们应用颜色)。

我知道矩形和圆形类可以正常工作,因为我在将所有内容放在一起之前分别测试了它们。按钮(功能)工作但不正确(我应该使用鼠标将形状拖到任何所需的位置,而这个程序只允许它们出现我放置它们的位置并且只能从角落,好像我只是从 x 和 y 位置 (0,0)) 拉伸形状。我唯一的问题是我似乎无法将按钮正确连接到形状功能,并使它们一起工作。

【问题讨论】:

    标签: java oop processing


    【解决方案1】:

    如果不查看 main 方法,就很难评估代码的正确性。您的“主”类的设置方式似乎使得 mousePressed() 方法旨在轮询按钮并根据它们的状态设置绘图模式。

    但是,如果不查看 main 方法,我无法知道您是否正确轮询按钮。

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

    在主类中:

    //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(...)
            ...
    }
    

    【讨论】:

      【解决方案2】:

      快速浏览一下您的代码,看起来在使用绘图命令、编写类以及它们如何交互方面存在一些混淆。 我会开始非常基础:

      • 将所有内容拆分为简单易懂的独立任务
      • 独立实施每个任务并对其进行测试
      • 根据需要简化和概括代码
      • 开始将实施整合到一个主项目中,逐个测试每个步骤。

      例如,让我们来完成使用鼠标交互式绘制矩形的任务。如果存储了鼠标按下时的位置,可以将它们与坐标的差取到最近的鼠标坐标:矩形的尺寸:

      //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 中)可能会很方便:一个图层渲染已绘制的内容,另一个临时在顶部渲染预览。

      幸运的是,在 Processing via 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);
      

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

      回到主要功能,您需要:

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

      这意味着有多种形状和多种颜色,但每次只使用一种。将上述成分放在一起,如果更容易的话,您可以在没有 GUI 或类的情况下对功能进行原型设计。只需使用键盘快捷键替换此测试阶段的 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 的吗?每个处理小程序已经有一个,命名为g,因此这是一种使用单个函数绘制多个 PGraphics 实例(Processing 和我们的canvas)但每个具有不同透明度的快速而肮脏的方法。

      使用这种方法值得注意的是,我们不是为每个绘制的形状存储多个圆形/矩形实例,而是简单地在画布中渲染一次并让它(一个PGraphics)对象存储像素。缺点是一旦绘制完成,您将无法检索以何种顺序以及以何种坐标/尺寸绘制的形状,但如果您不需要这些细节,它会更简单。

      如果您确实需要这些,可能值得一试PShape(尤其是GROUP,RECT,ELLIPSE 选项)。

      现在让我们添加按钮! Anthony 的建议很棒,简化的 buttonClicked 建议非常适合处理。 通常你会使用Interface 为按钮定义一个侦听器并让处理草图实现这个接口,但只需一个函数负责处理按钮,这是一个很好的解决方法。

      这是一个基本的实现:

      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();
        }
      }
      

      请注意,一些代码正在处理先前选择的颜色和形状模式按钮。这实际上不是必需的,但是向用户显示一些关于当前选择的形状/颜色的反馈(在本例中以轮廓的形式)是很好的。

      在 UI 方面还有很多需要探索的地方。例如,尽管在功能方面有多个按钮,但它们主要用作两个单选按钮组。一旦掌握了 OOP 基础知识,您就可以考虑创建一个通用的 GUIElement 类,例如按钮/复选框/单选按钮/滑块等其他 UI 元素可以使用(例如 draw() 函数、x、y、宽度、高度, ETC。)。然后每个班级都会专门研究这个超级班级。例如 Button 将扩展 GUIElement 并且 ToggleButton 将扩展 Button。也许 HBox 或 VBox 可以方便地将元素分组到水平或垂直组中。等等。玩得开心!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2017-07-05
        • 2022-01-20
        • 2011-01-23
        • 2012-01-24
        • 2015-04-22
        • 2010-09-18
        • 2013-09-05
        相关资源
        最近更新 更多