Make a simple GUI editor in Java Swing using Swing components
Asked Answered
A

3

6

I'm currently working on a project in which I need a very simple editor for GUI-like objects. This editor would be a canvas on which well known GUI widgets can be placed. For example one can place a button and a textfield on there, move them around and resize them. No interaction with the widgets themselves is needed.

I've been trying to accomplish this by adapting a very simple paint tutorial, I thought it would be easy to implement it this way, yet I bump into many problems with drawing custom shapes and text on a canvas and dragging and dropping those shapes.

I was wondering if I could reuse real Swing widgets on a JPanel and let the user place, move around and resize them. If so, what are the things I should look into?

I know this might seem very little information but I'm honestly stuck in searching for a solution.

Affection answered 16/8, 2012 at 14:58 Comment(4)
have a look at javaFX2 !Marcelenemarcelia
How would javaFX2 help me in this? It seems like just another framework. I was thinking I could maybe use the SWING widgets with their enabled variable on false and using absolute positioning. How I would make this interact with the mouse is a bit unclear to me.Affection
Did you look this ?Arose
yes indeed gontard, but I'm not quite sure how I would handle the adding, dragging and dropping or resizing of the components by the user.Affection
D
6

I thought I should remember the good old Swing days and have a little bit of fun writing you a proof of concept for this. It supports adding a button to the main panel and then move and some basic resize.

It is just a proof of concept, but it answers a lot of the questions you already have. I added a few TODOs there, such as you will know what to do next.

Enjoy...

import java.awt.BorderLayout;
import java.awt.Cursor;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToolBar;

public class UIBuilder {
  private static final int NONE = -1;

  private static final int BORDER = 3;

  private JFrame frame = new JFrame("Builder");

  private JToolBar toolbar = new JToolBar();

  private JPanel main = new JPanel();

  private int startX = NONE;

  private int startY = NONE;

  private int prevX = NONE;

  private int prevY = NONE;

  private boolean resize = false;

  public UIBuilder() {
    frame.setBounds(100, 100, 600, 450);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().setLayout(new BorderLayout());
    frame.getContentPane().add(toolbar, BorderLayout.PAGE_START);
    frame.getContentPane().add(main, BorderLayout.CENTER);
    frame.setVisible(true);
    buildToolbox();
    buildMainPanel();
  }

  private void buildToolbox() {
    JButton button = new JButton("Button");
    button.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        JButton btn = new JButton("Button");
        addComponent(btn);
      }
    });
    toolbar.add(button);
  }

  private void buildMainPanel() {
    main.setLayout(null);
  }

  private void addComponent(JComponent comp) {
    comp.setBounds(10, 10, 80, 24);

    comp.addMouseListener(new MouseAdapter() {
      public void mouseReleased(MouseEvent e) {
        startX = NONE;
        startY = NONE;
        ((JComponent) e.getSource()).setCursor(Cursor.getDefaultCursor());
      }

      public void mousePressed(MouseEvent e) {
        startX = e.getX();
        startY = e.getY();
      }
    });

    comp.addMouseMotionListener(new MouseMotionAdapter() {
      public void mouseMoved(MouseEvent e) {
        JComponent source = (JComponent) e.getSource();
        int x = e.getX();
        int y = e.getY();
        Rectangle bounds = source.getBounds();
        resize = x < BORDER || y < BORDER || Math.abs(bounds.width - x) < BORDER || Math.abs(bounds.height - y) < BORDER;
        if (resize) {
          // TODO: there are a lot of resize cursors here, this is just of proof of concept
          source.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
        } else {
          source.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));
        }
      }

      public void mouseDragged(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        if (startX != NONE && startY != NONE) {
          JComponent source = (JComponent) e.getSource();
          Rectangle bounds = source.getBounds();
          int deltaX = x - startX;
          int deltaY = y - startY;
          if (resize) {
            // TODO: handle all resize cases, left, right,...
            source.setSize(Math.max(10, bounds.width + x - prevX), Math.max(10, bounds.height + y - prevY));
          } else {
            source.setLocation(bounds.x + deltaX, bounds.y + deltaY);
          }
          // TODO: make sure you don't resize it as much as it disappears
          // TODO: make sure you don't move it outside the main panel
        } else {
          startX = x;
          startY = y;
        }
        prevX = x;
        prevY = y;
      }
    });
    main.add(comp);
    main.validate();
    main.repaint();
  }

  public static void main(String[] args) {
    new UIBuilder();
  }

}
Denby answered 16/8, 2012 at 17:17 Comment(0)
D
2

This sounds like a fun project.

I would definitely go on the JPanel as the container, with a layout set to null. In order to be able to move the components like JButton, JLabel in the container, you would have to listen for MouseListener and MouseMotionListener events on the added components and handle them accordingly. You would have to call validate() and repaint() on the container whenever one component is moved or resized.

Denby answered 16/8, 2012 at 15:26 Comment(8)
In order to add a new component, would it be possible to have the user drag over the Jpanel, during which the component would appear and continually resize until the user releases the mouse, in which case it would be placed upon the Jpanel? I'm thinking about using a MouseListener and MouseMotionListener on the JPanel to accomplish that, I would add the component in the pressed method and resize it (alter its bounds) during the dragged method.Affection
In order to drag the component from a "toolbox" you would have to use stuff from the java.awt.dnd package. I would suggest that in a first version to just hit a button in the toolbar and that would add a new JButton in your container that just waits to be moved and resized.Denby
Seems like a fair workaround, I have never used the awt.dnd package so I won't ga about exploring that if I haven't got the first part working yet. Thanks for the help so far Dan, I've been searching for solutions a long time now.Affection
Sure. I can help you if you get stuck developing this tool.Denby
There's still one thing unclear to me, how would I differentiate the dragging for moving a component, with that for resizing it?Affection
If the mouse location is close to the border, it is resize, if not, it is move.Denby
Dan, can I establish that from the event?Affection
You can. See my other answer on the question, with a working proof of concept.Denby
D
1

I would implement this in a mixed approach: Create a class that implements the mouse responses (dragging, resizing). This class has the responsibility of managing the user interaction.

For the actual painting of the components, use real Swing components (the user interaction class would have a reference to the component its supposed to represent and delegate the actual rendering to that component).

This approach avoids most of the complexities that arise when extending the original Swing components, yet you can still reuse all their painting code.

Dismissive answered 16/8, 2012 at 15:28 Comment(5)
It seems this approach would be more complex, yet I don't see the benefits of it very clearly if I compare it to the approach Dan suggests.Affection
In my approach, you implement the user interaction just once for a component that has no own intrinsic mouse behavior. In Dan's approach you have to deal with the (Swing) components inherent mouse responses. Imagine for example a JScrollPane or JScrollBar, those will respond to mouse event on their own. Or JButton responds to a click. Thats why I suggest using your own draggable component, so the delegate does not get in the way.Dismissive
but if you set the components enabled variable to false, doesn't that solve the problem too?Affection
If you dont mind the component having the disabled look it should avoid most if not all problems.Dismissive
It seems disabled won't work because it also disables all mouseevents from being fired. So I'll have to extend the widgets indeed. Thanks for offering me the solution.Affection

© 2022 - 2024 — McMap. All rights reserved.