Java 2D - JLabel 仅部分绘制在 JList 中
Java 2D - JLabel only partially painted inside JList
我正在尝试在 Swing 中进行 2D 绘画,我想在空的 JList
中间画一个 JLabel
。
因此我想出了:
public class MyJList<T> extends JList<T> {
private final JLabel emptyLabel = new JLabel("Whatever");
@Override
public void paint(final Graphics g) {
super.paint(g);
if (getModel().getSize() == 0 && !emptyLabel.getText().isBlank()) {
final var preferredSize = emptyLabel.getPreferredSize();
final var x = (getWidth() - preferredSize.width) / 2;
final var y = (getHeight() - preferredSize.height) / 2;
final var g2 = (Graphics2D) g.create(x, y, preferredSize.width, preferredSize.height);
try {
emptyLabel.setBounds(0, 0, preferredSize.width, preferredSize.height);
emptyLabel.paint(g2);
} finally {
g2.dispose();
}
}
}
}
然而,当文本到达列表的边界时,它似乎被截断了:
如果我增加裁剪矩形的宽度,标签就会被完全绘制。
我究竟做错了什么?这样画画好吗?
完成最小示例:
class Main {
public static void main(final String[] args) {
final var myFrame = new MyFrame();
myFrame.setVisible(true);
}
public static class MyFrame extends JFrame {
public MyFrame() {
super("Example");
final var list = new MyJList<String>();
final var contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(list, BorderLayout.CENTER);
pack();
setMinimumSize(new Dimension(160, 200));
setLocationRelativeTo(null);
}
}
public static class MyJList<T> extends JList<T> {
private final JLabel emptyLabel = new JLabel("Selezionare un oggetto");
{
emptyLabel.setEnabled(false);
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if (getModel().getSize() == 0) {
final var preferredSize = emptyLabel.getPreferredSize();
final var listBounds = getBounds();
final var x = (listBounds.width - preferredSize.width) / 2;
final var y = (listBounds.height - preferredSize.height) / 2;
final var g2 = (Graphics2D) g.create(x, y, preferredSize.width, preferredSize.height);
try {
emptyLabel.setBounds(0, 0, preferredSize.width, preferredSize.height);
emptyLabel.paint(g2);
} finally {
g2.dispose();
}
}
}
}
}
计算文本大小很困难,而且有很多陷阱。 JList
、JTable
等使用渲染器窗格而不是直接在单元格渲染器上调用 paint
是有原因的。
只有当组件是框架层次结构的一部分时,它才能正确确定文本的大小。那是因为只有这样组件才会对当前显示设备使用正确的 GraphicsEnvironment
。 GraphicsEnvironment
包含布局文本所需的信息,如抗锯齿支持、字距调整、小数字体支持等。
所以正确的方法是将标签添加到 JList
:
public static class MyJList<T> extends JList<T> {
private final JLabel emptyLabel = new JLabel("Selezionare un oggetto");
{
add(emptyLabel); // <---- add label
emptyLabel.setEnabled(false);
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if (getModel().getSize() == 0) {
final var preferredSize = emptyLabel.getPreferredSize();
final var listBounds = getBounds();
final var x = (listBounds.width - preferredSize.width) / 2;
final var y = (listBounds.height - preferredSize.height) / 2;
final var g2 = (Graphics2D) g.create(x, y, preferredSize.width, preferredSize.height);
try {
emptyLabel.setBounds(0, 0, preferredSize.width, preferredSize.height);
emptyLabel.paint(g2);
} finally {
g2.dispose();
}
}
}
}
文本布局在最好的时候是棘手的,越能避免它越好(恕我直言)。
就我个人而言,我避免使用自定义绘画路线,但这就是我,尝试一些更简单的方法。
通过使用 PropertyChangeListener
来监视 ListModel
何时更改,以及使用自定义 ListDataListener
来监视 ListModel
本身何时更改,您可以创建类似的结果。
这里的一个补充是我将文本包装在 <html>
标签中,这将触发“单词”包装。
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private DefaultListModel<String> model = new DefaultListModel<String>();
public TestPane() {
setLayout(new BorderLayout());
JPanel panel = new JPanel(new GridBagLayout());
JButton add = new JButton("Add");
JButton remove = new JButton("Remove");
panel.add(add);
panel.add(remove);
MyList<String> myList = new MyList<>();
myList.setModel(model);
add(new JScrollPane(myList));
add(panel, BorderLayout.SOUTH);
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
model.addElement("Hello");
}
});
remove.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
if (model.size() > 0) {
model.remove(0);
}
}
});
}
}
public class MyList<T> extends JList<T> {
final private ModelHandler modelHandler = new ModelHandler();
final private JLabel emptyLabel = new JLabel("<html>Selezionare un oggetto</html>");
public MyList() {
emptyLabel.setHorizontalAlignment(JLabel.CENTER);
setLayout(new BorderLayout());
add(emptyLabel);
emptyLabel.setVisible(false);
addPropertyChangeListener("model", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ((evt.getOldValue() instanceof ListModel)) {
ListModel model = (ListModel) evt.getOldValue();
model.removeListDataListener(modelHandler);
}
if ((evt.getNewValue() instanceof ListModel)) {
ListModel model = (ListModel) evt.getNewValue();
model.addListDataListener(modelHandler);
}
updateEmptyLabel();
}
});
}
protected void updateEmptyLabel() {
emptyLabel.setVisible(getModel().getSize() == 0);
}
protected class ModelHandler implements ListDataListener {
@Override
public void intervalAdded(ListDataEvent evt) {
updateEmptyLabel();
}
@Override
public void intervalRemoved(ListDataEvent evt) {
updateEmptyLabel();
}
@Override
public void contentsChanged(ListDataEvent evt) {
updateEmptyLabel();
}
}
}
}
我正在尝试在 Swing 中进行 2D 绘画,我想在空的 JList
中间画一个 JLabel
。
因此我想出了:
public class MyJList<T> extends JList<T> {
private final JLabel emptyLabel = new JLabel("Whatever");
@Override
public void paint(final Graphics g) {
super.paint(g);
if (getModel().getSize() == 0 && !emptyLabel.getText().isBlank()) {
final var preferredSize = emptyLabel.getPreferredSize();
final var x = (getWidth() - preferredSize.width) / 2;
final var y = (getHeight() - preferredSize.height) / 2;
final var g2 = (Graphics2D) g.create(x, y, preferredSize.width, preferredSize.height);
try {
emptyLabel.setBounds(0, 0, preferredSize.width, preferredSize.height);
emptyLabel.paint(g2);
} finally {
g2.dispose();
}
}
}
}
然而,当文本到达列表的边界时,它似乎被截断了:
如果我增加裁剪矩形的宽度,标签就会被完全绘制。
我究竟做错了什么?这样画画好吗?
完成最小示例:
class Main {
public static void main(final String[] args) {
final var myFrame = new MyFrame();
myFrame.setVisible(true);
}
public static class MyFrame extends JFrame {
public MyFrame() {
super("Example");
final var list = new MyJList<String>();
final var contentPane = getContentPane();
contentPane.setLayout(new BorderLayout());
contentPane.add(list, BorderLayout.CENTER);
pack();
setMinimumSize(new Dimension(160, 200));
setLocationRelativeTo(null);
}
}
public static class MyJList<T> extends JList<T> {
private final JLabel emptyLabel = new JLabel("Selezionare un oggetto");
{
emptyLabel.setEnabled(false);
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if (getModel().getSize() == 0) {
final var preferredSize = emptyLabel.getPreferredSize();
final var listBounds = getBounds();
final var x = (listBounds.width - preferredSize.width) / 2;
final var y = (listBounds.height - preferredSize.height) / 2;
final var g2 = (Graphics2D) g.create(x, y, preferredSize.width, preferredSize.height);
try {
emptyLabel.setBounds(0, 0, preferredSize.width, preferredSize.height);
emptyLabel.paint(g2);
} finally {
g2.dispose();
}
}
}
}
}
计算文本大小很困难,而且有很多陷阱。 JList
、JTable
等使用渲染器窗格而不是直接在单元格渲染器上调用 paint
是有原因的。
只有当组件是框架层次结构的一部分时,它才能正确确定文本的大小。那是因为只有这样组件才会对当前显示设备使用正确的 GraphicsEnvironment
。 GraphicsEnvironment
包含布局文本所需的信息,如抗锯齿支持、字距调整、小数字体支持等。
所以正确的方法是将标签添加到 JList
:
public static class MyJList<T> extends JList<T> {
private final JLabel emptyLabel = new JLabel("Selezionare un oggetto");
{
add(emptyLabel); // <---- add label
emptyLabel.setEnabled(false);
}
@Override
public void paint(final Graphics g) {
super.paint(g);
if (getModel().getSize() == 0) {
final var preferredSize = emptyLabel.getPreferredSize();
final var listBounds = getBounds();
final var x = (listBounds.width - preferredSize.width) / 2;
final var y = (listBounds.height - preferredSize.height) / 2;
final var g2 = (Graphics2D) g.create(x, y, preferredSize.width, preferredSize.height);
try {
emptyLabel.setBounds(0, 0, preferredSize.width, preferredSize.height);
emptyLabel.paint(g2);
} finally {
g2.dispose();
}
}
}
}
文本布局在最好的时候是棘手的,越能避免它越好(恕我直言)。
就我个人而言,我避免使用自定义绘画路线,但这就是我,尝试一些更简单的方法。
通过使用 PropertyChangeListener
来监视 ListModel
何时更改,以及使用自定义 ListDataListener
来监视 ListModel
本身何时更改,您可以创建类似的结果。
这里的一个补充是我将文本包装在 <html>
标签中,这将触发“单词”包装。
import java.awt.BorderLayout;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private DefaultListModel<String> model = new DefaultListModel<String>();
public TestPane() {
setLayout(new BorderLayout());
JPanel panel = new JPanel(new GridBagLayout());
JButton add = new JButton("Add");
JButton remove = new JButton("Remove");
panel.add(add);
panel.add(remove);
MyList<String> myList = new MyList<>();
myList.setModel(model);
add(new JScrollPane(myList));
add(panel, BorderLayout.SOUTH);
add.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
model.addElement("Hello");
}
});
remove.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent evt) {
if (model.size() > 0) {
model.remove(0);
}
}
});
}
}
public class MyList<T> extends JList<T> {
final private ModelHandler modelHandler = new ModelHandler();
final private JLabel emptyLabel = new JLabel("<html>Selezionare un oggetto</html>");
public MyList() {
emptyLabel.setHorizontalAlignment(JLabel.CENTER);
setLayout(new BorderLayout());
add(emptyLabel);
emptyLabel.setVisible(false);
addPropertyChangeListener("model", new PropertyChangeListener() {
@Override
public void propertyChange(PropertyChangeEvent evt) {
if ((evt.getOldValue() instanceof ListModel)) {
ListModel model = (ListModel) evt.getOldValue();
model.removeListDataListener(modelHandler);
}
if ((evt.getNewValue() instanceof ListModel)) {
ListModel model = (ListModel) evt.getNewValue();
model.addListDataListener(modelHandler);
}
updateEmptyLabel();
}
});
}
protected void updateEmptyLabel() {
emptyLabel.setVisible(getModel().getSize() == 0);
}
protected class ModelHandler implements ListDataListener {
@Override
public void intervalAdded(ListDataEvent evt) {
updateEmptyLabel();
}
@Override
public void intervalRemoved(ListDataEvent evt) {
updateEmptyLabel();
}
@Override
public void contentsChanged(ListDataEvent evt) {
updateEmptyLabel();
}
}
}
}