Javaで非矩形・半透明ウィンドウ(Swing版)

以前、Javaで非矩形ウィンドウの記事を書いたが、その時点は結局Wicocoの拡張APIを使わないでSWTで実装したのだが、Swingでも実装できることがTACの雑記というサイトで紹介されていた。

実装できるようになったのがJava6 Update10のリリース以降からなのか、それとも以前からなのかはわからないが、Swingでも非矩形ウィンドウが実装できることがわかったのはうれしい限り。

というわけで、早速TACの雑記のコードを参考に、あれこれいじくりまわしてみた。以下がそのソースコード。そして、出来上がったのがこれ。実際に動作している様子はこちら

package swing;

import java.awt.Component;
import java.awt.Container;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.geom.GeneralPath;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;

import javax.swing.ImageIcon;
import javax.swing.JComponent;
//import javax.swing.JFrame;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JWindow;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import com.sun.awt.AWTUtilities;

public class SwingWindow extends JWindow implements ActionListener {
    /**
     *
     */
    private static final long serialVersionUID = 1L;

//    public static final String TITLE    = "Swing版 非矩形ウィンドウテスト";
//    public static final String VERSION    = "Ver0.01";
    public static final int DEFAULT_SIZE = 64;

    JWindow window = null;
    Shape shape = null;
    Image image = null;
    private Point start = null;
    private Point point = null;
    private JPopupMenu popupMenu = null;

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

    public SwingWindow() {
        super();
        getRootPane().setDoubleBuffered(true);
        ((JComponent)getContentPane()).setDoubleBuffered(true);

        window = this;

        //イメージファイルの読み込み
        int width = DEFAULT_SIZE;
        int height = DEFAULT_SIZE;
        try {
            ImageIcon icon = new ImageIcon(getClass().getClassLoader().getResource("panel.png"));

            shape = getImageShape(icon);

            image = icon.getImage();
            width = Math.max(icon.getIconWidth(), DEFAULT_SIZE);
            height = Math.max(icon.getIconHeight(), DEFAULT_SIZE);
        } catch (Exception e) {
            System.out.println("ImageLoadError");
        }

        SwingUtilities.invokeLater(new Runnable(){
            public void run() {
                AWTUtilities.setWindowShape(window, shape);
            }
        });

        //UIをWindowsに設定
        //右クリックメニューで使用
        try {
            UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
        } catch (ClassNotFoundException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            // TODO 自動生成された catch ブロック
            e.printStackTrace();
        }

        addPopupMenu();
        addMouseEvent();

//        this.setTitle(TITLE + " " + VERSION);
        this.setSize(width, height);

        Container contentPane = getContentPane();

        //JWindowに非矩形ウィンドウの元となる画像を描画させると
        //右クリックでJPopupMenuを起動した時に表示ががおかしくなるので、
        //JPanelに画像を描画させる
        Canvas panel = new Canvas(image);

        contentPane.add(panel);

//        pack();

//        this.setResizable(false);
//        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        this.setLocationRelativeTo(null);
        this.setLocation(100, 100);

        this.setVisible(true);
    }

    private void addPopupMenu() {
        popupMenu = new JPopupMenu();
        JMenuItem menuItem = new JMenuItem();
        menuItem.setText("終了" );
        menuItem.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                System.exit(0);
            }
        });
        popupMenu.add(menuItem);
    }

    private void addMouseEvent() {
        this.addMouseListener(new MouseAdapter() {
            public void mouseClicked(MouseEvent e) {
                Component com = (Component)e.getComponent();

                switch (e.getButton()) {
                case MouseEvent.BUTTON1:
                    popupMenu.setVisible(false);
                    break;
                case MouseEvent.BUTTON3:
                    popupMenu.show(com, e.getX(), e.getY());
                    break;
                default:
                    popupMenu.setVisible(false);
                    break;
                }
            }
        });

        this.addMouseListener(new MouseAdapter() {

            @Override
            public void mouseReleased(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ
                Component c = (Component) e.getComponent();
                point = c.getLocation();
                c.setLocation(point);
            }

            @Override
            public void mousePressed(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ
                start = e.getPoint();
                point = e.getPoint();

//                if (e.getClickCount() >= 2) {
//                    System.exit(0);
//                }
            }
        });

        this.addMouseMotionListener(new MouseMotionListener() {

            @Override
            public void mouseMoved(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ

            }

            @Override
            public void mouseDragged(MouseEvent e) {
                // TODO 自動生成されたメソッド・スタブ
                Component c = (Component) e.getComponent();
                point = c.getLocation(point);
                int x = point.x - start.x + e.getX();
                int y = point.y - start.y + e.getY();
                c.setLocation(x, y);
            }
        });
    }

    /**
     * ImageIconからアルファ値が0でない画素からなるShapeを得る
     * */
    public Shape getImageShape(ImageIcon icon){
        GeneralPath shape = new GeneralPath();

        final BufferedImage bi = new BufferedImage(icon.getIconWidth(), icon.getIconHeight(), BufferedImage.TYPE_INT_ARGB);
        icon.paintIcon(null, bi.createGraphics(), 0, 0);

        ColorModel cm = bi.getColorModel();

        //元(上)のループが縦方向にスキャンしていたので、横方向にスキャンするように変更してみた
        //いまどきのOS/PCではグラフィック処理にラスターを意識しなくても良い?
        for (int y = 0; y < bi.getHeight(); y++) {
            for (int x = 0; x < bi.getWidth(); x++) {

                //完全に透過していなければshapeに追加
                if (cm.getAlpha(bi.getRGB(x, y)) > 0) {
                    int start = x;
                    int width = 0;
                    while (x < bi.getWidth() && cm.getAlpha(bi.getRGB(x++, y)) > 0) {    //forをwhileに変更
                        width++;
                    }

                    shape.append(new Rectangle(start, y, width, 1), true);
                }
            }
        }

        return shape;
    }

//    @Override
//    public void paint(Graphics g) {
//        g.drawImage(image, 0, 0, this);
//    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // TODO 自動生成されたメソッド・スタブ
    }

    private class Canvas extends JPanel {
        /**
         *
         */
        private static final long serialVersionUID = 1L;

        private Image image = null;

        public Canvas(Image image) {
            super();
            this.image = image;
        }

        public void paintComponent(Graphics g) {
            if (image == null) {
                return;
            }

            g.drawImage(image, 0, 0, this);
        }
    }
}

このコードを書く上で、ちょっとはまった箇所があるので紹介。

私がいじくりまわしたコードでは、終了は右クリックでポップアップメニューを表示させて選ぶように実装している。

しかし、参考にさせていただいたコードのまま(JWindowクラスで画像を描画)だと、以下の画像のような現象が発生してしまった。

どうやら追加したポップアップメニューが引き金となっているようなのだが、原因は不明。repaint()をコールしても改善されない。

結局、非矩形ウィンドウの下地となる画像はJWindowクラスで直接描画せずに、JPanelクラスを実装して描画させることで一応解決できた。

一応解決できたのだが、原因がわからずじまいなので、なんだかすっきりしない(苦笑)。

けれども、良い勉強になった。TACの雑記に感謝!

1件のコメント

コメントは受け付けていません。