【Java基础】swing-图形界面学习(下)

「爱情、让人受尽委屈。」 2022-12-23 06:51 286阅读 0赞

就是个人学习的笔记,按照下面的Demo一个一个复制粘贴跑起来大概就会使用Swing了,建议先从上半部分开始看

上半部分

【Java基础】swing-图形界面学习(上)

八.菜单

GUI的菜单分为 菜单栏JMenuBar菜单JMenu菜单项JMenuItem

8.1.菜单栏和菜单

在这里插入图片描述

  1. @Test
  2. public void MenuAndJMenuBar() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. // 菜单栏
  7. JMenuBar mb = new JMenuBar();
  8. // 菜单
  9. JMenu mHero = new JMenu("英雄");
  10. JMenu mItem = new JMenu("道具");
  11. JMenu mWord = new JMenu("符文");
  12. JMenu mSummon = new JMenu("召唤师");
  13. JMenu mTalent = new JMenu("天赋树");
  14. // 把菜单加入到菜单栏
  15. mb.add(mHero);
  16. mb.add(mItem);
  17. mb.add(mWord);
  18. mb.add(mSummon);
  19. mb.add(mTalent);
  20. // 把菜单栏加入到frame,这里用的是set而非add
  21. frame.setJMenuBar(mb);
  22. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  23. frame.setVisible(true);
  24. //设置休眠时间,是为了延缓主线程销毁
  25. TimeUnit.SECONDS.sleep(1000);
  26. }

8.2.菜单项

在这里插入图片描述

  1. @Test
  2. public void MenuItem() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 400);
  5. frame.setLocation(200, 200);
  6. JMenuBar mb = new JMenuBar();
  7. JMenu mHero = new JMenu("英雄");
  8. JMenu mItem = new JMenu("道具");
  9. JMenu mWord = new JMenu("符文");
  10. JMenu mSummon = new JMenu("召唤师");
  11. JMenu mTalent = new JMenu("天赋树");
  12. // 菜单项
  13. mHero.add(new JMenuItem("近战-Warriar"));
  14. mHero.add(new JMenuItem("远程-Range"));
  15. mHero.add(new JMenuItem("物理-physical"));
  16. mHero.add(new JMenuItem("坦克-Tank"));
  17. mHero.add(new JMenuItem("法系-Mage"));
  18. mHero.add(new JMenuItem("辅助-Support"));
  19. mHero.add(new JMenuItem("打野-Jungle"));
  20. mHero.add(new JMenuItem("突进-Charge"));
  21. mHero.add(new JMenuItem("男性-Boy"));
  22. mHero.add(new JMenuItem("女性-Girl"));
  23. // 分隔符
  24. mHero.addSeparator();
  25. mHero.add(new JMenuItem("所有-All"));
  26. mb.add(mHero);
  27. mb.add(mItem);
  28. mb.add(mWord);
  29. mb.add(mSummon);
  30. mb.add(mTalent);
  31. frame.setJMenuBar(mb);
  32. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  33. frame.setVisible(true);
  34. //设置休眠时间,是为了延缓主线程销毁
  35. TimeUnit.SECONDS.sleep(1000);
  36. }

8.3.练习-完成一个完整的记事本界面

在这里插入图片描述

  1. @Test
  2. public void NotePad() throws InterruptedException {
  3. //创建容器
  4. JFrame frame = new JFrame("记事本");
  5. //
  6. frame.setSize(400, 400);
  7. frame.setLocation(200, 200);
  8. //创建菜单栏
  9. JMenuBar menuBar = new JMenuBar();
  10. //创建菜单
  11. JMenu document = new JMenu("文件(A)");
  12. JMenu editor = new JMenu("编辑(E)");
  13. JMenu layout = new JMenu("格式(O)");
  14. JMenu viewing = new JMenu("查看(V)");
  15. JMenu help = new JMenu("帮助(H)");
  16. // 文件菜单添加菜单项
  17. document.add(new JMenuItem("新建(N) Ctrl+N"));
  18. document.add(new JMenuItem("打开(O) Ctrl+O"));
  19. document.add(new JMenuItem("保存(S) Ctrl+S"));
  20. document.add(new JMenuItem("另存为(N)"));
  21. document.addSeparator();//分割线
  22. document.add(new JMenuItem("页面设置(U)"));
  23. document.add(new JMenuItem("打印(P) Ctrl+P"));
  24. document.addSeparator();//分割线
  25. document.add(new JMenuItem("退出(X)"));
  26. // 编辑项菜单添加菜单项
  27. editor.add(new JMenuItem("撤销(U) Ctrl+Z"));
  28. editor.addSeparator();//分割线
  29. editor.add(new JMenuItem("剪切(T) Ctrl+X"));
  30. editor.add(new JMenuItem("复制(C) Ctrl+C"));
  31. editor.add(new JMenuItem("粘贴(P) Ctrl+V"));
  32. editor.add(new JMenuItem("删除(L) Del"));
  33. editor.addSeparator();//分割线
  34. editor.add(new JMenuItem("查找(frame) Ctrl+frame"));
  35. editor.add(new JMenuItem("查找下一个(N) F3"));
  36. editor.add(new JMenuItem("替换(R) Ctrl+H"));
  37. editor.add(new JMenuItem("转到(G) Ctrl+G"));
  38. editor.addSeparator();//分割线
  39. editor.add(new JMenuItem("全选(A) Ctrl+A"));
  40. editor.add(new JMenuItem("时间/日期(D) F5"));
  41. //格式菜单添加菜单项
  42. layout.add(new JMenuItem("自动换行(W) "));
  43. layout.add(new JMenuItem("字体(frame)"));
  44. //查看菜单添加菜单项
  45. viewing.add(new JMenuItem("状态栏(S)"));
  46. //帮助菜单添加菜单项
  47. help.add(new JMenuItem("查看帮助(H)"));
  48. help.add(new JMenuItem("关于记事本(A)"));
  49. //菜单加入菜单栏
  50. menuBar.add(document);
  51. menuBar.add(editor);
  52. menuBar.add(layout);
  53. menuBar.add(viewing);
  54. menuBar.add(help);
  55. //多行文本框
  56. JTextArea textArea = new JTextArea();
  57. textArea.setLineWrap(true);
  58. //加入滚动条面版
  59. JScrollPane scrollPane = new JScrollPane(textArea);
  60. //滚动面板加入容器
  61. frame.setContentPane(scrollPane);
  62. //容器加入菜单栏
  63. frame.setJMenuBar(menuBar);
  64. //设置默认关闭操作
  65. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  66. //设置容器可显示
  67. frame.setVisible(true);
  68. //设置休眠时间,是为了延缓主线程销毁
  69. TimeUnit.SECONDS.sleep(1000);
  70. }

九.工具栏

工具栏用于存放常用的按钮

btn.setToolTipText() 可以设置按钮提示信息
toolBar.setFloatable(false);禁止工具栏拖动:true可以拖动 false不可以拖动
在这里插入图片描述

  1. @Test
  2. public void JToolBarTest() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. // 添加菜单
  7. addMenu(frame);
  8. // 工具栏
  9. JToolBar toolBar = new JToolBar();
  10. // 为工具栏增加按钮
  11. JButton b1 = new JButton(new ImageIcon("e:/data/1.jpg"));
  12. //设置按钮提示文字
  13. b1.setToolTipText("坑爹英雄");
  14. JButton b2 = new JButton(new ImageIcon("e:/data/2.jpg"));
  15. JButton b3 = new JButton(new ImageIcon("e:/data/3.jpg"));
  16. JButton b4 = new JButton(new ImageIcon("e:/data/4.jpg"));
  17. JButton b5 = new JButton(new ImageIcon("e:/data/5.jpg"));
  18. JButton b6 = new JButton(new ImageIcon("e:/data/6.jpg"));
  19. toolBar.add(b1);
  20. toolBar.add(b2);
  21. toolBar.add(b3);
  22. toolBar.add(b4);
  23. toolBar.add(b5);
  24. toolBar.add(b6);
  25. // 禁止工具栏拖动
  26. toolBar.setFloatable(false);
  27. // 把工具栏放在north的位置
  28. frame.setLayout(new BorderLayout());
  29. frame.add(toolBar, BorderLayout.NORTH);
  30. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  31. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  32. frame.setVisible(true);
  33. //设置休眠时间,是为了延缓主线程销毁
  34. TimeUnit.SECONDS.sleep(1000);
  35. }

十.日期控件

10.1.DatePicker

  1. <dependency>
  2. <groupId>org.jdatepicker</groupId>
  3. <artifactId>jdatepicker</artifactId>
  4. <version>1.3.4</version>
  5. </dependency>

本例使用datepicker.jar 包,有一个缺点,不能设置时间,只能在创建控件的时候传入指定日期。
需要设置日期,请使用JXDatePicker
在这里插入图片描述

  1. import com.eltima.components.ui.DatePicker;
  2. public class TestGUI {
  3. public static void main(String[] args) {
  4. JFrame f = new JFrame("LoL");
  5. f.setSize(400, 300);
  6. f.setLocation(200, 200);
  7. f.setLayout(null);
  8. final DatePicker datepick;
  9. datepick = getDatePicker();
  10. f.add(datepick);
  11. JButton b = new JButton("获取时间");
  12. b.setBounds(137, 183, 100, 30);
  13. f.add(b);
  14. b.addActionListener(new ActionListener() {
  15. @Override
  16. public void actionPerformed(ActionEvent e) {
  17. JOptionPane.showMessageDialog(f, "获取控件中的日期:" + datepick.getValue());
  18. System.out.println(datepick.getValue());
  19. }
  20. });
  21. f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  22. f.setVisible(true);
  23. }
  24. private static DatePicker getDatePicker() {
  25. final DatePicker datepick;
  26. // 格式
  27. String DefaultFormat = "yyyy-MM-dd HH:mm:ss";
  28. // 当前时间
  29. Date date = new Date();
  30. // 字体
  31. Font font = new Font("Times New Roman", Font.BOLD, 14);
  32. Dimension dimension = new Dimension(177, 24);
  33. int[] hilightDays = { 1, 3, 5, 7 };
  34. int[] disabledDays = { 4, 6, 5, 9 };
  35. datepick = new DatePicker(date, DefaultFormat, font, dimension);
  36. datepick.setLocation(137, 83);
  37. datepick.setBounds(137, 83, 177, 24);
  38. // 设置一个月份中需要高亮显示的日子
  39. datepick.setHightlightdays(hilightDays, Color.red);
  40. // 设置一个月份中不需要的日子,呈灰色显示
  41. datepick.setDisableddays(disabledDays);
  42. // 设置国家
  43. datepick.setLocale(Locale.CHINA);
  44. // 设置时钟面板可见
  45. datepick.setTimePanleVisible(true);
  46. return datepick;
  47. }
  48. }

10.2.JXDatePicker

本例使用 包swingx-core-1.6.5-1.jar,界面比较简约,可以设置日期

  1. <dependency>
  2. <groupId>org.swinglabs.swingx</groupId>
  3. <artifactId>swingx-core</artifactId>
  4. <version>1.6.5-1</version>
  5. </dependency>

10.3.时间控件练习:统计大于指定更新时间文件

借助时间控件,选中一个时间,然后根据这个时间,统计e:\project 修改时间大于这个时间的文件,一共有多少

  1. import com.eltima.components.ui.DatePicker;
  2. public class TestGUI {
  3. public static void main(String[] args) {
  4. JFrame f = new JFrame("LoL");
  5. f.setSize(500, 300);
  6. f.setLocation(200, 200);
  7. f.setLayout(null);
  8. final DatePicker datepick;
  9. datepick = getDatePicker();
  10. f.add(datepick);
  11. JButton b = new JButton("统计在D:/eclipse3.1 目录下,修改时间大于控件日期的文件总数");
  12. b.setBounds(20, 183, 450, 30);
  13. f.add(b);
  14. b.addActionListener(new ActionListener() {
  15. @Override
  16. public void actionPerformed(ActionEvent e) {
  17. File folder = new File("D:\\eclipse3.1");
  18. Date d = (Date) datepick.getValue();
  19. JOptionPane.showMessageDialog(f, "文件总数是:" + countFilesModifiedDateGreaterThan(folder, d));
  20. }
  21. });
  22. f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  23. f.setVisible(true);
  24. }
  25. private static DatePicker getDatePicker() {
  26. final DatePicker datepick;
  27. // 格式
  28. String DefaultFormat = "yyyy-MM-dd HH:mm:ss";
  29. // 当前时间
  30. Date date = new Date();
  31. // 字体
  32. Font font = new Font("Times New Roman", Font.BOLD, 14);
  33. Dimension dimension = new Dimension(177, 24);
  34. int[] hilightDays = { 1, 3, 5, 7 };
  35. int[] disabledDays = { 4, 6, 5, 9 };
  36. datepick = new DatePicker(date, DefaultFormat, font, dimension);
  37. // datepick.setLocation(137, 83);
  38. datepick.setBounds(137, 83, 177, 24);
  39. // 设置一个月份中需要高亮显示的日子
  40. datepick.setHightlightdays(hilightDays, Color.red);
  41. // 设置一个月份中不需要的日子,呈灰色显示
  42. datepick.setDisableddays(disabledDays);
  43. // 设置国家
  44. datepick.setLocale(Locale.CHINA);
  45. // 设置时钟面板可见
  46. datepick.setTimePanleVisible(true);
  47. return datepick;
  48. }
  49. //递归获取文件
  50. public static List<File> scanFolder(File folder){
  51. List<File> files = new ArrayList<>();
  52. File[] fs = folder.listFiles();
  53. for (File f : fs) {
  54. if(f.isDirectory()){
  55. List<File> subFolderFiles = scanFolder(f);
  56. files.addAll(subFolderFiles);
  57. }
  58. if(f.isFile()){
  59. files.add(f);
  60. }
  61. }
  62. return files;
  63. }
  64. //通过stream过滤文件修改时间大于指定时间的个数
  65. public static long countFilesModifiedDateGreaterThan(File folder,Date d){
  66. List<File> fs = scanFolder(folder);
  67. return fs.stream()
  68. .filter(f->f.lastModified()>d.getTime())
  69. .count();
  70. }
  71. }

十一.表格

11.1.基本表格

显示一个Table需要两组数据

  • 一维数组: String[] columnName: 表示表格的标题
  • 二维数组: String[][] heros : 表格中的内容
  • 默认情况下,表格的标题不会显示,除非使用了JScrollPane
    在这里插入图片描述
  1. @Test
  2. public void JTable() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. frame.setLayout(new BorderLayout());
  7. // 表格上的title
  8. String[] columnNames = new String[]{ "id", "name", "hp", "damage"};
  9. // 表格中的内容,是一个二维数组
  10. String[][] heros = new String[][]{ { "1", "盖伦", "616", "100"}, { "2", "提莫", "512", "102"}, { "3", "奎因", "832", "200"}};
  11. JTable table = new JTable(heros, columnNames);
  12. frame.add(table, BorderLayout.CENTER);
  13. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  14. frame.setVisible(true);
  15. //设置休眠时间,是为了延缓主线程销毁
  16. TimeUnit.SECONDS.sleep(1000);
  17. }

11.2.使用JScrollPane

JScrollPane: 带滚动条的Panel

  • 把table放进去就可以看到table的title

同样的把textarea放进去,并且textarea内容够长的话,就会看到滚动条
在这里插入图片描述

  1. @Test
  2. public void JTableShowTitle() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. frame.setLayout(new BorderLayout());
  7. // 表格上的title
  8. String[] columnNames = new String[]{ "id", "name", "hp", "damage"};
  9. // 表格中的内容,是一个二维数组
  10. String[][] heros = new String[][]{ { "1", "盖伦", "616", "100"}, { "2", "提莫", "512", "102"}, { "3", "奎因", "832", "200"}};
  11. JTable table = new JTable(heros, columnNames);
  12. // 根据t创建 JScrollPane
  13. JScrollPane scrollPane = new JScrollPane(table);
  14. //或则创建一个空的JScrollPane,再通过setViewportView把table放在JScrollPane中
  15. // JScrollPane sp = new JScrollPane(table);
  16. // sp.setViewportView(table);
  17. // 把sp而非JTable加入到JFrame上,
  18. frame.add(scrollPane, BorderLayout.CENTER);
  19. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  20. frame.setVisible(true);
  21. TimeUnit.SECONDS.sleep(1000);
  22. }

11.3.设置列宽

  1. // 设置列宽度
  2. table.getColumnModel().getColumn(0).setPreferredWidth(10);

11.4.TableModel

TableMode可以将表格的数据和显示分离

  • 比如对于JTable而言,有数据部分,也有显示部分(比如列宽等信息)。 数据部分可以声明一个叫做TableModel存放要显示的数据。

使用TableModel的方式存放Table需要显示的数据

需要继承AbstractTableModel,进而实现了接口TableModel,在实现类 中提供一个table显示需要的所有信息

  1. getRowCount 返回一共有多少行
  2. getColumnCount 返回一共有多少列
  3. getColumnName 每一列的名字
  4. isCellEditable 单元格是否可以修改
    5.getValueAt 每一个单元格里的值

当图形界面需要渲染第一个单元格的数据的时候,就会调用方法TabelModelgetValueAt(0,0),把返回值拿到并显示

在这里插入图片描述

  1. public class HeroTableModel extends AbstractTableModel {
  2. private static final long serialVersionUID = -4796525201897896613L;
  3. String[] columnNames = new String[]{ "id", "name", "hp", "damage"};
  4. String[][] heros = new String[][]{ { "1", "盖伦", "616", "100"}, { "2", "提莫", "512", "102"}, { "3", "奎因", "832", "200"}};
  5. // 返回一共有多少行
  6. @Override
  7. public int getRowCount() {
  8. return heros.length;
  9. }
  10. // 返回一共有多少列
  11. @Override
  12. public int getColumnCount() {
  13. return columnNames.length;
  14. }
  15. // 获取每一列的名称
  16. @Override
  17. public String getColumnName(int columnIndex) {
  18. return columnNames[columnIndex];
  19. }
  20. // 单元格是否可以修改
  21. @Override
  22. public boolean isCellEditable(int rowIndex, int columnIndex) {
  23. return false;
  24. }
  25. // 每一个单元格里的值
  26. @Override
  27. public Object getValueAt(int rowIndex, int columnIndex) {
  28. return heros[rowIndex][columnIndex];
  29. }
  30. /** * 自定义TableModel * @throws InterruptedException */
  31. @Test
  32. public void customTableModel() throws InterruptedException {
  33. JFrame frame = new JFrame("LoL");
  34. frame.setSize(400, 300);
  35. frame.setLocation(200, 200);
  36. frame.setLayout(new BorderLayout());
  37. //创建一个TableModel
  38. HeroTableModel htm = new HeroTableModel();
  39. //根据 TableModel来创建 Table
  40. JTable t = new JTable(htm);
  41. JScrollPane sp = new JScrollPane(t);
  42. frame.add(sp, BorderLayout.CENTER);
  43. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  44. frame.setVisible(true);
  45. TimeUnit.SECONDS.sleep(1000);
  46. }
  47. }

也可以直接通过JTable的构造方法传入标题以及数据创建表格

  1. JTable(Object[][] rowData, Object[] columnNames)

11.5.TableModel 与 数据库结合(使用List模拟数据库操作)

只需要修改HeroTableModel,引入Dao层,无需修改展示。 这正好演绎了Model设计思想中的数据分离的好处,当只需要数据发生变化的时候,修改Model即可,界面GUI部分,不需要做任何改动

由于时间关系 下面的代码都是使用内存来模拟数据库的增删改查, 实际上操作数据库也只是把 HeroDAO代码改为操作数据库代码而已

  1. public class HeroTableModelDB extends AbstractTableModel {
  2. private static final long serialVersionUID = -4796525201897896613L;
  3. // 使用从DAO返回的List作为TableModel的数据
  4. public List<Hero> heros = new HeroDAO().list(0,10);
  5. public String[] columnNames = new String[]{ "id", "name", "hp", "damage"};
  6. // heros.size返回一共有多少行
  7. @Override
  8. public int getRowCount() {
  9. return heros.size();
  10. }
  11. @Override
  12. public int getColumnCount() {
  13. return columnNames.length;
  14. }
  15. @Override
  16. public String getColumnName(int columnIndex) {
  17. return columnNames[columnIndex];
  18. }
  19. @Override
  20. public boolean isCellEditable(int rowIndex, int columnIndex) {
  21. return false;
  22. }
  23. // 先通过heros.get(rowIndex)获取行对应的Hero对象
  24. // 然后根据columnIndex返回对应的属性
  25. @Override
  26. public Object getValueAt(int rowIndex, int columnIndex) {
  27. Hero h = heros.get(rowIndex);
  28. if (0 == columnIndex) {
  29. return h.id;
  30. }
  31. if (1 == columnIndex) {
  32. return h.name;
  33. }
  34. if (2 == columnIndex) {
  35. return h.hp;
  36. }
  37. if (3 == columnIndex) {
  38. return h.damage;
  39. }
  40. return null;
  41. }
  42. @Data
  43. @AllArgsConstructor
  44. @NoArgsConstructor
  45. static class Hero {
  46. private String id;
  47. private String name;
  48. private String hp;
  49. private String damage;
  50. }
  51. //静态内部类HeroDAO,通过操作内存来模拟对数据库的增删改查
  52. static class HeroDAO {
  53. //保存模拟数据容器
  54. public static List<Hero> heroList = new ArrayList<>();
  55. //模拟数据
  56. static {
  57. Hero h1 = new Hero("1", "盖伦", "616", "100");
  58. Hero h2 = new Hero("2", "提莫", "512", "102");
  59. Hero h3 = new Hero("3", "奎因", "832", "200");
  60. Hero h4 = new Hero("4", "后羿", "822", "500");
  61. Hero h5 = new Hero("5", "盖伦", "616", "100");
  62. Hero h6 = new Hero("6", "提莫", "512", "102");
  63. Hero h7 = new Hero("7", "奎因", "832", "200");
  64. Hero h8 = new Hero("8", "后羿", "822", "500");
  65. Hero h9 = new Hero("9", "盖伦", "616", "100");
  66. Hero h10 = new Hero("10", "提莫", "512", "102");
  67. Hero h11 = new Hero("11", "奎因", "832", "200");
  68. Hero h12 = new Hero("12", "后羿", "822", "500");
  69. heroList.add(h1);
  70. heroList.add(h2);
  71. heroList.add(h3);
  72. heroList.add(h4);
  73. heroList.add(h5);
  74. heroList.add(h6);
  75. heroList.add(h7);
  76. heroList.add(h8);
  77. heroList.add(h9);
  78. heroList.add(h10);
  79. heroList.add(h11);
  80. heroList.add(h12);
  81. }
  82. /** * 模拟查询数据库方法 * * @return */
  83. public List<Hero> list() {
  84. return heroList;
  85. }
  86. /** * 模拟查询数据库方法 * @return */
  87. public List<Hero> list(int start, int number) {
  88. return page(heroList,start,number);
  89. }
  90. /** * 模拟新增 * @return */
  91. public void add(Hero hero) {
  92. heroList.add(0, hero);
  93. }
  94. /** * 根据id删除 * @param id */
  95. public void delete(String id) {
  96. heroList.removeIf(hero -> StringUtils.equals(id, hero.getId()));
  97. }
  98. /** * 获取数据总量 * @return */
  99. public int getTotal() {
  100. return heroList.size();
  101. }
  102. /** * 根据id更新数据 * @param hero */
  103. public void update(Hero hero) {
  104. heroList.forEach(e -> {
  105. if (StringUtils.equals(e.getId(), hero.getId())) {
  106. e.setName(hero.getName());
  107. e.setHp(hero.getHp());
  108. e.setDamage(hero.getDamage());
  109. }
  110. });
  111. }
  112. /** * 循环截取某页列表进行分页 * @param dataList 分页数据 * @param pageSize 页面大小 * @param currentPage 当前页面 */
  113. public static List<Hero> page(List<Hero> dataList,int currentPage, int pageSize ) {
  114. List<Hero> currentPageList = new ArrayList<>();
  115. if (dataList != null && dataList.size() > 0) {
  116. int currIdx = (currentPage > 1 ? (currentPage - 1) * pageSize : 0);
  117. for (int i = 0; i < pageSize && i < dataList.size() - currIdx; i++) {
  118. Hero data = dataList.get(currIdx + i);
  119. currentPageList.add(data);
  120. }
  121. }
  122. return currentPageList;
  123. }
  124. }
  125. }

11.5.1.测试展示数据库内容

  1. @Test
  2. public void customTableModelDB() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. frame.setLayout(new BorderLayout());
  7. //创建一个TableModel
  8. HeroTableModelDB htm = new HeroTableModelDB();
  9. //根据 TableModel来创建 Table
  10. JTable table = new JTable(htm);
  11. JScrollPane sp = new JScrollPane(table);
  12. frame.add(sp, BorderLayout.CENTER);
  13. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  14. frame.setVisible(true);
  15. //设置休眠时间,是为了延缓主线程销毁
  16. TimeUnit.SECONDS.sleep(1000);
  17. }

在这里插入图片描述

11.5.2.监听table选中项的变化

通过table可以获取一个 TableSelectionModel,专门用于监听jtable选中项的变化
在这里插入图片描述

  1. @Test
  2. public void getSelectionModel() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. frame.setLayout(new BorderLayout());
  7. final HeroTableModelDB htm = new HeroTableModelDB();
  8. final JTable table = new JTable(htm);
  9. // 准备一个Panel上面放一个Label用于显示哪条被选中了
  10. JPanel panel = new JPanel();
  11. final JLabel label = new JLabel("暂时未选中条目");
  12. panel.add(label);
  13. JScrollPane sp = new JScrollPane(table);
  14. // 使用selection监听器来监听table的哪个条目被选中
  15. table.getSelectionModel().addListSelectionListener(
  16. new ListSelectionListener() {
  17. // 当选择了某一行的时候触发该事件
  18. @Override
  19. public void valueChanged(ListSelectionEvent e) {
  20. // 获取哪一行被选中了
  21. int row = table.getSelectedRow();
  22. // 根据选中的行,到HeroTableModel中获取对应的对象
  23. HeroTableModelDB.Hero h = htm.heros.get(row);
  24. // 更新标签内容
  25. label.setText("当前选中的英雄是: " + h.getName());
  26. }
  27. });
  28. frame.add(panel, BorderLayout.NORTH);
  29. frame.add(sp, BorderLayout.CENTER);
  30. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  31. frame.setVisible(true);
  32. //设置休眠时间,是为了延缓主线程销毁
  33. TimeUnit.SECONDS.sleep(1000);
  34. }

11.5.3.输入项校验

如果用户输入的名称为空,或者血量不是小数,在提交数据的时候弹出提示。
在这里插入图片描述

  1. @Test
  2. public void checkTable() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(200, 200);
  6. frame.setLayout(new BorderLayout());
  7. final HeroTableModelDB htm = new HeroTableModelDB();
  8. final JTable table = new JTable(htm);
  9. // 增加 一个 panel用于放置名称,血量输入框和增加 按钮
  10. JPanel panel = new JPanel();
  11. final JLabel lName = new JLabel("名称");
  12. final JTextField tfName = new JTextField("");
  13. final JLabel lHp = new JLabel("血量");
  14. final JTextField tfHp = new JTextField("");
  15. JButton bAdd = new JButton("增加");
  16. tfName.setPreferredSize(new Dimension(80, 30));
  17. tfHp.setPreferredSize(new Dimension(80, 30));
  18. panel.add(lName);
  19. panel.add(tfName);
  20. panel.add(lHp);
  21. panel.add(tfHp);
  22. panel.add(bAdd);
  23. // 为增加按钮添加监听
  24. bAdd.addActionListener(new ActionListener() {
  25. @Override
  26. public void actionPerformed(ActionEvent e) {
  27. HeroDAO heroDAO = new HeroDAO();
  28. String name = tfName.getText();
  29. // 通过name长度判断 名称是否为空
  30. if (name.length() == 0) {
  31. // 弹出对话框提示用户
  32. JOptionPane.showMessageDialog(frame, "名称不能为空");
  33. // 名称输入框获取焦点
  34. tfName.grabFocus();
  35. return;
  36. }
  37. String hp = tfHp.getText().trim();
  38. try {
  39. // 把hp转换为浮点型,如果出现异常NumberFormatException表示不是浮点型格式
  40. Float.parseFloat(hp);
  41. } catch (NumberFormatException e1) {
  42. JOptionPane.showMessageDialog(frame, "血量只能是小数 ");
  43. tfHp.grabFocus();
  44. return;
  45. }
  46. // 根据输入框数据创建一个Hero对象
  47. Hero hero = new Hero();
  48. hero.setName(name);
  49. hero.setHp(hp);
  50. hero.setId(System.currentTimeMillis() + "");
  51. hero.setDamage("0");
  52. // 通过dao把该对象加入到数据库
  53. heroDAO.add(hero);
  54. // 通过dao更新tablemodel中的数据
  55. htm.heros = heroDAO.list();
  56. // 调用JTable的updateUI,刷新界面。
  57. // 刷新界面的时候,会到tableModel中去取最新的数据
  58. // 就能看到新加进去的数据了
  59. table.updateUI();
  60. }
  61. });
  62. JScrollPane scrollPane = new JScrollPane(table);
  63. frame.add(panel, BorderLayout.NORTH);
  64. frame.add(scrollPane, BorderLayout.CENTER);
  65. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  66. frame.setVisible(true);
  67. //设置休眠时间,是为了延缓主线程销毁
  68. TimeUnit.SECONDS.sleep(1000);
  69. }

11.5.4.选中指定行

  • table初始化后,默认选中第一行
  • 新增数据后,默认选中新增的这一条
    在这里插入图片描述

    @Test

    1. public void selectLine() throws InterruptedException {
    2. JFrame frame = new JFrame("LoL");
    3. frame.setSize(400, 300);
    4. frame.setLocation(200, 200);
    5. frame.setLayout(new BorderLayout());
    6. final HeroTableModelDB htm = new HeroTableModelDB();
    7. final JTable table = new JTable(htm);
    8. // 设置选择模式为 只能选中一行
    9. table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
    10. // 选中第一行 (基本0)
    11. table.getSelectionModel().setSelectionInterval(0, 0);
    12. // 增加 一个 panel用于放置名称,血量输入框和增加 按钮
    13. JPanel panel = new JPanel();
    14. final JLabel lName = new JLabel("名称");
    15. final JTextField tfName = new JTextField("");
    16. final JLabel lHp = new JLabel("血量");
    17. final JTextField tfHp = new JTextField("");
    18. JButton bAdd = new JButton("增加");
    19. tfName.setPreferredSize(new Dimension(80, 30));
    20. tfHp.setPreferredSize(new Dimension(80, 30));
    21. panel.add(lName);
    22. panel.add(tfName);
    23. panel.add(lHp);
    24. panel.add(tfHp);
    25. panel.add(bAdd);
    26. // 为增加按钮添加监听
    27. bAdd.addActionListener(new ActionListener() {
    28. @Override
    29. public void actionPerformed(ActionEvent e) {
    30. HeroDAO heroDAO = new HeroDAO();
    31. String name = tfName.getText();
    32. // 通过name长度判断 名称是否为空
    33. if (name.length() == 0) {
    34. // 弹出对话框提示用户
    35. JOptionPane.showMessageDialog(frame, "名称不能为空");
    36. // 名称输入框获取焦点
    37. tfName.grabFocus();
    38. return;
    39. }
    40. String hp = tfHp.getText().trim();
    41. try {
    42. // 把hp转换为浮点型,如果出现异常NumberFormatException表示不是浮点型格式
    43. Float.parseFloat(hp);
    44. } catch (NumberFormatException e1) {
    45. JOptionPane.showMessageDialog(frame, "血量只能是小数 ");
    46. tfHp.grabFocus();
    47. return;
    48. }
    49. // 根据输入框数据创建一个Hero对象
    50. Hero hero = new Hero();
    51. hero.setName(name);
    52. hero.setHp(hp);
    53. hero.setId(System.currentTimeMillis() + "");
    54. hero.setDamage("0");
    55. // 通过dao把该对象加入到数据库
    56. heroDAO.add(hero);
    57. // 通过dao更新tablemodel中的数据
    58. htm.heros = heroDAO.list();
    59. // 调用JTable的updateUI,刷新界面。
    60. // 刷新界面的时候,会到tableModel中去取最新的数据
    61. // 就能看到新加进去的数据了
    62. table.updateUI();
    63. // 选中 第一行 ,因为 DAO是按照 ID倒排序查询,所以第一行就是新加入的数据
    64. table.getSelectionModel().setSelectionInterval(0, 0);
    65. }
    66. });
    67. JScrollPane scrollPane = new JScrollPane(table);
    68. frame.add(panel, BorderLayout.NORTH);
    69. frame.add(scrollPane, BorderLayout.CENTER);
    70. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    71. frame.setVisible(true);
    72. TimeUnit.SECONDS.sleep(1000);
    73. }

11.6.练习:表格增删改查(使用List模拟数据库操作)

在这里插入图片描述

  1. public class TableCrudDemo {
  2. static HeroTableModelDB htm = new HeroTableModelDB();
  3. static JTable table = new JTable(htm);
  4. // 把分页按钮放在这里,后面监听器好访问
  5. static JButton bFirst = new JButton("首页");
  6. static JButton bPre = new JButton("上一页");
  7. static JButton bNext = new JButton("下一页");
  8. static JButton bLast = new JButton("末页");
  9. static JComboBox<Integer> goPageCheckBox = new JComboBox<>();//跳转页数
  10. static int pageSize = 10;// 每页显示10个
  11. static int pageNum = 1;// 开始的页码
  12. private static boolean cbListenerEnabled = true;
  13. public static void main(String[] args) {
  14. //WebLookAndFeel.install ();
  15. final JFrame frame = new JFrame("LoL");
  16. frame.setSize(400, 340);
  17. frame.setLocation(200, 200);
  18. //默认选中第0行
  19. table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
  20. table.getSelectionModel().setSelectionInterval(0, 0);
  21. JPanel pOperation = new JPanel();
  22. JButton bAdd = new JButton("增加");
  23. JButton bDelete = new JButton("删除");
  24. JButton bEdit = new JButton("编辑");
  25. pOperation.add(bAdd);
  26. pOperation.add(bDelete);
  27. pOperation.add(bEdit);
  28. JPanel pPage = new JPanel();
  29. pPage.add(bFirst);
  30. pPage.add(bPre);
  31. pPage.add(goPageCheckBox);
  32. pPage.add(bNext);
  33. pPage.add(bLast);
  34. //新增按钮事件
  35. addBtnListener(frame, bAdd);
  36. //编辑按钮事件
  37. editBtnListener(frame, bEdit);
  38. //删除按钮事件
  39. deleteBtnListener(frame, bDelete);
  40. //分页按钮事件
  41. pageBtnListener();
  42. //跳转按钮事件
  43. goPageBtnListener(goPageCheckBox);
  44. JScrollPane scrollPane = new JScrollPane(table);
  45. frame.setLayout(null);
  46. scrollPane.setBounds(0, 0, 394, 200);
  47. pOperation.setBounds(0, 200, 394, 50);
  48. pPage.setBounds(0, 250, 394, 200);
  49. frame.add(scrollPane);
  50. frame.add(pOperation);
  51. frame.add(pPage);
  52. updateButtonStatus();
  53. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  54. frame.setVisible(true);
  55. }
  56. /** * 跳转按钮事件 */
  57. private static void goPageBtnListener(JComboBox<Integer> goPageCheckBox) {
  58. //跳转按钮事件
  59. goPageCheckBox.addActionListener(new ActionListener() {
  60. @Override
  61. public void actionPerformed(ActionEvent e) {
  62. if (!cbListenerEnabled) {
  63. return;
  64. }
  65. int currentPage = (int) goPageCheckBox.getSelectedItem();
  66. pageNum = currentPage;//当前页数=跳转页数
  67. updateTable();
  68. updateButtonStatus();
  69. }
  70. });
  71. }
  72. /** * 新增按钮事件 */
  73. private static void addBtnListener(JFrame frame, JButton bAdd) {
  74. bAdd.addActionListener(new ActionListener() {
  75. @Override
  76. public void actionPerformed(ActionEvent e) {
  77. new AddDialog(frame).setVisible(true);
  78. updateButtonStatus();
  79. }
  80. });
  81. }
  82. /** * 编辑按钮事件 */
  83. private static void editBtnListener(JFrame frame, JButton bEdit) {
  84. bEdit.addActionListener(new ActionListener() {
  85. @Override
  86. public void actionPerformed(ActionEvent e) {
  87. // 判断是否选中
  88. int index = table.getSelectedRow();
  89. if (-1 == index) {
  90. JOptionPane.showMessageDialog(frame, "编辑前需要先选中一行");
  91. return;
  92. }
  93. // 获取选中的对象
  94. HeroTableModelDB.Hero hero = htm.heros.get(index);
  95. // 显示编辑Dialog
  96. EditDialog ed = new EditDialog(frame);
  97. ed.tfName.setText(hero.getName());
  98. ed.tfHp.setText(hero.getHp());
  99. ed.setVisible(true);
  100. }
  101. });
  102. }
  103. /** * 删除按钮事件 */
  104. private static void deleteBtnListener(JFrame frame, JButton bDelete) {
  105. bDelete.addActionListener(new ActionListener() {
  106. @Override
  107. public void actionPerformed(ActionEvent e) {
  108. // 判断是否选中
  109. int index = table.getSelectedRow();
  110. if (-1 == index) {
  111. JOptionPane.showMessageDialog(frame, "删除前需要先选中一行");
  112. return;
  113. }
  114. // 进行确认是否要删除
  115. if (JOptionPane.OK_OPTION != JOptionPane.showConfirmDialog(frame, "确认要删除?")) {
  116. return;
  117. }
  118. // 获取id
  119. HeroTableModelDB.Hero hero = htm.heros.get(index);
  120. String id = hero.getId();
  121. // 删除
  122. new HeroTableModelDB.HeroDAO().delete(id);
  123. // 更新table
  124. pageNum = 1;
  125. updateTable();
  126. updateButtonStatus();
  127. }
  128. });
  129. }
  130. /** * 分页按钮事件 */
  131. private static void pageBtnListener() {
  132. bFirst.addActionListener(new ActionListener() {
  133. @Override
  134. public void actionPerformed(ActionEvent e) {
  135. pageNum = 1;
  136. updateTable();
  137. updateButtonStatus();
  138. }
  139. });
  140. bPre.addActionListener(new ActionListener() {
  141. @Override
  142. public void actionPerformed(ActionEvent e) {
  143. pageNum = pageNum - 1;
  144. updateTable();
  145. updateButtonStatus();
  146. }
  147. });
  148. bNext.addActionListener(new ActionListener() {
  149. @Override
  150. public void actionPerformed(ActionEvent e) {
  151. pageNum = pageNum + 1;
  152. updateTable();
  153. updateButtonStatus();
  154. }
  155. });
  156. bLast.addActionListener(new ActionListener() {
  157. @Override
  158. public void actionPerformed(ActionEvent e) {
  159. pageNum = calcTotalPage();
  160. updateTable();
  161. updateButtonStatus();
  162. }
  163. });
  164. }
  165. /** * 更新按钮状态 */
  166. private static void updateButtonStatus() {
  167. int totalPage = calcTotalPage();
  168. // 是否有上一页
  169. if (1 != pageNum) {
  170. bFirst.setEnabled(true);
  171. bPre.setEnabled(true);
  172. }
  173. // 是否是第一页
  174. if (1 == pageNum) {
  175. bFirst.setEnabled(false);
  176. bPre.setEnabled(false);
  177. }
  178. // 是否是最后一页
  179. if (pageNum == totalPage) {
  180. bLast.setEnabled(false);
  181. bNext.setEnabled(false);
  182. }
  183. // 是否有下一页
  184. if (pageNum < totalPage) {
  185. bLast.setEnabled(true);
  186. bNext.setEnabled(true);
  187. }
  188. cbListenerEnabled = false;
  189. goPageCheckBox.removeAllItems();
  190. //总共的页数
  191. for (int i = 0; i < totalPage; i++) {
  192. goPageCheckBox.addItem(i + 1);
  193. }
  194. cbListenerEnabled = true;
  195. goPageCheckBox.setSelectedItem(pageNum);
  196. }
  197. /** * 根据表格数据 */
  198. public static void updateTable() {
  199. htm.heros = new HeroTableModelDB.HeroDAO().list(pageNum, pageSize);
  200. table.updateUI();
  201. if (!htm.heros.isEmpty()) {
  202. table.getSelectionModel().setSelectionInterval(0, 0);
  203. }
  204. }
  205. /** * 检查输入框空 * * @param tf * @param msg * @return */
  206. private static boolean checkEmpty(JTextField tf, String msg) {
  207. String value = tf.getText();
  208. if (0 == value.length()) {
  209. JOptionPane.showMessageDialog(null, msg + " 不能为空");
  210. tf.grabFocus();
  211. return false;
  212. }
  213. return true;
  214. }
  215. /** * 检查输入框空与非数字 * * @param tf * @param msg * @return */
  216. private static boolean checkNumber(JTextField tf, String msg) {
  217. String value = tf.getText();
  218. if (0 == value.length()) {
  219. JOptionPane.showMessageDialog(null, msg + " 不能为空");
  220. tf.grabFocus();
  221. return false;
  222. }
  223. try {
  224. Integer.parseInt(value);
  225. } catch (NumberFormatException e) {
  226. JOptionPane.showMessageDialog(null, msg + " 只能是整数");
  227. tf.grabFocus();
  228. return false;
  229. }
  230. return true;
  231. }
  232. /** * 计算总页数 * * @return */
  233. private static int calcTotalPage() {
  234. int total = new HeroTableModelDB.HeroDAO().getTotal();
  235. int last = total % pageSize == 0 ? total / pageSize : (total / pageSize) + 1;
  236. return last;
  237. }
  238. /** * 新增对话框 */
  239. static class AddDialog extends JDialog {
  240. JLabel lName = new JLabel("名称");
  241. JLabel lHp = new JLabel("血量");
  242. JTextField tfName = new JTextField();
  243. JTextField tfHp = new JTextField();
  244. JButton bSubmit = new JButton("提交");
  245. AddDialog(JFrame frame) {
  246. super(frame);
  247. this.setModal(true);
  248. int gap = 50;
  249. this.setLayout(null);
  250. JPanel pInput = new JPanel();
  251. JPanel pSubmit = new JPanel();
  252. pInput.setLayout(new GridLayout(2, 2, gap, gap));
  253. pInput.add(lName);
  254. pInput.add(tfName);
  255. pInput.add(lHp);
  256. pInput.add(tfHp);
  257. pSubmit.add(bSubmit);
  258. pInput.setBounds(50, 20, 200, 100);
  259. pSubmit.setBounds(0, 130, 300, 150);
  260. this.add(pInput);
  261. this.add(pSubmit);
  262. this.setSize(300, 200);
  263. this.setLocationRelativeTo(frame);
  264. bSubmit.addActionListener(new ActionListener() {
  265. @Override
  266. public void actionPerformed(ActionEvent e) {
  267. if (checkEmpty(tfName, "名称")) {
  268. if (checkNumber(tfHp, "hp")) {
  269. String name = tfName.getText();
  270. String hp = tfHp.getText();
  271. HeroTableModelDB.Hero h = new HeroTableModelDB.Hero();
  272. h.setName(name);
  273. h.setHp(hp);
  274. h.setId(System.currentTimeMillis() + "");
  275. h.setDamage("0");
  276. new HeroTableModelDB.HeroDAO().add(h);
  277. JOptionPane.showMessageDialog(frame, "提交成功 ");
  278. AddDialog.this.setVisible(false);
  279. pageNum = 1;
  280. updateTable();
  281. }
  282. }
  283. }
  284. });
  285. }
  286. }
  287. /** * 编辑对话框 */
  288. static class EditDialog extends JDialog {
  289. JLabel lName = new JLabel("名称");
  290. JLabel lHp = new JLabel("血量");
  291. JTextField tfName = new JTextField();
  292. JTextField tfHp = new JTextField();
  293. JButton bSubmit = new JButton("提交");
  294. EditDialog(JFrame frame) {
  295. super(frame);
  296. this.setModal(true);
  297. int gap = 50;
  298. this.setLayout(null);
  299. JPanel pInput = new JPanel();
  300. JPanel pSubmit = new JPanel();
  301. pInput.setLayout(new GridLayout(2, 2, gap, gap));
  302. pInput.add(lName);
  303. pInput.add(tfName);
  304. pInput.add(lHp);
  305. pInput.add(tfHp);
  306. pSubmit.add(bSubmit);
  307. pInput.setBounds(50, 20, 200, 100);
  308. pSubmit.setBounds(0, 130, 300, 150);
  309. this.add(pInput);
  310. this.add(pSubmit);
  311. this.setSize(300, 200);
  312. this.setLocationRelativeTo(frame);
  313. bSubmit.addActionListener(new ActionListener() {
  314. @Override
  315. public void actionPerformed(ActionEvent e) {
  316. if (checkEmpty(tfName, "名称")) {
  317. if (checkNumber(tfHp, "hp")) {
  318. // 获取id
  319. int index = table.getSelectedRow();
  320. String id = htm.heros.get(index).getId();
  321. String name = tfName.getText();
  322. String hp = tfHp.getText();
  323. HeroTableModelDB.Hero h = new HeroTableModelDB.Hero();
  324. h.setName(name);
  325. h.setHp(hp);
  326. h.setId(id);
  327. h.setDamage("0");
  328. new HeroTableModelDB.HeroDAO().update(h);
  329. JOptionPane.showMessageDialog(frame, "提交成功 ");
  330. EditDialog.this.setVisible(false);
  331. updateTable();
  332. }
  333. }
  334. }
  335. });
  336. }
  337. }
  338. }

十二.Swing中的线程

12.1.三种线程

在Swing程序的开发中,有3种线程的概念

  1. 初始化线程
    用于创建各种容器,组件并显示他们,一旦创建并显示,初始化线程的任务就结束了。
  2. 事件调度线程
    Swing是一个事件驱动的模型,所有和事件相关的操作都放是放在事件调度线程 (Event Dispatch)中进行的。比如按钮的点击时间,ActionListener.actionPerformed中的代码,就是在事件调度线程 Event Dispatch Thread中执行的。
  3. 长耗时任务线程
    有时候需要进行一些长时间的操作,比如访问数据库,文件复制,连接网络,统计文件总数等等。 这些操作就不适合放在事件调度线程中进行,因为占用时间久了,会让使用者感觉界面响应很卡顿。 为了保持界面响应的流畅性,所有长耗时任务都应该放在专门的 长耗时任务线程中进行

12.2.事件调度线程是单线程的

为什么 事件调度线程是单线程的呢?

因为 Swing的各种组件,比如JTextField,JButton 都不是线程安全的,这就意味着,如果有多个线程,那么同一个JTextField的setText方法,可能会被多个线程同时调用,这会导致线程安全问题)的发生。

  • 如果把组件类设计成为线程安全的,由于Swing事件调度的复杂性,就很有可能导致死锁的发生。

为了规避同步问题,以及降低整个Swing设计的复杂度,提高Swing的相应速度,Swing中的 事件调度线程被设计成为了单线程模式,即只有一个线程在负责事件的响应工作。

12.3.初始化线程

如代码所示,我们初始化一个图形界面的时候,都会直接在主方法的主线程里,直接调用如下代码来进行初始化

  1. new TestFrame().setVisible(true);

如果是简单程序这没有什么问题,如果是复杂的程序就有可能产生问题了。

  • 因为这里有两个线程在同时访问组件:1. 主线程 2. 事件调度线程。 如果是复杂的图形界面程序,就有可能出现这两个线程同时操作的情况,导致同步问题的产生。

为了规避这个问题的产生,创建和显示界面的工作,最好也交给事件调度线程,这样就保证了只有一个线程在访问这些组件

  1. public class TestGUI {
  2. public static void main(String[] args) {
  3. //交给主线程创建和显示界面
  4. // new TestFrame().setVisible(true);
  5. //交给事件调度线程创建和显示界面
  6. SwingUtilities.invokeLater(new Runnable() {
  7. public void run() {
  8. new TestFrame().setVisible(true);
  9. }
  10. });
  11. }
  12. static class TestFrame extends JFrame {
  13. public TestFrame() {
  14. setTitle("LoL");
  15. setSize(400, 300);
  16. setLocation(200, 200);
  17. setLayout(null);
  18. JButton btn = new JButton("一键秒对方基地挂");
  19. btn .setBounds(50, 50, 280, 30);
  20. add(btn );
  21. setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  22. setVisible(true);
  23. System.out.println("当前线程是否是 事件调度线程: " + SwingUtilities.isEventDispatchThread());
  24. }
  25. }
  26. }

12.4.事件调度线程

以 按钮监听 中的代码为例,ActionListener.actionPerformed 中的代码,就是事件调度线程执行的。

可以借助SwingUtilities.isEventDispatchThread() 判断是否是事件调度线程

  1. @Test
  2. public void eventDiapatcherThread() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(400, 300);
  5. frame.setLocation(580, 200);
  6. frame.setLayout(null);
  7. final JLabel label = new JLabel();
  8. ImageIcon i = new ImageIcon("E:\\data\\gareen.jpg");
  9. label.setIcon(i);
  10. label.setBounds(50, 50, i.getIconWidth(), i.getIconHeight());
  11. JButton b = new JButton("隐藏图片");
  12. b.setBounds(150, 200, 100, 30);
  13. b.addActionListener(new ActionListener() {
  14. @Override
  15. public void actionPerformed(ActionEvent e) {
  16. label.setVisible(false);
  17. System.out.println("当前使用的是事件调度线程:" + SwingUtilities.isEventDispatchThread());
  18. }
  19. });
  20. frame.add(label);
  21. frame.add(b);
  22. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  23. frame.setVisible(true);
  24. //设置休眠时间,是为了延缓主线程销毁
  25. TimeUnit.SECONDS.sleep(1000);
  26. }

点击按钮
在这里插入图片描述

在这里插入图片描述

12.5.长耗时任务线程

有时候需要执行长耗时任务,比如数据库查询,文件复制,访问网络等。

  • 这些操作一般都会在事件响应后发起,就会自动进入事件调度线程。 而事件调度线程又是单线程模式,其结果就会是在执行这些长耗时任务的时候,界面就无响应了
  • 如图所示,当点击第一个按钮的时候,会在其中进行一个5秒钟的任务,这个期间,第一个按钮会保持按下状态,其他按钮也无法点击,出现了无响应了状态。
    在这里插入图片描述

为了解决这个问题,Swing提供了一个SwingWorker类来解决。SwingWorker是一个抽象类,为了使用,必须实现方法 doInBackground,在doInBackground中编写我们的任务,然后执行SwingWorker的execute方法,放在专门的工作线程中去运行。

  1. @Test
  2. public void longConsumingTask() throws InterruptedException {
  3. JFrame frame = new JFrame("LoL");
  4. frame.setSize(300, 300);
  5. frame.setLocation(200, 200);
  6. frame.setLayout(new FlowLayout());
  7. JButton b1 = new JButton("在事件调度线程中执行长耗时任务");
  8. JButton b2 = new JButton("使用SwingWorker执行长耗时任务");
  9. JLabel label = new JLabel("任务执行结果");
  10. frame.add(b1);
  11. frame.add(b2);
  12. frame.add(label);
  13. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  14. b1.addActionListener(new ActionListener() {
  15. @Override
  16. public void actionPerformed(ActionEvent e) {
  17. label.setText("开始执行完毕");
  18. try {
  19. Thread.sleep(5000);
  20. } catch (InterruptedException e1) {
  21. // TODO Auto-generated catch block
  22. e1.printStackTrace();
  23. }
  24. label.setText("任务执行完毕");
  25. }
  26. });
  27. b2.addActionListener(new ActionListener() {
  28. @Override
  29. public void actionPerformed(ActionEvent e) {
  30. SwingWorker<Void, Void> worker = new SwingWorker<Void, Void>() {
  31. @Override
  32. protected Void doInBackground() throws Exception {
  33. System.out.println("执行这个SwingWorder的线程是:" + Thread.currentThread().getName());
  34. label.setText("开始执行完毕");
  35. try {
  36. Thread.sleep(5000);
  37. } catch (InterruptedException e1) {
  38. // TODO Auto-generated catch block
  39. e1.printStackTrace();
  40. }
  41. label.setText("任务执行完毕");
  42. return null;
  43. }
  44. };
  45. worker.execute();
  46. }
  47. });
  48. frame.setVisible(true);
  49. //设置休眠时间,是为了延缓主线程销毁
  50. TimeUnit.SECONDS.sleep(1000);
  51. }

在这里插入图片描述
在这里插入图片描述

12.6.练习:搜索指定目录下的所有Java文件

在这里插入图片描述

  1. public class SearchJavaFile {
  2. private static int FOUND_COUNT = 0;
  3. public static void main(String[] args) {
  4. // 为了规避线程安全问题的产生,创建和显示界面的工作,使用调度线程初始化窗口,这样就保证了只有一个线程在访问这些组件
  5. SwingUtilities.invokeLater(() -> {
  6. WebLookAndFeel.install();
  7. new SearchFrame().setVisible(true);
  8. }
  9. );
  10. }
  11. /** * 递归搜索文件内容,是否包含指定关键字文件个数 * * @param file 目录 * @param search 关键字 * @param suffix 文件后缀 */
  12. public static void search(File file, String search, String suffix) {
  13. if (file.isFile()) {
  14. if (file.getName().toLowerCase().endsWith(suffix)) {
  15. String fileContent = readFileConent(file);
  16. if (fileContent.contains(search)) {
  17. System.out.println(fileContent);
  18. FOUND_COUNT++;
  19. }
  20. }
  21. }
  22. if (file.isDirectory()) {
  23. File[] files = file.listFiles();
  24. for (File f : files) {
  25. search(f, search, suffix);
  26. }
  27. }
  28. }
  29. /** * 递归搜索文件内容,是否包含指定关键字文件个数 * * @param file 目录 * @param search 关键字 * @param suffix 文件后缀 */
  30. public static int searchPlus(File file, String search, String suffix) {
  31. int foundCount = 0;
  32. if (file.isFile()) {
  33. if (file.getName().toLowerCase().endsWith(suffix)) {
  34. String fileContent = readFileConent(file);
  35. if (fileContent.contains(search)) {
  36. System.out.println(fileContent);
  37. foundCount += 1;
  38. }
  39. }
  40. }
  41. if (file.isDirectory()) {
  42. File[] files = file.listFiles();
  43. for (File f : files) {
  44. foundCount += searchPlus(f, search, suffix);
  45. }
  46. }
  47. return foundCount;
  48. }
  49. /** * 读取文件文本内容 * * @param file * @return */
  50. public static String readFileConent(File file) {
  51. try (FileReader fr = new FileReader(file)) {
  52. char[] all = new char[(int) file.length()];
  53. fr.read(all);
  54. return new String(all);
  55. } catch (IOException e) {
  56. e.printStackTrace();
  57. return null;
  58. }
  59. }
  60. /** * 查询窗口初始化 */
  61. static class SearchFrame extends JFrame {
  62. JLabel lLocation = new JLabel("查询目录");
  63. JLabel lSearch = new JLabel("文件内容");
  64. JTextField tfLocation = new JTextField("e:/data");
  65. JTextField tfSearch = new JTextField("g");
  66. JButton bSubmit = new JButton("搜索");
  67. /** * 容器初始化 */
  68. SearchFrame() {
  69. int gap = 50;
  70. this.setLayout(null);
  71. JPanel pInput = new JPanel();
  72. JPanel pSubmit = new JPanel();
  73. pInput.setLayout(new GridLayout(2, 2, gap, gap));
  74. pInput.add(lLocation);
  75. pInput.add(tfLocation);
  76. pInput.add(lSearch);
  77. pInput.add(tfSearch);
  78. pSubmit.add(bSubmit);
  79. pInput.setBounds(50, 20, 200, 100);
  80. pSubmit.setBounds(0, 130, 300, 150);
  81. this.add(pInput);
  82. this.add(pSubmit);
  83. this.setSize(300, 200);
  84. this.setLocationRelativeTo(null);
  85. this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  86. bSubmit.addActionListener(new ActionListener() {
  87. @Override
  88. public void actionPerformed(ActionEvent e) {
  89. FOUND_COUNT = 0;
  90. String location = tfLocation.getText();
  91. String search = tfSearch.getText();
  92. if (0 == location.length()) {
  93. JOptionPane.showMessageDialog(SearchFrame.this, "搜索目录不能为空");
  94. tfLocation.grabFocus();
  95. return;
  96. }
  97. if (0 == search.length()) {
  98. JOptionPane.showMessageDialog(SearchFrame.this, "搜索目录不能为空");
  99. tfSearch.grabFocus();
  100. return;
  101. }
  102. File folder = new File(location);
  103. if (!folder.exists()) {
  104. JOptionPane.showMessageDialog(SearchFrame.this, "搜索目录不存在");
  105. tfLocation.grabFocus();
  106. return;
  107. }
  108. if (!folder.isDirectory()) {
  109. JOptionPane.showMessageDialog(SearchFrame.this, "搜索目录不是一个文件夹");
  110. tfLocation.grabFocus();
  111. return;
  112. }
  113. //正在搜索按钮
  114. freeze();
  115. SwingWorker worker = new SwingWorker() {
  116. @Override
  117. protected Object doInBackground() throws Exception {
  118. //work here
  119. int foundCount = searchPlus(folder, search, ".java");
  120. JOptionPane.showMessageDialog(SearchFrame.this, "总共找到满足条件的文件: " + foundCount + " 个");
  121. //搜索按钮
  122. unfreeze();
  123. return null;
  124. }
  125. };
  126. worker.execute();
  127. }
  128. });
  129. }
  130. /** * 搜索按钮 */
  131. private void unfreeze() {
  132. bSubmit.setText("搜索");
  133. bSubmit.setEnabled(true);
  134. tfLocation.setEnabled(true);
  135. tfSearch.setEnabled(true);
  136. }
  137. /** * 正在搜索按钮 */
  138. private void freeze() {
  139. bSubmit.setText("正在搜索");
  140. bSubmit.setEnabled(false);
  141. tfLocation.setEnabled(false);
  142. tfSearch.setEnabled(false);
  143. }
  144. }
  145. }

十三.界面风格

Java提供了非常便捷的方式切换界面风格

引入依赖

  1. <dependency>
  2. <groupId>com.weblookandfeel</groupId>
  3. <artifactId>weblaf-ui</artifactId>
  4. <version>1.2.13</version>
  5. </dependency>

加入这一行代码就行了

  1. WebLookAndFeel.install();

使用前:
在这里插入图片描述

使用后:
在这里插入图片描述

网上还有很多Swing风格包,大家可以自己去网上搜索

发表评论

表情:
评论列表 (有 0 条评论,286人围观)

还没有评论,来说两句吧...

相关阅读