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

先日作成したSwingによる非矩形ウィンドウはJWindowのみ可能で、JFrameでは対応できなかった。

その後、いろいろと調べて試行錯誤を繰り返してみた結果、JFrameでも同様に非矩形ウィンドウを作成することができた。以下、そのソースコード。

先日作成したソースコードを流用して、またいじくりまわしているので、汚いのはご容赦のほど。

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.Window;
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.geom.RoundRectangle2D;
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.JLabel;
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 TransSwingWindow extends JFrame 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;

    Window 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 TransSwingWindow();

//        SwingUtilities.invokeLater(new Runnable(){
        java.awt.EventQueue.invokeLater(new Runnable() {
            public void run() {
//                javax.swing.JFrame.setDefaultLookAndFeelDecorated(true);
                new TransSwingWindow();
            }
        });
    }

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

        //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();
        }

        window = this;

        //イメージファイルの読み込み
        ImageIcon icon = null;
        int width = DEFAULT_SIZE;
        int height = DEFAULT_SIZE;
        try {
            icon = new ImageIcon(getClass().getClassLoader().getResource("suiseiseki.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");
        }

        addPopupMenu();
        addMouseEvent();

        if (window instanceof javax.swing.JFrame) {
            ((JFrame)window).setTitle(TITLE + " " + VERSION);
        }
        this.setSize(width, height);

        if (window instanceof javax.swing.JFrame) {
            ((JFrame)window).setUndecorated(true);
        }

        Container contentPane = getContentPane();

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

        //今回はJWindowに画像を描画させないで、JLabelを使用してみた
        JLabel label = new JLabel(icon);
        contentPane.add(label);

//        pack();

        if (window instanceof javax.swing.JFrame) {
            ((JFrame)window).setResizable(false);
            ((JFrame)window).setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }

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

//        AWTUtilities.setWindowShape(this, new RoundRectangle2D.Double(0, 0, getWidth(), getHeight(), 100, 100));
        AWTUtilities.setWindowShape(this, shape);
        AWTUtilities.setWindowOpacity(this, (Float)0.70f);

        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() {

            @Override
            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;
                }
            }

            @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 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);
//        }
//    }
}

今回は、先日のものをJFrameに対応させた上で半透明処理を施してみた。

ソースコード中でウィンドウを非矩形、半透明にしている部分は、以下の部分。

        AWTUtilities.setWindowShape(this, shape);
        AWTUtilities.setWindowOpacity(this, (Float)0.70f);

実際に動作している様子を動画にしたものがこちら。動画の中で実行しているjarファイルはこれ

動画サイズを抑えた結果、画質がやや荒くなってしまっているが、翠星石の形状にウィンドウが作られて、かつ半透明になっているのが確認できると思う。

ただ1点問題があって、ポップアップメニューなどの表示も非矩形・半透明の設定の影響を受けてしまい、表示が欠けてしまう(下図参照)。

この点については、私の実装方法に問題があるのか、JFrameで非矩形ウィンドウを実装した際に起きる不具合なのかは不明。

とりあえず、SwingでJWindowだけでなくJFrameでも半透明+非矩形ウィンドウが実装できたので、よしとしよう。

2009年04月29日(水) 23時36分  

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の雑記に感謝!

2009年04月26日(日) 23時33分