http://zetcode.com/tutorials/javaswingtutorial/swingmodels/

Swing 工程师创建了 Swing 工具箱,实现了修改后的 MVC 设计模式。 这样可以有效地处理数据,并在运行时使用可插入的外观。

传统的 MVC 模式将应用分为三个部分:模型,视图和控制器。 该模型表示应用中的数据。 视图是数据的视觉表示。 最后,控制器处理并响应事件(通常是用户操作),并可以调用模型上的更改。 这个想法是通过引入一个中间组件:控制器,将数据访问和业务逻辑与数据表示和用户交互分开。

Swing 工具箱使用修改后的 MVC 设计模式。 对于视图和控制器,它只有一个 UI 对象。 有时将这种修改后的 MVC 称为可分离模型架构。

在 Swing 工具箱中,每个组件都有其模型,甚至包括按钮之类的基本组件。 Swing 工具箱中有两种模型:

  • 状态模型
  • 数据模型

状态模型处理组件的状态。 例如,模型会跟踪组件是处于选中状态还是处于按下状态。 数据模型处理它们使用的数据。 列表组件保留其显示的项目列表。

对于 Swing 开发者来说,这意味着他们通常需要获取模型实例才能操纵组件中的数据。 但是也有例外。 为了方便起见,有一些方法可以返回数据,而无需程序员访问模型。

  1. public int getValue() {
  2. return getModel().getValue();
  3. }

一个示例是JSlider组件的getValue()方法。 开发者无需直接使用模型。 而是在后台进行对模型的访问。 在如此简单的情况下直接使用模型将是一个过大的杀伤力。 因此,Swing 提供了一些便捷的方法,如上一个。

要查询模型的状态,我们有两种通知:

  • 轻量级通知
  • 状态通知

轻量级通知使用ChangeListener类。 对于来自组件的所有通知,我们只有一个事件(ChangeEvent)。对于更复杂的组件,将使用状态通知。对于此类通知,我们具有不同类型的事件。例如,JList组件具有ListDataEventListSelectionEvent

如果我们不为组件设置模型,则会创建一个默认模型。 例如,按钮组件具有DefaultButtonModel模型。

  1. public JButton(String text, Icon icon) {
  2. // Create the model
  3. setModel(new DefaultButtonModel());
  4. // initialize
  5. init(text, icon);
  6. }

查看JButton.java源文件,我们发现默认模型是在构建组件时创建的。

按钮模型

该模型用于各种按钮,例如按钮,复选框,单选框和菜单项。 以下示例说明了JButton的模型。 因为没有数据可以与按钮关联,所以我们只能管理按钮的状态。

ButtonModelEx.java

  1. package com.zetcode;
  2. import javax.swing.AbstractAction;
  3. import javax.swing.DefaultButtonModel;
  4. import javax.swing.GroupLayout;
  5. import javax.swing.JButton;
  6. import javax.swing.JCheckBox;
  7. import javax.swing.JComponent;
  8. import javax.swing.JFrame;
  9. import javax.swing.JLabel;
  10. import javax.swing.event.ChangeEvent;
  11. import javax.swing.event.ChangeListener;
  12. import java.awt.EventQueue;
  13. import java.awt.event.ActionEvent;
  14. public class ButtonModelEx extends JFrame {
  15. private JButton okBtn;
  16. private JLabel enabledLbl;
  17. private JLabel pressedLbl;
  18. private JLabel armedLbl;
  19. private JCheckBox checkBox;
  20. public ButtonModelEx() {
  21. initUI();
  22. }
  23. private void initUI() {
  24. okBtn = new JButton("OK");
  25. okBtn.addChangeListener(new DisabledChangeListener());
  26. checkBox = new JCheckBox();
  27. checkBox.setAction(new CheckBoxAction());
  28. enabledLbl = new JLabel("Enabled: true");
  29. pressedLbl = new JLabel("Pressed: false");
  30. armedLbl = new JLabel("Armed: false");
  31. createLayout(okBtn, checkBox, enabledLbl, pressedLbl, armedLbl);
  32. setTitle("ButtonModel");
  33. setLocationRelativeTo(null);
  34. setDefaultCloseOperation(EXIT_ON_CLOSE);
  35. }
  36. private void createLayout(JComponent... arg) {
  37. var pane = getContentPane();
  38. var gl = new GroupLayout(pane);
  39. pane.setLayout(gl);
  40. gl.setAutoCreateContainerGaps(true);
  41. gl.setAutoCreateGaps(true);
  42. gl.setHorizontalGroup(gl.createParallelGroup()
  43. .addGroup(gl.createSequentialGroup()
  44. .addComponent(arg[0])
  45. .addGap(80)
  46. .addComponent(arg[1]))
  47. .addGroup(gl.createParallelGroup()
  48. .addComponent(arg[2])
  49. .addComponent(arg[3])
  50. .addComponent(arg[4]))
  51. );
  52. gl.setVerticalGroup(gl.createSequentialGroup()
  53. .addGroup(gl.createParallelGroup()
  54. .addComponent(arg[0])
  55. .addComponent(arg[1]))
  56. .addGap(40)
  57. .addGroup(gl.createSequentialGroup()
  58. .addComponent(arg[2])
  59. .addComponent(arg[3])
  60. .addComponent(arg[4]))
  61. );
  62. pack();
  63. }
  64. private class DisabledChangeListener implements ChangeListener {
  65. @Override
  66. public void stateChanged(ChangeEvent e) {
  67. var model = (DefaultButtonModel) okBtn.getModel();
  68. if (model.isEnabled()) {
  69. enabledLbl.setText("Enabled: true");
  70. } else {
  71. enabledLbl.setText("Enabled: false");
  72. }
  73. if (model.isArmed()) {
  74. armedLbl.setText("Armed: true");
  75. } else {
  76. armedLbl.setText("Armed: false");
  77. }
  78. if (model.isPressed()) {
  79. pressedLbl.setText("Pressed: true");
  80. } else {
  81. pressedLbl.setText("Pressed: false");
  82. }
  83. }
  84. }
  85. private class CheckBoxAction extends AbstractAction {
  86. public CheckBoxAction() {
  87. super("Disabled");
  88. }
  89. @Override
  90. public void actionPerformed(ActionEvent e) {
  91. if (okBtn.isEnabled()) {
  92. okBtn.setEnabled(false);
  93. } else {
  94. okBtn.setEnabled(true);
  95. }
  96. }
  97. }
  98. public static void main(String[] args) {
  99. EventQueue.invokeLater(() -> {
  100. var ex = new ButtonModelEx();
  101. ex.setVisible(true);
  102. });
  103. }
  104. }

在我们的示例中,我们有一个按钮,一个复选框和三个标签。 标签代表按钮的三个属性:按下,禁用或布防状态。

  1. okbtn.addChangeListener(new DisabledChangeListener());

我们使用ChangeListener来监听按钮状态的变化。

  1. var model = (DefaultButtonModel) okBtn.getModel();

在这里,我们获得默认的按钮模型。

  1. if (model.isEnabled()) {
  2. enabledLbl.setText("Enabled: true");
  3. } else {
  4. enabledLbl.setText("Enabled: false");
  5. }

我们查询模型是否启用了按钮。 标签会相应更新。

  1. if (okBtn.isEnabled()) {
  2. okBtn.setEnabled(false);
  3. } else {
  4. okBtn.setEnabled(true);
  5. }

该复选框启用或禁用该按钮。 要启用“确定”按钮,我们调用setEnabled()方法。 因此,我们更改了按钮的状态。 模型在哪里? 答案就在AbstractButton.java文件中。

  1. public void setEnabled(boolean b) {
  2. if (!b && model.isRollover()) {
  3. model.setRollover(false);
  4. }
  5. super.setEnabled(b);
  6. model.setEnabled(b);
  7. }

答案是,Swing 工具箱在内部与模型一起使用。 setEnabled()是程序员的另一种便捷方法。

Java Swing 模型架构 - 图1

图:ButtonModel

自定义ButtonModel

在前面的示例中,我们使用了默认按钮模型。 在下面的代码示例中,我们将使用我们自己的按钮模型。

CustomButtonModelEx.java

  1. package com.zetcode;
  2. import javax.swing.AbstractAction;
  3. import javax.swing.DefaultButtonModel;
  4. import javax.swing.GroupLayout;
  5. import javax.swing.JButton;
  6. import javax.swing.JCheckBox;
  7. import javax.swing.JComponent;
  8. import javax.swing.JFrame;
  9. import javax.swing.JLabel;
  10. import java.awt.EventQueue;
  11. import java.awt.event.ActionEvent;
  12. public class CustomButtonModelEx extends JFrame {
  13. private JButton okBtn;
  14. private JLabel enabledLbl;
  15. private JLabel pressedLbl;
  16. private JLabel armedLbl;
  17. private JCheckBox checkBox;
  18. public CustomButtonModelEx() {
  19. initUI();
  20. }
  21. private void initUI() {
  22. okBtn = new JButton("OK");
  23. checkBox = new JCheckBox();
  24. checkBox.setAction(new CheckBoxAction());
  25. enabledLbl = new JLabel("Enabled: true");
  26. pressedLbl = new JLabel("Pressed: false");
  27. armedLbl = new JLabel("Armed: false");
  28. var model = new OkButtonModel();
  29. okBtn.setModel(model);
  30. createLayout(okBtn, checkBox, enabledLbl, pressedLbl, armedLbl);
  31. setTitle("Custom button model");
  32. setLocationRelativeTo(null);
  33. setDefaultCloseOperation(EXIT_ON_CLOSE);
  34. }
  35. private void createLayout(JComponent... arg) {
  36. var pane = getContentPane();
  37. var gl = new GroupLayout(pane);
  38. pane.setLayout(gl);
  39. gl.setAutoCreateContainerGaps(true);
  40. gl.setAutoCreateGaps(true);
  41. gl.setHorizontalGroup(gl.createParallelGroup()
  42. .addGroup(gl.createSequentialGroup()
  43. .addComponent(arg[0])
  44. .addGap(80)
  45. .addComponent(arg[1]))
  46. .addGroup(gl.createParallelGroup()
  47. .addComponent(arg[2])
  48. .addComponent(arg[3])
  49. .addComponent(arg[4]))
  50. );
  51. gl.setVerticalGroup(gl.createSequentialGroup()
  52. .addGroup(gl.createParallelGroup()
  53. .addComponent(arg[0])
  54. .addComponent(arg[1]))
  55. .addGap(40)
  56. .addGroup(gl.createSequentialGroup()
  57. .addComponent(arg[2])
  58. .addComponent(arg[3])
  59. .addComponent(arg[4]))
  60. );
  61. pack();
  62. }
  63. private class OkButtonModel extends DefaultButtonModel {
  64. @Override
  65. public void setEnabled(boolean b) {
  66. if (b) {
  67. enabledLbl.setText("Enabled: true");
  68. } else {
  69. enabledLbl.setText("Enabled: false");
  70. }
  71. super.setEnabled(b);
  72. }
  73. @Override
  74. public void setArmed(boolean b) {
  75. if (b) {
  76. armedLbl.setText("Armed: true");
  77. } else {
  78. armedLbl.setText("Armed: false");
  79. }
  80. super.setArmed(b);
  81. }
  82. @Override
  83. public void setPressed(boolean b) {
  84. if (b) {
  85. pressedLbl.setText("Pressed: true");
  86. } else {
  87. pressedLbl.setText("Pressed: false");
  88. }
  89. super.setPressed(b);
  90. }
  91. }
  92. private class CheckBoxAction extends AbstractAction {
  93. public CheckBoxAction() {
  94. super("Disabled");
  95. }
  96. @Override
  97. public void actionPerformed(ActionEvent e) {
  98. if (okBtn.isEnabled()) {
  99. okBtn.setEnabled(false);
  100. } else {
  101. okBtn.setEnabled(true);
  102. }
  103. }
  104. }
  105. public static void main(String[] args) {
  106. EventQueue.invokeLater(() -> {
  107. var ex = new CustomButtonModelEx();
  108. ex.setVisible(true);
  109. });
  110. }
  111. }

本示例与上一个示例具有相同的作用。 区别在于我们不使用变更监听器,而使用自定义按钮模型。

  1. var model = new OkButtonModel();
  2. okBtn.setModel(model);

我们为按钮设置自定义模型。

  1. private class OkButtonModel extends DefaultButtonModel {
  2. ...
  3. }

我们创建一个自定义按钮模型并覆盖必要的方法。

  1. @Override
  2. public void setEnabled(boolean b) {
  3. if (b) {
  4. enabledLbl.setText("Enabled: true");
  5. } else {
  6. enabledLbl.setText("Enabled: false");
  7. }
  8. super.setEnabled(b);
  9. }

我们重写setEnabled()方法,并在其中添加一些功能。 我们一定不要忘记调用父方法来继续进行处理。

JList模型

几个组件具有两个模型。 JList是其中之一。 它具有以下模型:ListModelListSelectionModelListModel处理数据,ListSelectionModel处理列表的选择状态。 以下示例使用两种模型。

ListModelsEx.java

  1. package com.zetcode;
  2. import javax.swing.DefaultListModel;
  3. import javax.swing.GroupLayout;
  4. import javax.swing.JButton;
  5. import javax.swing.JComponent;
  6. import javax.swing.JFrame;
  7. import javax.swing.JList;
  8. import javax.swing.JOptionPane;
  9. import javax.swing.JScrollPane;
  10. import javax.swing.ListSelectionModel;
  11. import java.awt.EventQueue;
  12. import java.awt.event.MouseAdapter;
  13. import java.awt.event.MouseEvent;
  14. import static javax.swing.GroupLayout.Alignment.CENTER;
  15. public class ListModelsEx extends JFrame {
  16. private DefaultListModel<String> model;
  17. private JList<String> myList;
  18. private JButton remAllBtn;
  19. private JButton addBtn;
  20. private JButton renBtn;
  21. private JButton delBtn;
  22. public ListModelsEx() {
  23. initUI();
  24. }
  25. private void createList() {
  26. model = new DefaultListModel<>();
  27. model.addElement("Amelie");
  28. model.addElement("Aguirre, der Zorn Gottes");
  29. model.addElement("Fargo");
  30. model.addElement("Exorcist");
  31. model.addElement("Schindler's myList");
  32. myList = new JList<>(model);
  33. myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  34. myList.addMouseListener(new MouseAdapter() {
  35. @Override
  36. public void mouseClicked(MouseEvent e) {
  37. if (e.getClickCount() == 2) {
  38. int index = myList.locationToIndex(e.getPoint());
  39. var item = model.getElementAt(index);
  40. var text = JOptionPane.showInputDialog("Rename item", item);
  41. String newItem;
  42. if (text != null) {
  43. newItem = text.trim();
  44. } else {
  45. return;
  46. }
  47. if (!newItem.isEmpty()) {
  48. model.remove(index);
  49. model.add(index, newItem);
  50. var selModel = myList.getSelectionModel();
  51. selModel.setLeadSelectionIndex(index);
  52. }
  53. }
  54. }
  55. });
  56. }
  57. private void createButtons() {
  58. remAllBtn = new JButton("Remove All");
  59. addBtn = new JButton("Add");
  60. renBtn = new JButton("Rename");
  61. delBtn = new JButton("Delete");
  62. addBtn.addActionListener(e -> {
  63. var text = JOptionPane.showInputDialog("Add a new item");
  64. String item;
  65. if (text != null) {
  66. item = text.trim();
  67. } else {
  68. return;
  69. }
  70. if (!item.isEmpty()) {
  71. model.addElement(item);
  72. }
  73. });
  74. delBtn.addActionListener(event -> {
  75. var selModel = myList.getSelectionModel();
  76. int index = selModel.getMinSelectionIndex();
  77. if (index >= 0) {
  78. model.remove(index);
  79. }
  80. });
  81. renBtn.addActionListener(e -> {
  82. var selModel = myList.getSelectionModel();
  83. int index = selModel.getMinSelectionIndex();
  84. if (index == -1) {
  85. return;
  86. }
  87. var item = model.getElementAt(index);
  88. var text = JOptionPane.showInputDialog("Rename item", item);
  89. String newItem;
  90. if (text != null) {
  91. newItem = text.trim();
  92. } else {
  93. return;
  94. }
  95. if (!newItem.isEmpty()) {
  96. model.remove(index);
  97. model.add(index, newItem);
  98. }
  99. });
  100. remAllBtn.addActionListener(e -> model.clear());
  101. }
  102. private void initUI() {
  103. createList();
  104. createButtons();
  105. var scrollPane = new JScrollPane(myList);
  106. createLayout(scrollPane, addBtn, renBtn, delBtn, remAllBtn);
  107. setTitle("JList models");
  108. setLocationRelativeTo(null);
  109. setDefaultCloseOperation(EXIT_ON_CLOSE);
  110. }
  111. private void createLayout(JComponent... arg) {
  112. var pane = getContentPane();
  113. var gl = new GroupLayout(pane);
  114. pane.setLayout(gl);
  115. gl.setAutoCreateContainerGaps(true);
  116. gl.setAutoCreateGaps(true);
  117. gl.setHorizontalGroup(gl.createSequentialGroup()
  118. .addComponent(arg[0])
  119. .addGroup(gl.createParallelGroup()
  120. .addComponent(arg[1])
  121. .addComponent(arg[2])
  122. .addComponent(arg[3])
  123. .addComponent(arg[4]))
  124. );
  125. gl.setVerticalGroup(gl.createParallelGroup(CENTER)
  126. .addComponent(arg[0])
  127. .addGroup(gl.createSequentialGroup()
  128. .addComponent(arg[1])
  129. .addComponent(arg[2])
  130. .addComponent(arg[3])
  131. .addComponent(arg[4]))
  132. );
  133. gl.linkSize(addBtn, renBtn, delBtn, remAllBtn);
  134. pack();
  135. }
  136. public static void main(String[] args) {
  137. EventQueue.invokeLater(() -> {
  138. var ex = new ListModelsEx();
  139. ex.setVisible(true);
  140. });
  141. }
  142. }

该示例显示了一个列表组件和四个按钮。 这些按钮控制列表组件中的数据。 该示例更大,因为我们在那里进行了一些其他检查。 例如,我们不允许在列表组件中输入空格。

  1. model = new DefaultListModel<>();
  2. model.addElement("Amelie");
  3. model.addElement("Aguirre, der Zorn Gottes");
  4. model.addElement("Fargo");
  5. ...

我们创建一个默认列表模型,并向其中添加元素。

  1. myList = new JList<>(model);
  2. myList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

我们创建一个列表组件。 构造器的参数是我们创建的模型。 我们使列表进入单选模式。

  1. if (text != null) {
  2. item = text.trim();
  3. } else {
  4. return;
  5. }
  6. if (!item.isEmpty()) {
  7. model.addElement(item);
  8. }

我们仅添加不等于null且不为空的项目,例如包含至少一个非空格字符的项目。 在列表中添加空格或空值没有意义。

  1. var selModel = myList.getSelectionModel();
  2. int index = selModel.getMinSelectionIndex();
  3. if (index >= 0) {
  4. model.remove(index);
  5. }

这是我们按下删除按钮时运行的代码。 为了从列表中删除一个项目,必须选择它-我们必须找出当前选择的项目。 为此,我们调用getSelectionModel()方法。 我们使用getMinSelectionIndex()获取选定的索引,并使用remove()方法删除该项目。

在此示例中,我们使用了两种列表模型。 我们调用列表数据模型的add()remove()clear()方法来处理我们的数据。 并且我们使用了一个列表选择模型,以便找出所选项目。

Java Swing 模型架构 - 图2

图:列表模型

文件模型

文档模型是从视觉表示中分离数据的一个很好的例子。 在JTextPane组件中,我们有一个StyledDocument用于设置文本数据的样式。

DocumentModelEx.java

  1. package com.zetcode;
  2. import javax.swing.BorderFactory;
  3. import javax.swing.ImageIcon;
  4. import javax.swing.JButton;
  5. import javax.swing.JFrame;
  6. import javax.swing.JPanel;
  7. import javax.swing.JScrollPane;
  8. import javax.swing.JTextPane;
  9. import javax.swing.JToolBar;
  10. import javax.swing.text.StyleConstants;
  11. import javax.swing.text.StyledDocument;
  12. import java.awt.BorderLayout;
  13. import java.awt.EventQueue;
  14. public class DocumentModelEx extends JFrame {
  15. private StyledDocument sdoc;
  16. private JTextPane textPane;
  17. public DocumentModelEx() {
  18. initUI();
  19. }
  20. private void initUI() {
  21. createToolbar();
  22. var panel = new JPanel(new BorderLayout());
  23. panel.setBorder(BorderFactory.createEmptyBorder(8, 8, 8, 8));
  24. textPane = new JTextPane();
  25. sdoc = textPane.getStyledDocument();
  26. initStyles(textPane);
  27. panel.add(new JScrollPane(textPane));
  28. add(panel);
  29. pack();
  30. setTitle("Document Model");
  31. setLocationRelativeTo(null);
  32. setDefaultCloseOperation(EXIT_ON_CLOSE);
  33. }
  34. private void createToolbar() {
  35. var toolbar = new JToolBar();
  36. var bold = new ImageIcon("src/main/resources/bold.png");
  37. var italic = new ImageIcon("src/main/resources/italic.png");
  38. var strike = new ImageIcon("src/main/resources/strike.png");
  39. var underline = new ImageIcon("src/main/resources/underline.png");
  40. var boldBtn = new JButton(bold);
  41. var italBtn = new JButton(italic);
  42. var striBtn = new JButton(strike);
  43. var undeBtn = new JButton(underline);
  44. toolbar.add(boldBtn);
  45. toolbar.add(italBtn);
  46. toolbar.add(striBtn);
  47. toolbar.add(undeBtn);
  48. add(toolbar, BorderLayout.NORTH);
  49. boldBtn.addActionListener(e -> sdoc.setCharacterAttributes(
  50. textPane.getSelectionStart(),
  51. textPane.getSelectionEnd() - textPane.getSelectionStart(),
  52. textPane.getStyle("Bold"), false));
  53. italBtn.addActionListener(e -> sdoc.setCharacterAttributes(
  54. textPane.getSelectionStart(),
  55. textPane.getSelectionEnd() - textPane.getSelectionStart(),
  56. textPane.getStyle("Italic"), false));
  57. striBtn.addActionListener(e -> sdoc.setCharacterAttributes(
  58. textPane.getSelectionStart(),
  59. textPane.getSelectionEnd() - textPane.getSelectionStart(),
  60. textPane.getStyle("Strike"), false));
  61. undeBtn.addActionListener(e -> sdoc.setCharacterAttributes(
  62. textPane.getSelectionStart(),
  63. textPane.getSelectionEnd() - textPane.getSelectionStart(),
  64. textPane.getStyle("Underline"), false));
  65. }
  66. private void initStyles(JTextPane textPane) {
  67. var style = textPane.addStyle("Bold", null);
  68. StyleConstants.setBold(style, true);
  69. style = textPane.addStyle("Italic", null);
  70. StyleConstants.setItalic(style, true);
  71. style = textPane.addStyle("Underline", null);
  72. StyleConstants.setUnderline(style, true);
  73. style = textPane.addStyle("Strike", null);
  74. StyleConstants.setStrikeThrough(style, true);
  75. }
  76. public static void main(String[] args) {
  77. EventQueue.invokeLater(() -> {
  78. var ex = new DocumentModelEx();
  79. ex.setVisible(true);
  80. });
  81. }
  82. }

该示例具有一个文本窗格和一个工具栏。 在工具栏中,我们有四个按钮可以更改文本的属性。

  1. sdoc = textpane.getStyledDocument();

在这里,我们获得样式化的文档,该文档是文本窗格组件的模型。

  1. var style = textpane.addStyle("Bold", null);
  2. StyleConstants.setBold(style, true);

样式是一组文本属性,例如颜色和大小。 在这里,我们为文本窗格组件注册了一个粗体样式。 可以随时检索已注册的样式。

  1. doc.setCharacterAttributes(textpane.getSelectionStart(),
  2. textpane.getSelectionEnd() - textpane.getSelectionStart(),
  3. textpane.getStyle("Bold"), false);

在这里,我们更改文本的属性。 参数是选择的偏移量和长度,样式和布尔值替换。 偏移量是我们应用粗体文本的开头。 我们通过减去选择结束值和选择开始值来获得长度值。 布尔值false表示我们不会用新样式替换旧样式,而是将它们合并。 这意味着如果文本带有下划线,并且我们将其设为粗体,则结果为带下划线的粗体文本。

Java Swing 模型架构 - 图3

图:文档模型

在本章中,我们提到了 Swing 模型。