Search code examples
javaswingtransparencyhighlightjtextpane

How to arrange a transparent JTextPane in other components with disabled highlighting


Could someone give me a hint on how to display a transparent JTextPane that is the child of other components such as a JScrollPane and a JPanel, please. I've used an AlphaContainer which is available here for transparency.

This is working:

chatTextPane = new JTextPane();
chatTextPane.setEditable(false);
chatTextPane.setHighlighter(null);
chatTextPane.setBackground(new Color(255,255,255,100));
AlphaContainer ac = new AlphaContainer(chatTextPane);   

JPanel chatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
chatPanel.setOpaque(false);
chatPanel.add(ac);

It looks like this:

transparent JTextPane

However if I wrap it into a non-opaque JScrollPane, it is not transparent anymore. And if I put the JScrollPane in an AlphaContainer, highlighting is enabled again (and buggy):

chatTextPane = new JTextPane();
chatTextPane.setEditable(false);
chatTextPane.setHighlighter(null);
chatTextPane.setBackground(new Color(255,255,255,100));
JScrollPane scrollPane = new JScrollPane(chatTextPane);
scrollPane.setOpaque(false);
AlphaContainer ac = new AlphaContainer(scrollPane);

JPanel chatPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
chatPanel.setOpaque(false);
chatPanel.add(ac);

It looks like this then:

broken transparent JTextPane in JScrollPane

Finally I originally want to add it to another panel with a JTextField in a BoxLayout as well. If I try this, the JTextField is displayed twice: once correctly at the bottom, but also at the top (non-editable).

transparent JTextPane with JTextField

I've been trying to fix this for a couple of hours now and I'm thankful for anything.

Here's runnable code for the second example:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;

import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

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

    public MyClass() {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException
                        | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.getContentPane().setBackground(new Color(100, 0, 0, 255));

                //
                JTextPane chatTextPane = new JTextPane();
                chatTextPane.setEditable(false);
                chatTextPane.setHighlighter(null);
                chatTextPane.setPreferredSize(new Dimension(300, 300));
                chatTextPane.setBackground(new Color(255,255,255,100));
                chatTextPane.setText("testtesttesttesttesttest");
                JScrollPane scrollPane = new JScrollPane(chatTextPane);
                scrollPane.setOpaque(false);
                AlphaContainer ac = new AlphaContainer(scrollPane);

                JPanel chatPanel = new JPanel();
                chatPanel.setOpaque(false);
                chatPanel.add(ac);              
                //
                
                frame.add(chatPanel);
                frame.pack();
                frame.setVisible(true);
            }

        });
    }

    private class AlphaContainer extends JComponent {
        private JComponent component;

        public AlphaContainer(JComponent component) {
            this.component = component;
            setLayout(new BorderLayout());
            setOpaque(false);
            component.setOpaque(false);
            add(component);
        }

        /**
         * Paint the background using the background Color of the contained component
         */
        @Override
        public void paintComponent(Graphics g) {
            g.setColor(component.getBackground());
            g.fillRect(0, 0, getWidth(), getHeight());
        }
    }
}

Solution

  • Swing components have a parent/child relationship. In your case it looks like:

    • JFrame
      • content pane (with custom background color)
        • chat panel
          • JScrollPane
            • JViewport
              • JTextPane

    Swing paints the parent components before the child components. So you want the content pane (which is opaque) to be painted first, then the "ChatPanel" (which will be semi-transparent) painted next and then all other child components to be non-opaque.

    So a couple of changes:

    1. The AlphaContainer needs to be used on the component being added to the content pane.

    You do this by using:

    //frame.add(chatPanel);
    frame.add( new AlphaContainer(chatPanel) );
    

    Note: the AlphaContainer will make the component added to it non-opaque, so you don't need to explicitly use setOpaque( false );

    1. You need to make the JTextPane non-opaque (since is no loner added to the AlphaContainer).

    2. You also need to make the JViewport non-opaque.

    This is done by using:

    scrollPane.getViewport().setOpaque( false );
    

    Also don't use:

    chatTextPane.setPreferredSize(new Dimension(300, 300));
    

    The scrollbars will only appear when the preferred size of the component is greater than the size of the scroll pane. If you hardcode the preferred size, then the scrollbar won't appear because the preferred size won't change as you add text to the text area.

    Set the preferred size on the scroll pane.