[英]How to identify a direct click on a JCheckBox in a JTable?
使用JCheckBox作為JTable列中的編輯器,我想忽略TableCell中CheckBox左右兩側的鼠標點擊。
我在2011年的Oracle論壇上找到了一個討論,但問題沒有在那里解決: https : //community.oracle.com/thread/2183210
這是我到目前為止所實現的hack,有趣的部分從class CheckBoxEditor
開始:
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.DefaultCellEditor;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
/**
* Trying to set the Checkbox only if clicked directly on the box of the CheckBox. And ignore clicks on the
* remaining space of the TableCell.
*
* @author bobndrew
*/
public class JustCheckOnCheckboxTable extends JPanel
{
private static final int CHECK_COL = 1;
private static final Object[][] DATA = { { "One", Boolean.TRUE }, { "Two", Boolean.FALSE },
{ "Three", Boolean.TRUE }, { "Four", Boolean.FALSE }, { "Five", Boolean.TRUE },
{ "Six", Boolean.FALSE }, { "Seven", Boolean.TRUE }, { "Eight", Boolean.FALSE },
{ "Nine", Boolean.TRUE }, { "Ten", Boolean.FALSE } };
private static final String[] COLUMNS = { "Number", "CheckBox" };
private final DataModel dataModel = new DataModel( DATA, COLUMNS );
private final JTable table = new JTable( dataModel );
public JustCheckOnCheckboxTable()
{
super( new BorderLayout() );
this.add( new JScrollPane( table ) );
table.setRowHeight( table.getRowHeight() * 2 );
table.setPreferredScrollableViewportSize( new Dimension( 250, 400 ) );
TableColumn checkboxColumn = table.getColumnModel().getColumn( 1 );
checkboxColumn.setCellEditor( new CheckBoxEditor() );
}
private class DataModel extends DefaultTableModel
{
public DataModel( Object[][] data, Object[] columnNames )
{
super( data, columnNames );
}
@Override
public Class<?> getColumnClass( int columnIndex )
{
if ( columnIndex == 1 )
{
return getValueAt( 0, CHECK_COL ).getClass();
}
return super.getColumnClass( columnIndex );
}
}
class CheckBoxEditor extends DefaultCellEditor
{
private final JCheckBox checkBox;
public CheckBoxEditor()
{
super( new JCheckBox() );
checkBox = (JCheckBox) getComponent();
checkBox.setHorizontalAlignment( JCheckBox.CENTER );
System.out.println( "the checkbox has no size: " + checkBox.getSize() );
}
@Override
public boolean shouldSelectCell( final EventObject anEvent )
{
System.out.println( "\nthe checkbox fills the TableCell: " + checkBox.getSize() );
//Throws NullPointerException: System.out.println( checkBox.getIcon().getIconWidth() );
System.out.println( "always JTable :-( " + anEvent.getSource() );
MouseEvent ev =
SwingUtilities.convertMouseEvent( ((ComponentEvent) anEvent).getComponent(), (MouseEvent) anEvent,
getComponent() );
System.out.println( "Position clicked in TableCell: " + ev.getPoint() );
System.out.println( "always JCheckBox :-( " + getComponent().getComponentAt( ev.getPoint() ) );
Point middleOfTableCell = new Point( checkBox.getWidth() / 2, checkBox.getHeight() / 2 );
System.out.println( "middleOfTableCell: " + middleOfTableCell );
Dimension preferredSizeOfCheckBox = checkBox.getPreferredSize();
int halfWidthOfClickArea = (int) (preferredSizeOfCheckBox.getWidth() / 2);
int halfHeightOfClickArea = (int) (preferredSizeOfCheckBox.getHeight() / 2);
if ( (middleOfTableCell.getX() - halfWidthOfClickArea > ev.getX() || middleOfTableCell.getX() + halfWidthOfClickArea < ev.getX())
|| (middleOfTableCell.getY() - halfHeightOfClickArea > ev.getY() || middleOfTableCell.getY() + halfHeightOfClickArea < ev.getY()) )
{
stopCellEditing();
}
return super.shouldSelectCell( anEvent );
}
}
private static void createAndShowUI()
{
JFrame frame = new JFrame( "Direct click on CheckBox" );
frame.add( new JustCheckOnCheckboxTable() );
frame.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible( true );
}
public static void main( String[] args )
{
java.awt.EventQueue.invokeLater( new Runnable()
{
@Override
public void run()
{
createAndShowUI();
}
} );
}
}
我喜歡這個解決方案:
我不喜歡它:
int halfWidthOfClickArea
)
或者有更好的方法來實現此表和CheckBox行為?
編輯:
我根據camickr的建議更改了源代碼,並為具有更高RowHeights的表添加了垂直hitzone。
但到目前為止,我忘了提到我的問題的主要原因...... ;-)
我在方法shouldSelectCell(..)
調用stopCellEditing()
shouldSelectCell(..)
。
是否可以決定更多的細胞選擇?
我在哪里可以獲得未上漆組件的尺寸?
System.out.println(checkBox.getPreferredSize() );
我認為在shouldSelectCell()
方法中停止選擇實際是一種迂回方法,並且轉換鼠標事件似乎很奇怪。
相反,更簡潔的方法是使復選框不填充整個單元格,這樣只有直接按下它的“復選框”部分才能選中它。
這種行為可以通過將JCheckbox
放在JPanel
並在不拉伸它的情況下居中來實現。 為此,我們可以使JPanel
的布局管理器成為GridBagLayout
。 看看當使用GridBagLayout
,內部內容不會拉伸:
(來自StackOverflow的答案 )
所以現在,如果你點擊它周圍的空白區域,你將點擊一個JPanel
,這樣你就不會改變JCheckBox
的值。
CheckBoxEditor
的代碼最終結果如下:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
componentPanel.setOpaque(false);
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
@Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
if (isSelected) {
setForeground(table.getSelectionForeground());
setBackground(table.getSelectionBackground());
} else {
setForeground(table.getForeground());
setBackground(table.getBackground());
}
return componentPanel;
}
}
(請注意,您不能再從DefaultCellEditor
擴展 - 在上面的代碼中,您現在必須從AbstractCellEditor
擴展並實現TableCellEditor
)。
我認為這個版本的CheckBoxEditor
您的需求 - 如果您點擊復選框周圍的空白區域,則沒有任何反應。 如果您直接單擊該復選框,則僅會檢查該復選框。
此外,通過使用JPanel
,您不必執行任何MouseEvent
計算(至少對我而言),代碼看起來更清晰,並且更容易看到正在發生的事情。
EDIT1:
我讀了你的評論,我找到了一個解決方案:為什么不保留編輯器 ,但是只需要創建一個派生自DefaultTableCellRenderer
的單元格渲染器 ? 然后,在CheckBoxEditor
使用與渲染器相同的邊框和背景。
這應該達到你想要的效果(我已經將公共代碼移動到外部類方法中,所以我不必重復它們):
private static void setCheckboxValue(JCheckBox checkBox, Object value) {
if (value instanceof Boolean) {
checkBox.setSelected(((Boolean) value).booleanValue());
} else if (value instanceof String) {
checkBox.setSelected(value.equals("true"));
}
}
private static void copyAppearanceFrom(JPanel to, Component from) {
if (from != null) {
to.setOpaque(true);
to.setBackground(from.getBackground());
if (from instanceof JComponent) {
to.setBorder(((JComponent) from).getBorder());
}
} else {
to.setOpaque(false);
}
}
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxEditor() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
@Override
public Object getCellEditorValue() {
return Boolean.valueOf(checkBox.isSelected());
}
@Override
public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
setCheckboxValue(checkBox, value);
TableCellRenderer renderer = table.getCellRenderer(row, column);
Component c = renderer.getTableCellRendererComponent(table, value, true, true, row, column);
copyAppearanceFrom(componentPanel, c);
return componentPanel;
}
}
class CheckBoxRenderer extends DefaultTableCellRenderer {
private static final long serialVersionUID = 1L;
private final JPanel componentPanel;
private final JCheckBox checkBox;
public CheckBoxRenderer() {
componentPanel = new JPanel(new GridBagLayout()); // Use GridBagLayout to center the checkbox
checkBox = new JCheckBox();
checkBox.setOpaque(false);
componentPanel.add(checkBox);
}
@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
setCheckboxValue(checkBox, value);
copyAppearanceFrom(componentPanel, this);
return componentPanel;
}
}
然后,您必須在構造函數中將渲染器與編輯器一起設置:
checkboxColumn.setCellEditor(new CheckBoxEditor());
checkboxColumn.setCellRenderer(new CheckBoxRenderer());
這里有兩個比較兩種方法的截圖:
你原來的方法: JPanel
和 JCheckBox
方法:
我幾乎看不出差別:)
恕我直言,我仍然認為只使用普通的Java表格API比計算基於鼠標指針位置的檢查更簡潔,但選擇取決於你。
我希望這有幫助!
EDIT2:
此外,如果您希望能夠使用空格鍵切換,我認為您只需在CheckBoxEditor
構造函數中添加一個鍵綁定到componentPanel
:
class CheckBoxEditor extends AbstractCellEditor implements TableCellEditor {
// ...
public CheckBoxEditor() {
// ...
componentPanel.getInputMap().put(KeyStroke.getKeyStroke("SPACE"), "spacePressed");
componentPanel.getActionMap().put("spacePressed", new AbstractAction() {
private static final long serialVersionUID = 1L;
@Override
public void actionPerformed(ActionEvent e) {
checkBox.setSelected(!checkBox.isSelected());
}
});
// ...
}
// ...
}
我不確定你是否可以使用布爾值拖放。 我嘗試將“true”拖到原始版本的復選框中,但沒有任何反應,所以我認為你不必擔心DnD。
您的代碼有錯誤。 嘗試按單元格編輯器的復選框,拖動單元格外部並釋放鼠標按鈕。 然后單擊復選框外的某處。 正在編輯單元格。 我想這意味着shouldSelectCell
對你來說不是正確的解決方案。
我認為您應該考慮禁用整個列的單元格編輯器,並在JTable
上實現自定義MouseAdapter
來計算復選框的位置並更改模型本身。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.