将JDialog显示为工作表不起作用

时间:2013-07-24 20:57:05

标签: java macos swing jdialog

我目前正在使用此代码创建JDialog;

package com.kamuara.reposync.window;

import java.awt.Dialog;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.UIManager;

public class SheetDialog {

    private JFrame _windowFrame;

    public static void main(String[] args) {
        System.setProperty("apple.awt.documentModalSheet", "true");
        System.setProperty("apple.awt.brushMetalLook", "true");

        try {
            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            new SheetDialog();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SheetDialog() {
        _windowFrame = new JFrame();
        _windowFrame.setResizable(false);
        _windowFrame.setBounds(100, 100, 451, 320);
        _windowFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        _windowFrame.getContentPane().setLayout(null);
        _windowFrame.setVisible(true);

        JButton showDialogButton = new JButton("Show Dialog");
        showDialogButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                showSheetDialog(_windowFrame, "Test", "This should be a sheet dialog", "Oke");
            }
        });
        showDialogButton.setBounds(328, 263, 117, 29);
        _windowFrame.getContentPane().add(showDialogButton);
    }

    public void showSheetDialog(JFrame owner, String title, String message, String button) {
        final JDialog messageDialog = new JDialog(owner, title, Dialog.ModalityType.DOCUMENT_MODAL);
        messageDialog.setBounds(30, 0, owner.getWidth() - 60, 130);

        // TODO: only when os is osx
        messageDialog.getRootPane().putClientProperty("apple.awt.documentModalSheet", "true");
        messageDialog.setLayout(null);

        int offsetX = 25;

        JLabel titleLabel = new JLabel(title);
        titleLabel.setFont(new Font("Lucida Grande", Font.BOLD, 13));
        titleLabel.setBounds(offsetX, 10, 100, 25);
        messageDialog.getContentPane().add(titleLabel);

        JLabel messageLabel = new JLabel(message);
        messageLabel.setVerticalTextPosition(JLabel.TOP);
        messageLabel.setHorizontalTextPosition(JLabel.LEFT);
        messageLabel.setFont(new Font("Lucida Grande", Font.PLAIN, 11));
        messageLabel.setBounds(offsetX, 10, messageDialog.getWidth() - 10, messageDialog.getHeight() - 60);
        messageDialog.getContentPane().add(messageLabel);

        JButton okButton = new JButton(button);
        okButton.setBounds(messageDialog.getWidth() - 105, messageDialog.getHeight() - 35, 100, 25);
        okButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent arg0) {
                messageDialog.dispose();
            }
        });
        messageDialog.getContentPane().add(okButton);

        messageDialog.setVisible(true);
    }
}

我以前使用Java 6编译应用程序并设置clientProperty apple.awt.documentModalSheet完美地将对话框显示为OSX上的“Sheet”但现在我开始使用Java 7(更新25)和对话框不再显示为工作表。我似乎无法找到任何关于此的更新文档。他们有没有改变这个?我怎么解决这个问题?使用Sheet而不是对话框,当前的界面设计看起来更好。

更新

我发现以下错误报告似乎与我遇到的问题相同;

http://bugs.sun.com/view_bug.do?bug_id=8010197

有谁知道如何解决这个问题?我已经研究了像QuaQua这样的库,但我不想使用任何库,因为我只想要Sheet功能。

更新2

我尝试过QuaQua,但是在使用Java 7进行编译时,库目前存在完全相同的问题。任何解决方法?

更新3

将代码替换为工作样本(http://pastebin.com/PJ8VGdPb

更新4

发现SWT有一个名为SWT.SHEET的Shell类的样式,它仍然适用于Java7,我不喜欢使用类似SWT的库,但它似乎是唯一的解决方案。

3 个答案:

答案 0 :(得分:2)

据我所知,Apple没有正式发布他们的JDK 7版本。最新版本的JDK Apple针对他们的OS X进行了优化仍然是JDK 6.这也是为什么Java的更新来自AppStore的原因更新标签。这些更新不是直接来自Oracle。

如果您直接从Oracle下载了JDK 7,则这是一个更通用的非调整版本。

所以,我认为你只需等待Apple发布他们的OS X优化JDK 7。

我从Oracle下载时经历了很多OS X功能无法运行:

  • 触控板手势
  • Native Aqua Look'n'Feel无效,即使在尝试通过UIManager手动设置时也是如此。
  • 使用JOptionPane时,应用程序图标无效。
  • JMenu将坚持使用JFrame本身而不是移动到屏幕顶部。

答案 1 :(得分:1)

在JDK修复错误之前,你必须自己实现工作表。

关键点是:

  1. 使用窗口框架的玻璃窗格来保持“工作表”对话框
  2. 使用GridBagLayout(使用NORTH锚点)将对话框置于窗格的顶部中心
  3. 通过重复绘制对话框显示/消失时为表单设置动画,每次绘制更多/更少的对话框部分
  4. 以下是示例代码

    import java.awt.Color;
    import java.awt.Dimension;
    import java.awt.Graphics;
    import java.awt.Graphics2D;
    import java.awt.GraphicsConfiguration;
    import java.awt.GraphicsEnvironment;
    import java.awt.GridBagConstraints;
    import java.awt.GridBagLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.awt.image.BufferedImage;
    
    import javax.swing.Box;
    import javax.swing.JComponent;
    import javax.swing.JDialog;
    import javax.swing.JFrame;
    import javax.swing.JPanel;
    import javax.swing.Timer;
    import javax.swing.border.LineBorder;
    
    public class SheetableJFrame extends JFrame implements ActionListener {
    
        public static final int INCOMING = 1;
        public static final int OUTGOING = -1;
        public static final float ANIMATION_DURATION = 1000f;
        public static final int ANIMATION_SLEEP = 50;
    
        JComponent sheet;
        JPanel glass;
        Sheet animatingSheet;
        boolean animating;
        int animationDirection;
        Timer animationTimer;
        long animationStart;
        BufferedImage offscreenImage;
    
        public SheetableJFrame() {
            super();
            glass = (JPanel) getGlassPane();
            glass.setLayout(new GridBagLayout());
            animatingSheet = new Sheet();
            animatingSheet.setBorder(new LineBorder(Color.black, 1));
        }
    
        public JComponent showJDialogAsSheet(JDialog dialog) {
            sheet = (JComponent) dialog.getContentPane();
            sheet.setBorder(new LineBorder(Color.black, 1));
            glass.removeAll();
            animationDirection = INCOMING;
            startAnimation();
            return sheet;
        }
    
        public void hideSheet() {
            animationDirection = OUTGOING;
            startAnimation();
        }
    
        private void startAnimation() {
            glass.repaint();
            // clear glasspane and set up animatingSheet
            animatingSheet.setSource(sheet);
            glass.removeAll();
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.anchor = GridBagConstraints.NORTH;
            glass.add(animatingSheet, gbc);
            gbc.gridy = 1;
            gbc.weighty = Integer.MAX_VALUE;
            glass.add(Box.createGlue(), gbc);
            glass.setVisible(true);
    
            // start animation timer
            animationStart = System.currentTimeMillis();
            if (animationTimer == null) animationTimer = new Timer(ANIMATION_SLEEP, this);
            animating = true;
            animationTimer.start();
        }
    
        private void stopAnimation() {
            animationTimer.stop();
            animating = false;
        }
    
        // used by the Timer
        public void actionPerformed(ActionEvent e) {
            if (animating) {
                // calculate height to show
                float animationPercent = (System.currentTimeMillis() - animationStart) / ANIMATION_DURATION;
                animationPercent = Math.min(1.0f, animationPercent);
                int animatingHeight = 0;
    
                if (animationDirection == INCOMING) {
                    animatingHeight = (int) (animationPercent * sheet.getHeight());
                } else {
                    animatingHeight = (int) ((1.0f - animationPercent) * sheet.getHeight());
                }
                // clip off that much from sheet and put it into animatingSheet
                animatingSheet.setAnimatingHeight(animatingHeight);
                animatingSheet.repaint();
    
                if (animationPercent >= 1.0f) {
                    stopAnimation();
                    if (animationDirection == INCOMING) {
                        finishShowingSheet();
                    } else {
                        glass.removeAll();
                        glass.setVisible(false);
                    }
                }
            }
        }
    
        private void finishShowingSheet() {
            glass.removeAll();
            GridBagConstraints gbc = new GridBagConstraints();
            gbc.anchor = GridBagConstraints.NORTH;
            glass.add(sheet, gbc);
            gbc.gridy = 1;
            gbc.weighty = Integer.MAX_VALUE;
            glass.add(Box.createGlue(), gbc);
            glass.revalidate();
            glass.repaint();
        }
    
        class Sheet extends JPanel {
            Dimension animatingSize = new Dimension(0, 1);
            JComponent source;
            BufferedImage offscreenImage;
    
            public Sheet() {
                super();
                setOpaque(true);
            }
    
            public void setSource(JComponent source) {
                this.source = source;
                animatingSize.width = source.getWidth();
                makeOffscreenImage(source);
            }
    
            public void setAnimatingHeight(int height) {
                animatingSize.height = height;
                setSize(animatingSize);
            }
    
            private void makeOffscreenImage(JComponent source) {
                GraphicsConfiguration gfxConfig = GraphicsEnvironment.getLocalGraphicsEnvironment()
                        .getDefaultScreenDevice().getDefaultConfiguration();
                offscreenImage = gfxConfig.createCompatibleImage(source.getWidth(), source.getHeight());
                Graphics2D offscreenGraphics = (Graphics2D) offscreenImage.getGraphics();
                source.paint(offscreenGraphics);
            }
    
            public Dimension getPreferredSize() {
                return animatingSize;
            }
    
            public Dimension getMinimumSize() {
                return animatingSize;
            }
    
            public Dimension getMaximumSize() {
                return animatingSize;
            }
    
            public void paint(Graphics g) {
                // get the bottom-most n pixels of source and paint them into g, where n is height
                BufferedImage fragment = offscreenImage.getSubimage(0, offscreenImage.getHeight() - animatingSize.height,
                        source.getWidth(), animatingSize.height);
                g.drawImage(fragment, 0, 0, this);
            }
        }
    }
    

    测试代码

    import java.beans.PropertyChangeEvent;
    import java.beans.PropertyChangeListener;
    
    import javax.swing.JDialog;
    import javax.swing.JOptionPane;
    
    public class SheetTest extends Object implements PropertyChangeListener {
    
        JOptionPane optionPane;
        SheetableJFrame frame;
    
        public static void main(String[] args) {
            new SheetTest();
        }
    
        public SheetTest() {
            frame = new SheetableJFrame();
            // build JOptionPane dialog and hold onto it
            optionPane = new JOptionPane("Do you want to close?", JOptionPane.QUESTION_MESSAGE, JOptionPane.CANCEL_OPTION);
            frame.setSize(640, 480);
            frame.setVisible(true);
            optionPane.addPropertyChangeListener(this);
    
            JDialog dialog = optionPane.createDialog(frame, "irrelevant");
            frame.showJDialogAsSheet(dialog);
        }
    
        public void propertyChange(PropertyChangeEvent pce) {
            if (pce.getPropertyName().equals(JOptionPane.VALUE_PROPERTY)) {
                System.out.println("Selected option " + pce.getNewValue());
                frame.hideSheet();
            }
        }
    }
    

    参考
    http://oreilly.com/pub/h/4852
    http://book.javanb.com/swing-hacks/swinghacks-chp-6-sect-6.html

答案 2 :(得分:0)

这是我提出的一个超级狡猾的黑客,它设置了JDK现在看起来忘记设置并手动将窗口定位在正确位置的标志。虽然还有一个缺失的阴影,所以我想知道是否有人可以改进它。 ;)

这与内部类和私有字段混淆,因此它可能会破坏任何给定的JDK新版本,但它仍然适用于8u5。也许它会对这些内部AWT类的结构有所了解。

public static void makeSheet(Dialog dialog) {
    dialog.addNotify();
    ComponentPeer peer = dialog.getPeer();

    // File dialogs are CFileDialog instead. Unfortunately this means this hack
    // can't work for those. :(
    if (peer instanceof LWWindowPeer) {
        LWWindowPeer windowPeer = (LWWindowPeer) dialog.getPeer();
        //XXX: Should check this before casting too.
        CPlatformWindow platformWindow = (CPlatformWindow) windowPeer.getPlatformWindow();
        try {
            Method method = CPlatformWindow.class.getDeclaredMethod(
                "setStyleBits", int.class, boolean.class);
            method.setAccessible(true);
            method.invoke(platformWindow, 64 /* CPlatformWindow.SHEET */, true);

            Window parent = dialog.getOwner();
            dialog.setLocation(dialog.getLocation().x,
                               parent.getLocation().y + parent.getInsets().top);
        } catch (Exception e) {
            Logger.getLogger(SheetHack.class.getName())
                .log(Level.WARNING, "Couldn't call setStyleBits", e);
        }
    }
}