java lookandfeel_Java核心知识点,Swing用户界面组件:模型-视图-控制器设计模式

前言

在上一章中,主要介绍了如何使用Java中的事件模型。通过学习读者已经初步知道了构造图形用户界面的基本方法。本章将主要介绍构造功能齐全的图形用户界面所需要的一些重要工具。

下面,首先概述一下Swing的基本体系结构。为了理解怎样高效地使用一些更高级的组件,必须了解“底层”的东西。然后,再讲述Swing中各种常用的用户界面组件的使用,如文本域、单选按钮以及菜单等等。接下来,介绍在不用考虑特定的用户界面观感(look and feel)的情况下,如何使用Java中的布局管理器特性在一个窗口中排列这些组件。最后,介绍如何在Swing中实现对话框。

本章覆盖了基本的Swing组件,如文本组件、按钮和滑块等,这些都是使用十分频繁的重要用户界面组件。Swing中的高级组件将在卷II中讨论。如果要详细深入地理解Swing框架,推荐参考Kim Topley的《Core JFC》和《Core SwingAdvanced Topics》(Prentice Hall PTR出版社出版)。

模型-视图-控制器设计模式

前面讲过,本章将从Swing组件的体系结构开始。在解释本节标题的含义之前,先看一下构成用户界面组件(如按钮、复选框、文本域或者复杂的树形控件等)的各个部分。每个组件都有三个要素:

• 内容,如按钮的状态(是否按下),或者文本域的文本。

• 外观(颜色,大小等等)。

• 行为(对事件的反应)。

这三个要素之间的关系是相当复杂的,即使最简单的组件(如按钮)也是如此。很明显,按钮的外观显示取决于观感。Metal按钮的外观不同于Windows按钮或者Motif按钮。另外,外观显示还要取决于按钮的状态:当按钮被按下时,按钮需要被重新绘制成另一种不同的外观。而状态取决于按钮接收到的事件。当用户用鼠标在按钮上点击时,按钮就被按下。

当然,在程序中使用按钮的时候,只需把它看成是一个按钮,而不需要去考虑它的内部操作和特性。毕竟,这些是实现按钮的程序员的工作。但是,实现按钮的程序员就要对这些按钮考虑得细一些了。毕竟,无论观感如何,他们必须实现这些按钮和其他用户界面组件,以便让这些组件正常地工作。

为了实现这样的需求,Swing设计者采用了一种著名的设计模式(design pattern):模型-视图-控制器(model-view-controller)模式。该设计模式同其他许多设计模式一样,都遵循第5章中介绍的面向对象设计中的一个基本原则:限制一个对象拥有的功能数量。不要用一个按钮类完成所有的事情,而是应该让一个对象负责组件的观感,另一个对象负责存储内容。模型-视图-控制器(MVC)设计模式将告诉我们如何实现这种设计,实现三个独立的类:

• 模型(model):存储内容。

• 视图(view):显示内容。

• 控制器(controller):处理用户输入。

这个模式明确地规定了三个对象如何进行交互。模型存储内容,它没有用户界面。按钮的内容非常简单,只有几个用来表示当前按钮是否按下,是否处于活动状态的图标等。文本域内容稍稍复杂一些。它是容纳当前文本的字符串对象,与视图显示的内容并不一致—如果内容的长度大于文本域的显示长度,用户只能看到显示出来的那一部分,如图9-1所示。

43739bcb2f318c43da58b919795db36c.png

模型必须实现改变内容和查找内容的方法。例如,一个文本模型中的方法有:在当前文本中添加或者删除字符以及把当前文本作为一个字符串返回等。记住:模型是完全不可见的。显示存储在模型中的数据是视图的工作。

注意:“模型”这个术语可能不太贴切,因为人们通常把模型视为一个抽象概念的具体表示。汽车和飞机的设计者构造模型来模拟真实的汽车和飞机。但这种类比可能会使你对模型-视图-控制器模式产生错误的理解。在设计模式中,模型存储完整的内容,视图给出了内容的可视化显示(完整或者不完整)。一个更恰当的类比应当是摆好姿势给画家作画的模特(model)。此时,取决于画家是如何看模特的,并据此来作一张画。那张画是一幅正规的肖像画,还是一幅印象派作品,还是一幅立体派作品(以古怪的曲线来描画四肢)完全取决于画家。

模型-视图-控制器模式的一个优点是一个模型可以有多个视图,其中每个视图可以显示全部内容的不同部分或不同方面。例如,一个HTML编辑器常常为同一内容提供两个同时的视图:

一个WYSIWYG(所见即所得)视图和一个“原始标记”视图(见图9-2)。

a58dc4bdf1910f95de93b7b5275e82c0.png

当通过某一个视图的控制器对模型进行更新时,模型会把这种改变通知给两个视图。视图得到通知以后就会自动地刷新。当然,对于一个简单的用户界面组件如按钮来说,不需要为同一模型提供多个视图。

控制器负责处理用户输入事件,如点击鼠标和敲击键盘。然后决定是否把这些事件转化成对模型或视图的改变。例如,如果用户在一个文本框中按下了一个字符键,控制器调用模型中的“插入字符”命令,然后模型告诉视图进行更新。而视图永远不会知道文本为什么改变了。

但是如果用户按下了一个光标键,控制器就会通知视图进行卷屏。卷动视图对实际文本不会有任何影响,因此模型永远不会知道该事件的发生。

设计模式

在解决一个问题时,不需要从头做起,而是参考过去的经验,或者向做过相关工作的专家请教。设计模式是一种方法,该方法以一种结构化的形式展示了专家们的心血。

近几年来,软件工程师们开始对这些模式进行汇总分类。这个领域的先行者的灵感来源于建筑师Christopher Alexander的设计模式。他在《The Timeless Way of Building》(Oxford University Press, 1979)一书中,为公共和私人生活空间的建筑设计模式进行了分类。下面是一个典型的例子:

窗户位置

人人都喜欢靠窗户的座位,可以画上凸窗、低窗台的大窗户以及放在这里的舒适的椅子。如果一个房间没有这样的地方,那么很少有人会感到舒服和安逸。

如果这个房间没有一个像这样“位置”的窗户,那么房间里的人就可能会面对下面的选择左右为难:

1)舒服地坐下。

2)要充足的光线。

很明显,如果舒适的位置—该房间内最想坐的位置—远离窗户的话,那么冲突就不可避免。

因此,对于白天长时间呆的房间,至少要把一个窗户设计成“窗户位置”。

在Alexander的模式分类以及软件模式的分类中,每个模式都遵循一种特定的格式。模式首先说明使用环境,即产生设计问题的背景。接着解释问题,通常这里会有几个冲突的因素。最终,权衡这些冲突,给出问题的解决方案。

f7450de93263201576bc751fb04ed0ee.png

在“窗户位置”模式中,其使用环境就是白天大部分时间呆着的房间。冲突因素就是想舒服地坐下和充足的光线。而解决方案就是找到一个“窗户位置”。

在模型-视图-控制器模式中,使用环境就是表示信息和接收用户输入的用户界面系统。

这里有若干冲突因素:对于同一数据来说,可能同时需要更新多个可视化表示,而可视化表示方式可能会改变,例如,适应不同的观感标准。交互机制也可能改变,例如,支持语音命令等。解决方案是把这些功能分布到三个独立的交互组件:模型、视图和控制器。

当然,模型-视图-控制器模式要比“窗户位置”模式复杂得多,它需要详细地说明如何分布这些功能。

可以在由Erich Gamma等编写的《Design PatternsElements of Reusable ObjectOriented Software》(Addisom-Wesley出版社,1995年出版 )一书中找到模型-视图-控制器模式和其他大量实用的软件模式的规范描述。这是一本研究模式运动的书籍。

另外,我们强烈推荐另一本由Frank Buschmann等编写的《A System of Patterns》,JohnWiley & Sons出版社于1996年出版。这是一本不错的书,相对上一本来说,这本书更容易读懂。

模型-视图-控制器模式并不是Java设计中所使用的唯一模式。例如,AWT事件处理机制采用了“观察者”(observer)模式。

设计模式的另一个重要特点就是它们已经成为文化的一部分。当你谈论模型-视图-控制器模式或者“观察者”模式时,全世界的程序员都能明白你的意思。因而,模式已经成为探讨设计问题的有效方法。

图9-4给出了模型、视图、控制器对象之间的交互。

943cd196946ff28a9d3d2218b5049ca6.png

程序员使用Swing组件,通常不需要考虑它们的模型-视图-控制器体系结构。每个用户界面元素都有一个包装器类(如JButton或JTextField)来保存模型和视图。

当需要查询内容(如文本域中的文本)时,包装器类会向模型询问并且返回所要的结果。当想改变视图时(例如,在一个文本域中移动光标位置),包装器类会把此请求转发给视图。然而,有时候包装器转发命令并不卖力。在这种情况下,就不得不直接同模型打交道。(不必直接操作视图—这是观感代码的工作。)

模型-视图-控制器模式吸引Swing设计者的一个重要原因是该模式允许他们实现可插观感。每个按钮或者文本域的模型是独立于观感的。当然可视化表示是完全依赖于特殊观感的用户界面设计的,且控制器也能改变它。例如,在一个语音控制设备中,控制器需要处理的各种事件与使用键盘和鼠标的标准计算机完全不同。通过把底层模型与用户界面分离开,Swing设计者就能够重用模型的代码,甚至在程序运行时对观感进行切换。

当然,模式只能作为一种指导性的建议而并没有严格的戒律。没有一种模式能够适用于所有情况。例如,使用“窗户位置”模式(设计模式中并非主要成分)来安排小卧室就不太合适。同样地,Swing设计者发现对于可插观感实现来说,使用模型-视图-控制器模式并非都是完美的。

模型容易分离开,每个用户界面组件都有一个模型类。但是,视图和控制器的职责分工有时并不很明显,这样将会导致产生很多不同的类。当然,作为这些类的使用者来说,不必为这些细节问题费心。前面已经说过,这些类的使用者根本无需为模型操心—只是使用组件包装器类。

Swing按钮的模型-视图-控制器分析

前一章已经介绍了如何使用按钮,当时没有考虑模型、视图和控制器。按钮是最简单的用户界面元素,所以我们从按钮开始学习模型-视图-控制器模式。对于更加复杂的Swing组件来说,所遇到的类和接口都是类似的。

对于大多数组件来说,模型类实现了名字结尾为Model的接口,例如,按钮就实现了ButtonModel接口。实现了此接口的类可以定义按钮的多种状态。实际上,按钮并不复杂,Swing库包含了一个名为DefaultButtonModel的类,该类实现了这个接口。

我们可以通过查看ButtonModel接口中的方法来获知按钮模型维护的是哪种类型的数据。表9-1列举了访问性方法。

cd736bfac373489fc0cfb66072e9d673.png

每个JButton对象存储一个按钮模型对象,可以用下列形式得到它的引用。

JButton button = new JButton("Blue");

ButtonModel model = button.getModel( );

实际上,我们不必关心按钮状态的琐碎信息,只有绘制它的视图才对此感兴趣。而像按钮是否可用这样的重要信息可以从JButton类中得到。(当然,JButton类也通过向它的模型询问来获得这些信息。)

重新查看ButtonModel接口中不包含的信息。模型不存储按钮标签或者图标。对于一个按钮来说,由模型无法知道它的外观。(在关于单选按钮组的一节中会看到,这种纯粹的设计会为程序员带来一些麻烦。)

值得注意的是,同样的模型(即DefaultButtonModel)可用于下压按钮、单选按钮、复选框,甚至是菜单项。当然,这些按钮都有各自不同的视图和控制器。当使用Metal观感时,JButton类用BasicButtonUI类作为其视图;用ButtonUIListener类作为控制器。一般来说,每个Swing组件都有一个相关的后缀为UI的视图对象,但并不是所有的Swing组件都有专门的控制器对象。

所以,在给出JButton底层工作的简短介绍之后,你可能会想:JButton究竟是什么?事实上,它仅仅是一个继承了JComponent的包装器类,JComponent包含了一个DefaultButtonModel对象,一些视图数据(例如按钮标签和图标)以及一个负责按钮视图的BasicButtonUI对象。

本文就介绍到这里了,喜欢的朋友可以转发关注一下!!

下篇文章介绍布局管理器概述的内容,不容错过!!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个基于Java Swing的简单文件拷贝GUI界面代码,包含了8个以上的Swing组件和事件响应,同时支持Look and Feel设置和二进制类型文件的复制(例如图片、声音等)。 ```java import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; public class FileCopyGUI extends JFrame implements ActionListener { private JButton btnSrc, btnDest, btnCopy; private JTextField txtSrc, txtDest; private JProgressBar progressBar; private JCheckBox chkBinary; private JLabel lblStatus; public FileCopyGUI() { super("File Copy"); // Set Look and Feel try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } // Create components btnSrc = new JButton("Source"); btnDest = new JButton("Destination"); btnCopy = new JButton("Copy"); txtSrc = new JTextField(20); txtDest = new JTextField(20); progressBar = new JProgressBar(); chkBinary = new JCheckBox("Binary Mode"); lblStatus = new JLabel(""); // Set component properties progressBar.setStringPainted(true); // Set component positions setLayout(new GridBagLayout()); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridx = 0; gbc.gridy = 0; gbc.anchor = GridBagConstraints.WEST; gbc.insets = new Insets(5, 5, 5, 5); add(btnSrc, gbc); gbc.gridx = 1; add(txtSrc, gbc); gbc.gridx = 2; add(btnDest, gbc); gbc.gridx = 3; add(txtDest, gbc); gbc.gridx = 0; gbc.gridy = 1; gbc.gridwidth = 4; gbc.fill = GridBagConstraints.HORIZONTAL; add(progressBar, gbc); gbc.gridx = 0; gbc.gridy = 2; gbc.gridwidth = 4; add(chkBinary, gbc); gbc.gridx = 0; gbc.gridy = 3; gbc.gridwidth = 4; add(btnCopy, gbc); gbc.gridx = 0; gbc.gridy = 4; gbc.gridwidth = 4; add(lblStatus, gbc); // Add event listeners btnSrc.addActionListener(this); btnDest.addActionListener(this); btnCopy.addActionListener(this); // Set window properties setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setSize(400, 200); setLocationRelativeTo(null); setVisible(true); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == btnSrc) { // Show file chooser dialog for source file JFileChooser chooser = new JFileChooser(); FileNameExtensionFilter filter = new FileNameExtensionFilter("Binary Files", "jpg", "gif", "png", "wav", "mp3"); chooser.setFileFilter(filter); int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { txtSrc.setText(chooser.getSelectedFile().getPath()); } } else if (e.getSource() == btnDest) { // Show file chooser dialog for destination file JFileChooser chooser = new JFileChooser(); chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int returnVal = chooser.showOpenDialog(this); if (returnVal == JFileChooser.APPROVE_OPTION) { txtDest.setText(chooser.getSelectedFile().getPath()); } } else if (e.getSource() == btnCopy) { // Copy the file String srcPath = txtSrc.getText(); String destPath = txtDest.getText(); boolean binaryMode = chkBinary.isSelected(); if (srcPath.isEmpty() || destPath.isEmpty()) { JOptionPane.showMessageDialog(this, "Please select source and destination files."); return; } try { File srcFile = new File(srcPath); File destFile = new File(destPath, srcFile.getName()); FileInputStream fis = new FileInputStream(srcFile); FileOutputStream fos = new FileOutputStream(destFile); byte[] buffer = new byte[1024]; int bytesRead = 0; while ((bytesRead = fis.read(buffer)) != -1) { fos.write(buffer, 0, bytesRead); if (binaryMode) { progressBar.setValue((int) (srcFile.length() * 100.0 / srcFile.length())); } else { progressBar.setValue((int) (srcFile.length() * 100.0 / destFile.length())); } } fis.close(); fos.close(); lblStatus.setText("File copied successfully."); } catch (IOException ex) { JOptionPane.showMessageDialog(this, "Error copying file: " + ex.getMessage()); } } } public static void main(String[] args) { new FileCopyGUI(); } } ``` 在本示例中,我们创建了一个名为`FileCopyGUI`的JFrame窗口,其中包含了8个以上的Swing组件,包括JButton、JTextField、JProgressBar、JCheckBox和JLabel等。我们给“Source”和“Destination”按钮以及“Copy”按钮添加了事件响应函数,当用户单击这些按钮时,我们将分别显示文件选择器对话框来选择源文件和目标文件,或者复制源文件到目标文件中。 在文件复制过程中,我们使用`FileInputStream`和`FileOutputStream`类来打开源文件和目标文件,然后使用一个缓冲区来读取和写入文件内容。我们在进度栏中显示文件复制的进度,并在复制过程结束后在标签中显示成功消息或错误消息。在复制过程中,我们可以选择二进制模式或文本模式来复制文件,这可以通过选中或取消“Binary Mode”复选框来实现。 这是一个简单的文件拷贝GUI界面Java Swing示例代码,可以拓展和修改以满足特定的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值