Отображение 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, но теперь я начал использовать Java 7 (обновление 25), и диалог не был больше отображается в виде листа. Похоже, я не могу найти документацию об обновлении. Они что-то изменили? Как я могу это решить? Современный дизайн интерфейса выглядит лучше с листом, чем с диалогом.
Обновление
Я нашел следующий отчет об ошибке, который, похоже, та же проблема, что и я,
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, который по-прежнему работает в Java7, я не предпочитаю использовать такую библиотеку, как SWT, но это единственное решение.
Ответы
Ответ 1
Насколько я знаю, Apple официально не опубликовала свою версию JDK 7. Последняя версия JDK Apple оптимизирована для их OS X по-прежнему JDK 6. Вот почему обновления для Java поставляются через AppStore обновить. Эти обновления не поступают прямо из Oracle.
Если вы загрузили JDK 7 напрямую из Oracle, это более общая версия без исправления.
Итак, я думаю, вам просто придется ждать, пока Apple выпустит оптимизированный для ОС X JDK 7.
Я испытал много функций OS X, которые не будут работать при загрузке из Oracle:
- Жесты трекпада.
- Native Aqua Look'n'Feel не работает даже при попытке установить его вручную через UIManager.
- Значок приложения не работает при использовании JOptionPane.
- JMenu будет придерживаться самого JFrame, вместо того, чтобы переходить в верхнюю часть экрана.
Ответ 2
Кажется, прежде чем JDK исправит ошибку, вы должны сами реализовать Лист.
Ключевыми моментами являются:
- Используйте стеклянную панель окна, чтобы удерживать диалоговое окно "Лист".
- Используйте GridBagLayout (с привязкой NORTH), чтобы поместить диалог в верхнюю область области
- Анимация листа при показе/исчезновении путем многократного рисования диалогового окна, при каждом краске больше/меньше части диалогового окна
Ниже приведен пример кода
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
Ответ 3
Вот супер хитроумный хак, с которым я столкнулся, который устанавливает флаг, который теперь 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);
}
}
}