Java Swing實(shí)現(xiàn)JTable檢測(cè)單元格數(shù)據(jù)變更事件的方法示例
本文實(shí)例講述了Java Swing實(shí)現(xiàn)JTable檢測(cè)單元格數(shù)據(jù)變更事件的方法。分享給大家供大家參考,具體如下:
在JTable的初級(jí)教程中往往會(huì)提到,使用TableModel的 addTableModelListener方法可以監(jiān)聽單元格數(shù)據(jù)的變更,在其事件處理函,數(shù)tableChanged中,可以通過e.getColumn(),e.getFirstRow(),e.getLastRow(),e.getType()來獲取變更發(fā)生的位置和變更的類型(插入、更新或刪除)。然而該方法存在2個(gè)致命的問題:
1.雙擊單元格使其處于可編輯狀態(tài)后,即使沒有做出任何修改,當(dāng)單元格失去焦點(diǎn)時(shí),該事件將被激活。
2.通過該事件你可以獲取單元格最新的數(shù)據(jù),卻無法獲取原有數(shù)據(jù)。
經(jīng)過一番搜索發(fā)現(xiàn)該文章已經(jīng)解決了這個(gè)問題Table Cell Listener,作者自己實(shí)現(xiàn)了一個(gè)單元格監(jiān)聽器TableCellListener,它訂閱了指定table的addPropertyChangeListener,根據(jù)e.getPropertyName()來識(shí)別單元格編輯事件,根據(jù)table.isEditing()方法來判斷單元格正在編輯還是編輯完畢。如果是正在編輯,則記錄單元格位置和原因數(shù)據(jù);如果已經(jīng)編輯完畢,則記錄新數(shù)據(jù)并與原有數(shù)據(jù)進(jìn)行比對(duì),如果不一致則說明單元格數(shù)據(jù)發(fā)生了變更,則激活指定響應(yīng)函數(shù)。
測(cè)試用例如下:
TableDemo.java
/*
* Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle or the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package awtDemo;
/*
* TableDemo.java requires no other files.
*/
import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.table.AbstractTableModel;
import java.awt.Dimension;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
/**
* TableDemo is just like SimpleTableDemo, except that it
* uses a custom TableModel.
*/
@SuppressWarnings("serial")
public class TableDemo extends JPanel {
private boolean DEBUG = false;
@SuppressWarnings("unused")
public TableDemo() {
super(new GridLayout(1,0));
JTable table = new JTable(new MyTableModel());
table.setPreferredScrollableViewportSize(new Dimension(500, 70));
table.setFillsViewportHeight(true);
//Create the scroll pane and add the table to it.
JScrollPane scrollPane = new JScrollPane(table);
//Add the scroll pane to this panel.
add(scrollPane);
Action action = new AbstractAction()
{
public void actionPerformed(ActionEvent e)
{
TableCellListener tcl = (TableCellListener)e.getSource();
System.out.printf("cell changed%n");
System.out.println("Row : " + tcl.getRow());
System.out.println("Column: " + tcl.getColumn());
System.out.println("Old : " + tcl.getOldValue());
System.out.println("New : " + tcl.getNewValue());
}
};
TableCellListener tcl = new TableCellListener(table, action);
}
class MyTableModel extends AbstractTableModel {
private String[] columnNames = {"First Name",
"Last Name",
"Sport",
"# of Years",
"Vegetarian"};
private Object[][] data = {
{"Kathy", "Smith",
"Snowboarding", new Integer(5), new Boolean(false)},
{"John", "Doe",
"Rowing", new Integer(3), new Boolean(true)},
{"Sue", "Black",
"Knitting", new Integer(2), new Boolean(false)},
{"Jane", "White",
"Speed reading", new Integer(20), new Boolean(true)},
{"Joe", "Brown",
"Pool", new Integer(10), new Boolean(false)}
};
public int getColumnCount() {
return columnNames.length;
}
public int getRowCount() {
return data.length;
}
public String getColumnName(int col) {
return columnNames[col];
}
public Object getValueAt(int row, int col) {
return data[row][col];
}
/*
* JTable uses this method to determine the default renderer/
* editor for each cell. If we didn't implement this method,
* then the last column would contain text ("true"/"false"),
* rather than a check box.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public Class getColumnClass(int c) {
return getValueAt(0, c).getClass();
}
/*
* Don't need to implement this method unless your table's
* editable.
*/
public boolean isCellEditable(int row, int col) {
//Note that the data/cell address is constant,
//no matter where the cell appears onscreen.
if (col < 2) {
return false;
} else {
return true;
}
}
/*
* Don't need to implement this method unless your table's
* data can change.
*/
public void setValueAt(Object value, int row, int col) {
if (DEBUG) {
System.out.println("Setting value at " + row + "," + col
+ " to " + value
+ " (an instance of "
+ value.getClass() + ")");
}
data[row][col] = value;
fireTableCellUpdated(row, col);
if (DEBUG) {
System.out.println("New value of data:");
printDebugData();
}
}
private void printDebugData() {
int numRows = getRowCount();
int numCols = getColumnCount();
for (int i=0; i < numRows; i++) {
System.out.print(" row " + i + ":");
for (int j=0; j < numCols; j++) {
System.out.print(" " + data[i][j]);
}
System.out.println();
}
System.out.println("--------------------------");
}
}
/**
* Create the GUI and show it. For thread safety,
* this method should be invoked from the
* event-dispatching thread.
*/
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("TableDemo - www.dhdzp.com");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Create and set up the content pane.
TableDemo newContentPane = new TableDemo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
//Display the window.
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
//Schedule a job for the event-dispatching thread:
//creating and showing this application's GUI.
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
TableCellListener.java
package awtDemo;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
/*
* This class listens for changes made to the data in the table via the
* TableCellEditor. When editing is started, the value of the cell is saved
* When editing is stopped the new value is saved. When the oold and new
* values are different, then the provided Action is invoked.
*
* The source of the Action is a TableCellListener instance.
*/
public class TableCellListener implements PropertyChangeListener, Runnable
{
private JTable table;
private Action action;
private int row;
private int column;
private Object oldValue;
private Object newValue;
/**
* Create a TableCellListener.
*
* @param table the table to be monitored for data changes
* @param action the Action to invoke when cell data is changed
*/
public TableCellListener(JTable table, Action action)
{
this.table = table;
this.action = action;
this.table.addPropertyChangeListener( this );
}
/**
* Create a TableCellListener with a copy of all the data relevant to
* the change of data for a given cell.
*
* @param row the row of the changed cell
* @param column the column of the changed cell
* @param oldValue the old data of the changed cell
* @param newValue the new data of the changed cell
*/
private TableCellListener(JTable table, int row, int column, Object oldValue, Object newValue)
{
this.table = table;
this.row = row;
this.column = column;
this.oldValue = oldValue;
this.newValue = newValue;
}
/**
* Get the column that was last edited
*
* @return the column that was edited
*/
public int getColumn()
{
return column;
}
/**
* Get the new value in the cell
*
* @return the new value in the cell
*/
public Object getNewValue()
{
return newValue;
}
/**
* Get the old value of the cell
*
* @return the old value of the cell
*/
public Object getOldValue()
{
return oldValue;
}
/**
* Get the row that was last edited
*
* @return the row that was edited
*/
public int getRow()
{
return row;
}
/**
* Get the table of the cell that was changed
*
* @return the table of the cell that was changed
*/
public JTable getTable()
{
return table;
}
//
// Implement the PropertyChangeListener interface
//
@Override
public void propertyChange(PropertyChangeEvent e)
{
// A cell has started/stopped editing
if ("tableCellEditor".equals(e.getPropertyName()))
{
if (table.isEditing()){
//System.out.printf("tableCellEditor is editing..%n");
processEditingStarted();
}
else{
//System.out.printf("tableCellEditor editing stopped..%n");
processEditingStopped();
}
}
}
/*
* Save information of the cell about to be edited
*/
private void processEditingStarted()
{
// The invokeLater is necessary because the editing row and editing
// column of the table have not been set when the "tableCellEditor"
// PropertyChangeEvent is fired.
// This results in the "run" method being invoked
SwingUtilities.invokeLater( this );
}
/*
* See above.
*/
@Override
public void run()
{
row = table.convertRowIndexToModel( table.getEditingRow() );
column = table.convertColumnIndexToModel( table.getEditingColumn() );
oldValue = table.getModel().getValueAt(row, column);
//這里應(yīng)對(duì)oldValue為null的情況做處理,否則將導(dǎo)致原值與新值均為空時(shí)仍被視為值改變
if(oldValue == null)
oldValue = "";
newValue = null;
}
/*
* Update the Cell history when necessary
*/
private void processEditingStopped()
{
newValue = table.getModel().getValueAt(row, column);
//這里應(yīng)對(duì)newValue為null的情況做處理,否則后面會(huì)拋出異常
if(newValue == null)
newValue = "";
// The data has changed, invoke the supplied Action
if (! newValue.equals(oldValue))
{
// Make a copy of the data in case another cell starts editing
// while processing this change
TableCellListener tcl = new TableCellListener(
getTable(), getRow(), getColumn(), getOldValue(), getNewValue());
ActionEvent event = new ActionEvent(
tcl,
ActionEvent.ACTION_PERFORMED,
"");
action.actionPerformed(event);
}
}
}
運(yùn)行效果:

由圖可見,單元格數(shù)據(jù)修改后,控制臺(tái)輸出內(nèi)容變更信息!
更多關(guān)于java相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Java數(shù)據(jù)結(jié)構(gòu)與算法教程》、《Java字符與字符串操作技巧總結(jié)》、《Java操作DOM節(jié)點(diǎn)技巧總結(jié)》、《Java文件與目錄操作技巧匯總》和《Java緩存操作技巧匯總》
希望本文所述對(duì)大家java程序設(shè)計(jì)有所幫助。
- Java Swing窗體關(guān)閉事件的調(diào)用關(guān)系
- Java Swing中JList選擇事件監(jiān)聽器ListSelectionListener用法示例
- JavaSwing基礎(chǔ)之Layout布局相關(guān)知識(shí)詳解
- Java Swing實(shí)現(xiàn)坦克大戰(zhàn)游戲
- Java Swing最詳細(xì)基礎(chǔ)知識(shí)總結(jié)
- Java實(shí)戰(zhàn)之基于swing的QQ郵件收發(fā)功能實(shí)現(xiàn)
- Eclipse+Java+Swing實(shí)現(xiàn)學(xué)生成績(jī)管理系統(tǒng)的實(shí)例代碼
- Java Swing 只關(guān)閉當(dāng)前窗體的實(shí)現(xiàn)
- java swing 創(chuàng)建一個(gè)簡(jiǎn)單的QQ界面教程
- Java基礎(chǔ)學(xué)習(xí)之Swing事件監(jiān)聽
相關(guān)文章
Java數(shù)據(jù)結(jié)構(gòu)之有效隊(duì)列定義與用法示例
這篇文章主要介紹了Java數(shù)據(jù)結(jié)構(gòu)之有效隊(duì)列定義與用法,結(jié)合實(shí)例形式分析了java有效隊(duì)列的數(shù)據(jù)插入、刪除、判斷、計(jì)算等相關(guān)操作技巧,需要的朋友可以參考下2017-10-10
SpringBoot啟動(dòng)后啟動(dòng)內(nèi)嵌瀏覽器的方法
這篇文章主要介紹了SpringBoot啟動(dòng)后啟動(dòng)內(nèi)嵌瀏覽器的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
Java中的Monad設(shè)計(jì)模式及其實(shí)現(xiàn)過程
本文介紹了Java中的Monad設(shè)計(jì)模式及其在函數(shù)式編程中的應(yīng)用,雖然Java不是函數(shù)式編程語言,但可以通過接口和泛型模擬Monad的行為,實(shí)現(xiàn)鏈?zhǔn)秸{(diào)用和上下文管理,通過一個(gè)示例展示了如何使用OptionalMonad進(jìn)行鏈?zhǔn)秸{(diào)用,并解析了Monad接口和OptionalMonad的實(shí)現(xiàn)細(xì)節(jié)2025-03-03
SpringBoot項(xiàng)目中自定義Banner的技術(shù)指南
在 Spring Boot 項(xiàng)目中,當(dāng)應(yīng)用啟動(dòng)時(shí)會(huì)顯示默認(rèn)的 Spring 標(biāo)志和版本信息,定制化的啟動(dòng) Banner 不僅可以美化應(yīng)用,甚至可以提供一些關(guān)鍵信息,本文將介紹如何在 Spring Boot 項(xiàng)目中自定義啟動(dòng) Banner,需要的朋友可以參考下2025-03-03
如何利用Vue+SpringBoot實(shí)現(xiàn)評(píng)論功能
簡(jiǎn)單的評(píng)論功能是指能夠在文章底下進(jìn)行評(píng)論,而且能夠?qū)υu(píng)論進(jìn)行回復(fù),下面這篇文章主要給大家介紹了關(guān)于如何利用Vue+SpringBoot實(shí)現(xiàn)評(píng)論功能的相關(guān)資料,需要的朋友可以參考下2023-06-06
Spring實(shí)戰(zhàn)之搜索Bean類操作示例
這篇文章主要介紹了Spring實(shí)戰(zhàn)之搜索Bean類操作,結(jié)合實(shí)例形式分析了Spring搜索Bean類的相關(guān)配置、接口實(shí)現(xiàn)與操作技巧,需要的朋友可以參考下2019-12-12

