经典代码收藏之——JList过滤

现在许多电子书都有这样的功能块:右侧导航视窗上部具有一个供输入的文本框,下部有个列表.当在上部输入某个字符时下面的列表会自动给出以输入框中字串为前缀的所有匹配字符.下面的代码就实现了类似的功能(仅显示前缀匹配的字串)

 代码不一定工作,但主要给出实现思路:JList中所显示的数据来源于其模型--与大多数的Swing组件一样.从上面的场景中可看出有这样的动作序列:1.文本框中输入字符

2.列表中动态过滤并显示;这是表面情况,当我们要实现这样的功能时就需要知道"变化"到底发生在那里:输入的字符,导致JTextField的模型中(Document)的数据发生变化(变化--是产生事件的原因,就是说任何事件都是因为某个东西变了才有的)其产生的事件需要通知给JList并显示过滤后的字符列表,所以JList可以被作为JTextField的监听器加入到JTextField的列表中.下面是用一个内部类FilterField实现监听逻辑的(紧凑的实现!).

 FilterModel即为JList的模型,不过它的内部维护了两份数据的容器(ArrayList用于放数据),一个items用于放原始数据,另一个filterItems用于放过滤后的数据,而最终显示的是过滤后的数据.这样的技巧很棒!不会改变原始数据,显示时只是原始数据的子集,当然这样的逻辑可成为一个技巧,

如:可用原始数据做为输入,产生一个排序后的数据,但并不改变原来的数据(看JTable的排序篇就有类似的思想).总结成这样的设计小模式:显示模型:=fun( const 原始数据模型)//fun表示某种变换const表示变换不会改变原始的数据模型.,这样可用不同的变换策略生成不同的显示模型来,使设计易于修改.当然可用定义一个接口来完成.可以基于下面的代码思想完成更复杂的过滤或排序功能.

import java.awt.BorderLayout;
import java.util.ArrayList;

import javax.swing.AbstractListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.ListModel;
import javax.swing.ScrollPaneConstants;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;

public class FilteredJList<E> extends JList {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	private FilterField filterField; // 输入用的文本框__为JTextfield的子类
	private int DEFAULT_FIELD_WIDTH = 20; // 最多输入20个字符

	public FilteredJList() {
		super();
		setModel(new FilterModel<E>());
		filterField = new FilterField(DEFAULT_FIELD_WIDTH);
	}
	


	public void setModel(ListModel m) {
		if (!(m instanceof FilterModel)) // 仅仅能以FielterModel作为JList的模型
			throw new IllegalArgumentException();
		super.setModel(m);
	}
	
	private FilterModel<E> getFModel() {
		return (FilterModel<E>) getModel();
	}

	public void addItem(E o) {
		getFModel().addElement(o);
	}

	public JTextField getFilterField() {
		return filterField;
	}

	class FilterModel<E> extends AbstractListModel {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;
		ArrayList<E>  items; // 原始数据
		ArrayList<E>  filterItems;// 过滤后的显示数据

		public FilterModel() {
			super();
			items = new ArrayList<E>();
			filterItems = new ArrayList<E>();
		}

		public E getElementAt(int index) {
			if (index < filterItems.size())
				return filterItems.get(index);
			else
				return null;
		}

		public int getSize() {
			return filterItems.size();
		}

		public void addElement(E o) {
			items.add(o);
			refilter();
		}

		private void refilter() { //完成过滤逻辑的函数
			 // public void refilter() {
	          filterItems.clear(); //清空用于显示的数据容器
	          String term = getFilterField().getText(); //获取输入的字串
	          for (int i=0; i<items.size(); i++)    //遍历原始数据容器列表
	          {
	        	  if (items.get(i).toString().indexOf(term, 0) != -1)
	        		  filterItems.add (items.get(i));        //如果满足条件被放入到过滤后的数据容器中用于显示
	          }
	          fireContentsChanged(this, 0, getSize());  //通知JList的视图更新显示数据源自filterItems
              //它会产生ListDataEvent 事件给 JList JList重新从模型中获取数据并重绘(repaint)自己.
        }
	}

	// FilterField inner class listed below
	// inner class provides filter-by-keystroke field
	class FilterField extends JTextField implements DocumentListener {
		/**
		 * 
		 */
		private static final long serialVersionUID = 1L;

		// 在文本框中的增 删 改 都会改变其模型中的数据并激发DocumentEvent事 .在此事件发生时
		// 唯一要做的就是对JList的FilterModel重新进行过滤,过滤后JList就会显示过滤后的数据
		public FilterField(int width) {
			super(width);
			getDocument().addDocumentListener(this);
		}

		public void changedUpdate(DocumentEvent e) {
			getFModel().refilter();

		}

		public void insertUpdate(DocumentEvent e) {
			getFModel().refilter();
		}

		public void removeUpdate(DocumentEvent e) {
			getFModel().refilter();
		}


	}

	public static void main(String[] args) {
		String[] listItems = { "Chris", "Joshua", "Daniel", "Michael", "Don",
				"Kimi", "Kelly", "Keagan" };
		JFrame frame = new JFrame("FilteredJList");
		frame.getContentPane().setLayout(new BorderLayout());
		// populate list
		FilteredJList<String> list = new FilteredJList<String>();
		for (int i = 0; i < listItems.length; i++)
			list.addItem(listItems[i]);
		// add to gui
		JScrollPane pane = new JScrollPane(list,
				ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS,
				ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
		frame.getContentPane().add(pane, BorderLayout.CENTER);
		frame.getContentPane().add(list.getFilterField(), BorderLayout.NORTH);
		frame.pack();
		frame.setVisible(true);
	}
}


posted on 2013-05-24 10:34  Java码界探秘  阅读(140)  评论(0编辑  收藏  举报

导航