Day18

피오·2021년 11월 24일
0
post-thumbnail

AWT(Abstract Window Toolkit)

  • 자바의 추상화된 GUI 프로그래밍 툴킷.
  • GUI 프로그래밍을 추상화했기 때문에 운영체제를 가리지 않고 개발할 수 있는 장점이 있다.
  • 하지만 필요 최소한의 기능만을 가져와서 일반화한 것이기 때문에 각 OS GUI 프로그램의 모든 기능을 발할 수 없다는 단점도 존재.

그림판

  • 18일차 미션으로 그림판을 구현해보았다.
  • 버튼을 클릭하여 여러 도형을 그릴 수 있다.

Frame 초기화

  • 도형이 그려질 출발 좌표를 찍기 위한 변수와 버튼을 가지고 있는 패널, 프레임에 그림을 그려줄 Graphics객체를 가지고 있다.
  • 도형의 굵기를 조절하기 위해 Graphics2D 객체도 받아 놓았으나 기능은 아직 미구현.
  • Frame의 여러 설정을 initFrame() 메서드로 분리.
  • Frame 내 컴포넌트 위치 설정을 자유롭게 하기 위해 LayoutManager세팅을 null로 없애주었다.
  • 종료 버튼에 windowClosing 이벤트 핸들러를 달아주기 위해 Adapter 클래스를 하나 만들었다.
public PaintFrame() {
  this.startX = 0;
  this.startY = 0;
  this.toolBoxPanel = new ToolBoxPanel();
  initFrame();
  this.graphics = getGraphics(); //Frame이 렌더링되기 전에 호출하면 null을 리턴함.
  this.graphics2D = (Graphics2D) graphics;
}

private void initFrame() {
  initFrame(DEFAULT_FRAME_WIDTH, DEFAULT_FRAME_HEIGHT);
}

private void initFrame(int width, int height) {
  setLayout(null);
  setBackground(Color.WHITE);
  setSize(width, height);

  Toolkit tk = Toolkit.getDefaultToolkit();
  Dimension screenSize = tk.getScreenSize();
  setLocation((screenSize.width / 2) - (width / 2), (screenSize.height / 2) - (height / 2));
  addWindowListener(new AdapterWindowListener() {
    @Override
    public void windowClosing(WindowEvent e) {
      e.getWindow().dispose();
    }
  });

    this.add(toolBoxPanel);
    setVisible(true);
  }



버튼 Panel 초기화

  • 버튼 리스트를 만들어서 버튼마다 마우스 이벤트 리스너를 달아주었다.

  • 버튼마다 actionPerforemd 이벤트 핸들러를 달아주기 위해 MouseMotionLister, MouseListener 두 개를 같이 구현해놓은 어댑터 클래스를 만들었다.
    public class AdapterMouseListener implements MouseMotionListener, MouseListener {...}

  • 버튼 패널 클래스가 어댑터를 멤버변수로 가지고 있는데, 이는 Frame에 마우스 리스너를 삭제할 때 메서드 인자로 넘기기 위한 것이다.

  • 버튼을 클릭할 때마다 Frame에 등록된 마우스 리스너를 삭제하고, 선택한 도구에 맞게 새로운 리스너를 등록해준다(직선, 연필만 구현).

도형 그리기

  • 도형별로 마우스 이벤트가 다르게 동작해야 하므로 버튼을 클릭할 때마다 Frame에 마우스 이벤트 리스너를 새로 할당해주었다.
  • 연필의 경우 drawLine의 출발지점이 계속 바뀌어야 한다.
    • mouseMoved 이벤트 핸들러에서 출발지점을 계속 바꿔줌으로써 마우스가 움직일 때마다 좌표 변경이 이루어진다. 이 부분이 없으면 아래 사진과 같이 도형을 그리다 마지막으로 마우스를 뗀 지점의 좌표가 출발지점으로 되어있어 직선이 그려진다.
    • mouseDragged 이벤트 핸들러에서는 변경된 출발지점 좌표와 현재 마우스 좌표를 이용해 계속해서 직선을 그려나간다. 이로 인해 매우 짧은 직선이 연속되어 그려진다.
  • 직선의 경우 처음 마우스를 press한 출발지점이 변경되지 않는다(사각형, 삼각형, 원도 동일할 것 같다).
    • 마우스를 release할 때의 지점과 press시의 좌표를 출발 지점으로 하여 drawLine()한다.
  • 지우개는 굵기가 중요할 거라 생각했는데, Graphics2D로 굵기를 지정할 수 있는 것 같아서 알아보던 중 시간이 부족해 미구현 상태로 제출.
private final String[] buttonLabels = {"연필", "직선", "사각형", "원", "세모", "지우개"};
private final List<Button> toolBtns;
private AdapterMouseListener mouseListener;

public ToolBoxPanel() {
  this.toolBtns = makeButtons();
  initPanel();
  addActionEventListenerToButtons();
}

private void initPanel() {
  setLayout(new GridLayout(1, 6));
  setBounds(0, 30, 1024, 100);
  for (Button toolBtn : toolBtns) {
    this.add(toolBtn);
  }
}

public void addActionEventListenerToButtons() {
    for (Button toolBtn : toolBtns) {
      toolBtn.addActionListener(new ActionListener() {
        @Override
        public void actionPerformed(ActionEvent e) {
          removeMouseListenerFromPaintFrame(e);
          addMouseListenerToPaintFrame(e);
        }
      });
    }
  }
  
private void removeMouseListenerFromPaintFrame(ActionEvent e) {
    Button btn = (Button) e.getSource();
    PaintFrame paintFrame = (PaintFrame) btn.getParent().getParent();
    paintFrame.removeMouseMotionListener(mouseListener);
    paintFrame.removeMouseListener(mouseListener);
  }

private void addMouseListenerToPaintFrame(ActionEvent e) {
  Button btn = (Button) e.getSource();
  PaintFrame paintFrame = (PaintFrame) btn.getParent().getParent();
  Graphics graphics = paintFrame.getPaintFrameGraphics();
  Graphics2D graphics2D = paintFrame.getPaintFrameGraphics2D();

  switch (btn.getLabel()) {
    case "연필":
        mouseListener = new AdapterMouseListener() {
          @Override
          public void mouseMoved(MouseEvent e) {
            paintFrame.setX(e.getX());
            paintFrame.setY(e.getY());
          }

          @Override
          public void mouseDragged(MouseEvent e) {
            if (e.getModifiersEx() != MouseEvent.BUTTON1_DOWN_MASK) {
              return;
            }
            graphics.drawLine(paintFrame.getX(), paintFrame.getY(), e.getX(), e.getY());
            paintFrame.setX(e.getX());
            paintFrame.setY(e.getY());
          }
        };
        paintFrame.addMouseMotionListener(mouseListener);
        break;
      case "직선":
        mouseListener = new AdapterMouseListener() {
          @Override
          public void mouseDragged(MouseEvent e) {
            //TODO: 마우스 드래그시 출발점부터 마우스 포인터까지 계속 선이 따라다니도록 구현. 선이 마구 생기면 안됨.
          }

          @Override
          public void mousePressed(MouseEvent e) {
            paintFrame.setX(e.getX());
            paintFrame.setY(e.getY());
          }

          @Override
          public void mouseReleased(MouseEvent e) {
            graphics.drawLine(paintFrame.getX(), paintFrame.getY(), e.getX(), e.getY());
          }
        };
        paintFrame.addMouseMotionListener(mouseListener);
        paintFrame.addMouseListener(mouseListener);
        break;
      case "사각형":
        mouseListener = new AdapterMouseListener() {
        };
        break;
      case "원":
        break;
      case "세모":
        break;
      case "지우개":
        mouseListener = new AdapterMouseListener() {
          @Override
          public void mouseDragged(MouseEvent e) {

          }
        };
        break;

    }
  }



에러 사항

현재 마우스 리스너 등록 순서가 꼬여서 그림판이 로딩 되고 나서 버튼을 한번 클릭 해줘야 작동함.

오늘 고생한 것

  • AWT 컴포넌트에 이벤트리스너 등록 및 삭제하기
    • remove 리스너 메서드 인자에, 등록할 때와 똑같은 인스턴스를 넘겨야 하는 것 같다.
    • 그리고 리스너를 어느 클래스에 둘 것인지를 고민 해봐야 할 것 같다.
  • 이벤트 객체로 상위 컴포넌트 거슬러 올라가기.
    • 중간중간 형변환 해줘야 하다보니 디버깅 하기가 성가셨다.
    • 애초에 구조 설계부터 틀려먹은 것 같긴 하다. 설계를 잘 했더라면 서로 잘 포함된 관계가 되지 않았을까.. 싶다.

회고

오랜만에 디버깅으로 api를 뒤져보면서 삽질을 많이 했다. AWT 컴포넌트 자체가 익숙하지 않은 상태에서 클래스를 나눠보려고 하니, 뭐가 되고 안되는지를 몰라 썼다 지웠다를 많이 반복했다. 그래도 역시 삽질하면서 하나하나 구현해 나가는 게 재미있었다.

profile
블로그 이전했습니다. https://pzbg.tistory.com/

0개의 댓글