Making JButtons overlap
Asked Answered
C

6

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.

Caves answered 25/11, 2010 at 19:23 Comment(0)
E
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

Eye answered 25/11, 2010 at 19:34 Comment(4)
Components z order are messed up!!Getup
Ehhh, compiled and run and moved mouse over it... it will render the buttons in the wrong order when they are re-rendered.Getup
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.Caves
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.Eye
G
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

Getup answered 25/11, 2010 at 20:8 Comment(2)
Excellent! This may seem like a silly question, but can I assign ActionListeners and text to those anonymous JButtons?Caves
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*/ } });Getup
T
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.

Teddi answered 25/11, 2010 at 19:39 Comment(2)
How would you handle the problems aioobe has?Getup
Hmm, on my OSX running 1.6.0_22, I don't see any issues with the z-ordering.Teddi
F
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.

Forked answered 25/11, 2010 at 19:35 Comment(0)
N
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.

Nemertean answered 25/11, 2010 at 19:37 Comment(0)
B
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.

Bistro answered 25/11, 2010 at 21:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.