5

I'm creating a virtual piano type program in Java Swing. My area for the piano keys right now is a JPanel with a horizontal BoxLayout containing white JButtons as white keys. I want to add black keys as well, and have them overlap the white keys.

There are two different approaches I've tried. One is using the OverlayLayout. Unfortunately there isn't much documentation online for the OverlayLayout manager, and it's not available in the NetBeans GUI builder. I don't have a clue how to make it work. The second thing I've tried is using JLayeredPanes. I can't seem to figure that one out either, even after messing with it in Netbeans.

So I think my question is pretty simple. What is the best approach, if there is one, to add JButtons on top of other JButtons? Or maybe there is an alternative to using JButtons for piano keys?

EDIT

I've combined aioobe's and dacwe's code to get the result I wanted. I basically used dacwe's z-ordering with aioobe's basic dimensions (scaled up a bit) and the mod 7 part too. I also added some variables to make things more clear. This is what I have now.

import javax.swing.*;
import java.awt.Color;

public class Test2 {

public static void main(String[] args) {

    JFrame frame = new JFrame("Test");

    JLayeredPane panel = new JLayeredPane();
    frame.add(panel);

    int maxKeys = 8;

    int width = 60;
    int height = 240;

    for (int i = 0; i < maxKeys; i++) {
        JButton b = new JButton();
        b.setBackground(Color.WHITE);
        b.setLocation(i * width, 0);
        b.setSize(width, height);

        panel.add(b, 0, -1);
    }

    int width2 = 48;
    int height2 = 140;
    for (int i = 0; i < maxKeys; i++) {
        int j = i % 7;
        if (j == 2 || j == 6)
            continue;

        JButton b = new JButton();
        b.setBackground(Color.BLACK);
        b.setLocation(i*(width) + (width2*3/4), 0);
        b.setSize(width2, height2);

        panel.add(b, 1, -1);
    }

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(500,280);
    frame.setVisible(true);
    }
}

Thanks guys! Now I need to attach the listener and text to these buttons somehow.

aharlow
  • 267
  • 1
  • 4
  • 9

6 Answers6

5

I would write a custom PianoLayoutManager and position the black keys with a higher z-order than the white buttons. Create your own "constraint" class that allows you to add components like this:

add(new WhiteKey(), new PianoLayoutConstraint(WHITE, 1);
add(new WhiteKey(), new PianoLayoutConstraint(WHITE, 2);
...
add(new WhiteKey(), new PianoLayoutConstraint(WHITE, n);

add(new BlackKey(), new PianoLayoutConstraint(BLACK, 1);
add(new BlackKey(), new PianoLayoutConstraint(BLACK, 2);
...
add(new BlackKey(), new PianoLayoutConstraint(BLACK, m);

From Using Swing Components tutorial trail

Note: The z-order determines the order that components are painted. The component with the highest z-order paints first and the component with the lowest z-order paints last. Where components overlap, the component with the lower z-order paints over the component with the higher z-order.

Here is an ugly hack that uses null-layout to get you started.

import java.awt.Color;
import javax.swing.*;

class PianoComponent extends JPanel {

    PianoComponent() {

        setLayout(null);

        for (int i = 0; i < 20; i++) {
            JButton key = new JButton();
            key.setBackground(Color.WHITE);
            key.setLocation(i * 20, 0);
            key.setSize(20, 120);
            add(key);
            setComponentZOrder(key, i);
        }

        for (int i = 0; i < 20; i++) {
            int j = i % 7;
            if (j == 2 || j == 6)
                continue;

            JButton key = new JButton();
            key.setBackground(Color.BLACK);
            key.setLocation(i * 20 + 12, 0);
            key.setSize(16, 80);
            add(key);
            setComponentZOrder(key, 0);
        }
    }
}

public class Test {
    public static void main(String[] args) {
        JFrame jf = new JFrame("Piano!");
        jf.setSize(400, 200);
        jf.add(new PianoComponent());
        jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        jf.setVisible(true);
    }
}

enter image description here

aioobe
  • 413,195
  • 112
  • 811
  • 826
  • Components z order are messed up!! – dacwe Nov 25 '10 at 19:58
  • Ehhh, compiled and run and moved mouse over it... it will render the buttons in the wrong order when they are re-rendered. – dacwe Nov 25 '10 at 20:01
  • That looks great! But I just tried it, and the layer ordering changes when the button is pressed or highlighted. Can they stay in the same Z ordering? And can I still bind action listeners to anonymous JButtons? I've only got one octave on my panel anyway. – aharlow Nov 25 '10 at 20:06
  • ah, yes. I see it too. Have a look at dacwes answer... using a JLayeredPane and the `add` method that takes two `ints` should do the trick. – aioobe Nov 25 '10 at 20:11
2

This is an example of layeredpane (it works as you expect):

public static void main(String[] args) {

    JFrame frame = new JFrame("Test");

    JLayeredPane panel = new JLayeredPane();
    frame.add(panel);


    for (int i = 0; i < 8; i++) {
        JButton b = new JButton();
        b.setBackground(Color.WHITE);
        b.setLocation(i * 20, 0);
        b.setSize(20, 100);

        panel.add(b, 0, -1);
    }

    for (int i = 0; i < 4; i++) {
        JButton b = new JButton();
        b.setBackground(Color.BLACK);
        b.setLocation(10 + i * 40, 0);
        b.setSize(20, 70);

        panel.add(b, 1, -1);
    }

    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(400, 300);
    frame.setVisible(true);
}

However, you need to lay out the piano keys in the correct order (:)) :

alt text

dacwe
  • 43,066
  • 12
  • 116
  • 140
  • Excellent! This may seem like a silly question, but can I assign ActionListeners and text to those anonymous JButtons? – aharlow Nov 25 '10 at 21:00
  • Post another question so that I get get an accept! ;) well yes, just add `b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { /*your code*/ } });` – dacwe Nov 25 '10 at 22:19
1

Apart from writing your own layout manager, you could just use a null LayoutManager (that is, no automatic layout management) and position the keys manually. Then use setComponentZOrder on the container to make sure the black keys appear above the white ones.

jackrabbit
  • 5,525
  • 1
  • 27
  • 38
0

GridBag layout does allow widget overlapping; You could use overlapping spans to construct the layout You want. But yes, writing own layout manager wold be nicer.

barti_ddu
  • 10,179
  • 1
  • 45
  • 53
0

I've done a couple games in Swing, and I find it's easier just not to use a layout manager or make your own. For example I have a card game and a custom layout manager that looks at where the card has been played and positions it based on that and the current panel size.

JOTN
  • 6,120
  • 2
  • 26
  • 31
0

Check out Darryl's solution (the last reply) in this posting. It gets tricky and uses a "Button" as the black button so it paints on top of the white "JButton". This probably won't work in new versions of the JDK that allow AWT and Swing components to be mixed.

However, the neat part is that the keys actually produce a sound when clicked.

camickr
  • 321,443
  • 19
  • 166
  • 288