java-swing编程(一个简单的图片查看器)
图1
图2
图3
图4
图5
图6
怎么样,java swing可视化编程。适合对java swing有所了解的java爱好者。如果有兴趣
就继续往下看,别见笑!
包含的内容:
1. 整个窗口为一个JFrame。
2. 最上方的JMenuBar。
3. 中间最大的那块区域——mainPanel。
4. 右侧边栏——rightPanel。
5. 底部的一栏——basePanel。
主要功能:
JMenuBar里设置了两个菜单项——File和Help。File里有打开文件、关闭文件和退出菜单项。单击Open...或者按快捷键alt+O,弹出JFileChooser文件选择对话框,选择图片文件(这里支持jpg、jpeg、gif、tif、tiff和png五种格式)后,图片将在mainPanel里显示。同一文件夹下的其他图片文件显示在rightPanel,如果图片很多可以出现滑动条。basePanel 里有两个按钮和一个显示当前图片序号和图片总数的标签;按钮可以往上往下翻图片,主面板、右侧边栏和标签都会动态更新。如果到了最后一张,“下一个”按钮被禁用;第一张时,“上一个”被禁用。同理在右侧边栏里选图片其它地方也都可以动态更新。
点File里的Close时,会清空mainPanel,rightPanel和标签中显示的内容,禁用两个按钮,效果(如图1)就像是还没有打开文件一样。
点退出时关闭Frame,结束程序。
下面正式开始介绍程序。
注:这里所讲的和提供的源码稍有差异,有兴趣的可以结合文中给出的代码去编写自己的类。
程序分为四部分,分别为三个面板的建立。最后组合在一起,放在一个JFrame里,加入菜单栏,各种监听器。
Part I 写主面板类——MainPanel
可以从JPanel继承。MainPanel相对与JPanel,多了一个图像显示的功能,所以里面一定要有获取图片的方法,还必须重载paintComponent方法。
关于paintComponent这我想多说一点。可视化组件要完成显示的工作一般都要调用paint 方法,而paint方法又把绘图任务交给了三个方法:paintComponent,paintBorder,和paintChildren。我们只需把需要个性定制的实现代码放在paintComponent方法里,在添加你的代码之前记得一定要调用super.paintComponent。在写自己的实现方法前一定要记住给自己留一条退路。什么退路?比如说我们前面提到的关闭文件方法,要实现一定的清理工作,等价于不在原组件里画图。这里我们可以这样实现:
@override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
/*Customize your paint plans here.*/
if(image!=null){ /*假设image就是需要显示的图片*/
/*your code to paint the image*/
}
}
一般情况下,我们不能直接调用paint方法,当需要更新显示内容时直接调用repaint。repaint 先完成一定的清理工作然后会调用paint,paint又调用paintComponent,就可以显示出你画的东西了。
要让MainPanel类获取图片,可以给它传一个ImageIcon,或图片文件或其它任何可以得到图片的东西。我们从JFileChooser中得到的是图片文件,直接把图片文件或者经转化为ImageIcon后作为参数传递给MainPanel。具体如下:
/*MainPanel.java*/
/*import every class needed here*/
public class MainPanel extends JPanel
{
protected File imgFile;
protected ImageIcon img;
public PaintImage()
{
/*add your code here*/
}
public void setImageFile(File newImgFile)
{
imgFile = newImgFile;
ImageIcon newImg = new ImageIcon(imgFile.getPath());
setImage(newImg);
}
public void setImage(ImageIcon newImg)
{
img = newImg;
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(img!=null){
Util.paintImg(this, g, img); /*paintImg 是类Util里的一个方法,用来画img*/ }
}
}
上述我们已经完成了MainPanel类的创建。下面写一个测试类,看看效果:
/*MainPanelTest.java*/
/*import needed classes here*/
public class MainPanelTest
{
private static final ImageIcon pigImg = new ImageIcon(“imgs/Pig.gif”);
public MainPanelTest(MainPanel mp)
{
JPanel panel = new JPanel();
JButton btn = new JButton(“Load image”);
panel.setLayout(new BorderLayout());
mp.setPreferredSize(new Dimension(350,350));
panel.add(mp);
btn.addActionListener(new ActionListener(){
public void actionPerformed(ActioneEvent e){
mp.setImage(pigImg);
}
});
panel.add(btn);
Util.run(this, null);
}
public static void main(String[] args)
{
new MainPanelTest(new MainPanel());
}
}
运行时点击按钮即可载入图片。这里再一次地用到了Util类,有关Util请参考Util.java。Part II 创建右侧面板类——SlidePane
SlidePane的显示内容是一个列表,我们可以从JList继承。JList通过ListSelectionModel
可以设置三种选择模式:MULTIPLE_INTERVAL_SELECTION,
SINGLE_INTERVAL_SELECTION,SINGLE_SELECTION。由于每次mainPanel里只能显示一张图片,故我们把它设置成单选模式(SINGLE_SELECTION)。getSelectionModel().setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
JList中每个元素的显示方式通过ListCellRenderer来管理。我们想要改变它的显示方式,变成显示图标,我们就必须通过设置ListCellRenderer来实现。
ListCellRenderer是一个接口,里面只有一个方法:
Component getListCellRendererComponent(JList extends E> list,
E value,
int index,
boolean isSelected,
boolean cellHasFocus)
list是JList中所要显示的元素,value是当前选择的元素,index是当前选择元素的序号。我们可以选择JLabel来实现这个方法,让list里放ImageIcon,然后把label的图标设置成value就可以了。
如:
public class ImageRenderer extends JLabel implements ListCellRenderer
{
public ImageRenderer()
{
setOpaque(true);
setHorizontalAlignment(SwingConstants.CENTER);
setVerticalAlignment(SwingConstants.CENTER);
}
public Component getListCellRendererComponent(JList extends E> list,
E value,
int index,
boolean isSelected,
boolean cellHasFocus) {
if(isSelected){
setBackground(list.getSelectionBackground());
setForeground(list.getSelectionForeground());
}
else{
setBackground(list.getBackground());
setForeground(list.getForeground());
}
setIcon(value);
return this;
}
}
最后要出现滑动条的效果还需要把SlidePane加到JSlidePane里,不需要设置SlidePane 的大小,否则SlidePane的可视区域就局限于设置的大小里,超出部分不能通过滚动滑动条看到。
Part III 创建BasePanel类
这里我们稍稍做一点改动,让按钮和面板标签中的显示内容想关联,让按钮来实现数组index 的移动。下面给出代码:
/*BasePanel.java*/
/*import needed classes here*/
public class BasePanel extends JPanel
{
JButton last;
JButton next;
private JLabel progress;
private int index;
private int length;
public BasePanel()
{
setLayout(new FlowLayout());
setBorder(new MatteBorder(1, 0, 0, 0, Color.black));
last = new JButton(“上一个”);
last.setActionCommand(https://www.360docs.net/doc/d93689393.html,ST_IMG);
next = new JButton(“下一个”);
next.setActionCommand(ActionCommand.NEXT_IMG);
progress = new JLabel(“/”);
setPreferredSize(new Dimension(600, 50));
add(last);
add(progress);
add(next);
}
public void setParam(int length, int index) throws IllegalParameterException
{
if(index<0||length<0||length throw new IllegalParameterException(); } this.length = length; setIndex(index); } public void next() { setIndex(index+1); } public void last() { setIndex(index-1); } public int getIndex() { return index; } public void setIndex(int index) { this.index = index; if(index<=0){ last.setEnabled(false); } else{ last.setEnabled(true); } if(index>=length-1){ next.setEnabled(false); } else{ next.setEnabled(true); } if(index==0&&length==0){ progress.setText(“/”); } else{ progress.setText((index+1)+” / ”+length); } } } 两个button的监听器在这里暂时不加,因为BasePanel、SlidePane和MainPanel三者之间要互相通信,在这里增加监听器只能实现对其自身的控制,不能改变别的面板的状态。故监听器的功能等到了写主程序的时候再一并实现。 Part IV 创建主程序 将前面几个容器都组合进来,实现一个menuBar,再写各类监听器,最后显示出来。 第一步:添加组件到面板 首先定义前面三个类的组件。 MainPanel mainPanel = new MainPanel(); SlidePane rightPanel = new SlidePane(); BasePanel basePanel = new BasePanel(); 然后把rightPanel加到JScrollPane 里, JScrollPane scrolledRightPanel = new JScrollPane(rightPanel); 再把mainPanel和scrolledRightPanel放到一个JSplitPane里, JSplitPane splitPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, mainPanel, scrolledRightPanel); 最后把splitPanel和basePanel装到一个容器中,容器使用BorderLayout排布。即: 这里主程序类继承自JPanel,其本身就是一个容器。 setLayout(new BorderLayout()); add(splitPanel, BorderLayout.CENTER); add(basePanel, BorderLayout.SOUTH); 第二步:创建menuBar 仅以一个为例: JMenuBar menuBar = new JMenuBar(); JMenu fileMenu = new JMenu(“File”); menuBar.add(fileMenu); JMenuItem fileOpenItem = new JMenuItem(“Open File”); fileMenu.add(fileOpenItem); 创建其它菜单或菜单项与此过程类似,不重复写了。 第三步:增加监听器 这一步最复杂,留在了最后作为压轴部分。首先让我们来盘点一下有哪些组件需要增加监听器,都需要增加什么类型的监听器。 按照从上到下,从左到右的顺序: 1. File菜单,Help菜单的各个菜单项。需要ActionListener。 2. mainPanel,当尺寸改变时,需要重新绘制,应增加ComponentListener。 3. rightPanel里,选择不同的文件时其它的面板需要更新。需要ListSelectionListener。 4. basePanel里两个按钮,需要ActionListener。 所有这些监听器的实现强烈建议使用内部类在主程序里实现,这样一来主程序中的变量可以被这些类访问,你便拥有了更大的灵活性。但是你可以发现有多个组件需要同一种监听器,当在一个组件上发生动作时,监听器就可以收到信号,但是它并不知道是谁发出的信号,这带来了一个问题。有两种解决方式。第一,getSource方法获取源,然后判断。这种方法针对类型单一的组件(比如都是JButton)比较好用,但是像我们这里的情况解决起来就不是很合适。第二,在增加监听器的同时调用当前组件的setActionCommand方法设置动作命令。当监听器收到触发信号后,调用getActionCommand,然后判断和哪个组件的相同。 下面增加监听器: openFileItem.addActionListener(this); openFileItem.setActionCommand(ActionCommand.OPEN_FILE); openFileItem.setAccelerator(HotKey.OPEN_FILE); …… mainPanel.addComponentListener(new ComponentAdapter(){ public void componentResized(ComponentEvent e) { mainPanel.repaint(); } }); /*ListSelectionListener在后面说*/ https://www.360docs.net/doc/d93689393.html,stImg.addActionListener(this); https://www.360docs.net/doc/d93689393.html,stImg.setActionCommand(https://www.360docs.net/doc/d93689393.html,ST_IMG); …… setActionCommand方法里用到的String类型的参数都使用定义在ActionCommand里的常量。菜单项设置了快捷键,按钮不支持。 主程序里实现ActionListener接口的方法如下: public void actionPerformed(ActionEvent e) { String command = getActionCommand(); if(command==null) return ; switch(command) { case ActionCommand. OPEN_FILE: doOpenFile(); case ActionCommand.CLOSE_FILE: doCloseFile(); …… case ActionCommand. LAST_IMG: showLastImg(); } } 以下细说doOpenFile(),doCloseFile()和showLastImg(),actionPerformed里其它都略了。doOpenFile() 点击Open File菜单,执行doOpenFile方法,首先应该弹出一个选择文件的对话框。定义一个JFileChooser,使用里面的showOpenDialog,调用该方法后得到一个返回值。通过返回值可以判断是否选择了文件,如果选择了文件就进一步处理。 首先可以用JFileChooser的getSelectedFile得到所选的文件curImageFile。由curImageFile又可以得到当前目录下的图片文件列表imgFileList。把imgFileList传递给rightPanel和basePanel里的两个按钮,因为打开文件后,不仅要在mainPanel里显示图片,而且右侧边栏和下方的控制/状态栏也会被激活。这两个组件需要的正是一个文件数组。调用MainPanel的setImageFile就可以显示当前图片。调用SlidePane的setListData就可以把要显示的文件加入到列表里了,但是我们前面约定的SlidePane里的元素类型是ImageIcon,这里不能直接提供File类型的元素列表,故转换后再调用setListData方法。针对我们前面写的BasePanel类,只需要向其传递一个图片数组的长度和当前选定图片在图片数组中的位置就可以了。所以doOpenFile就可以这样来写: /*in the field declaration*/ protected File curImageFile; protected File[] imgFiles; protected ImageIcon curImage; protected ImageIcon[] imgs; protected int index; …… void doOpenFile() { JFileChooser jc = new JFileChooser(); int retValue = jc.showOpenDialog(); if(retValue!=JFileChooser. APPROVE_OPTION){ /*specify your handle option*/ return; } /*here we deal with when choose approve option*/ curImageFile = jc.getSelectedFile(); imgFiles = curImgFile.getParentFile().listFiles(new FilenameFilter(){ public boolean accept(File dir, String name){ /*to filter out the image files. handle it yourself.*/ } }); imgs = new ImageIcon[imgFiles.length]; for(int i=0; i imgs[i] = new ImageIcon(imgFiles[i]); if(imgFiles[i].equals(curImageFile)){ index = i; } } curImage = imgs[index]; mainPanel.setImage(curImage); rightPanel.setListData(imgs); rightPanel.setSelectedIndex(index); try{ basePanel.setParam(imgs.length, index); }catch(IllegalParameterExcepton ex){ new RuntimeException(“illegal paramter”); } } doCloseFile() 这里要做的是模拟文件关闭以后三个面板上应该显示的东西,通过擦出是不明智的选择。在开始设计的时候就需要考虑到这一步了,现在来看看解决方法: MainPanel接受空的参数,故对它就这样写:mainPanel.setImage(null); RightPanel继承自JList,JList在调用setListData时不允许用null,如果这样会得到不可预期的错误(doc里是这样说的),所以我们还是要给它传一个数组,把数组的长度弄成零就可以达到目的,如下: rightPanel.setListData(new ImageIcon[] {}); {}里不包含任何元素,表明其长度为零。但是{}不能省,否则就是一个没有初始化的数组。而对于BasePanel,setParam方法参数输入错误也抛出异常,但是可能你已经看出来了,还是留下了一个空位,即setParam(0, 0);这样既不会抛出异常,又可以禁用两个按钮,只是标签上还有显示。 showLastImg() 有了前面的铺垫,这一方法实现起来就简单多了,首先改变index的值,再反映到相关联的其它两个面板里。 void showLastImg() { https://www.360docs.net/doc/d93689393.html,st(); rightPanel.setSelectedIndex(basePanel.getIndex()); mainPanel.setImage(rightPanel.getSelectedValue()); } 直接调用BasePanel类里的方法就可以解决问题。 大家注意一下,这里这几个方法前面都没有加public,是因为这些都只是为了增强代码的可读性刻意把一部分代码分离出来而增加的辅助方法,不需要别人调用,自己知道就可以了,所以不加。 到此ActionListener已经讲得差不多了,而ComponentListener在增加监听器的时候就用匿名内部类实现了,当检测到尺寸发生变化时就重画图片。最后来说说RightPanel的ListSelectionListener。 这个接口里一个方法, public void valueChanged(ListSelectionEvent e); 当list里的选项发生变化时,我们应该通知mainPanel和basePanel做出相应的调整。即:public void valueChanged(ListSelectionEvent e) { mainPanel.setImage(rightPanel.getSelectedValue()); basePanel.setIndex(rightPanel.getSelectedIndex()); } 到此整个程序已经将完了,点此下载源码。