jjzjj

java - 在 JavaFX 中单击可编辑 TableView 单元格外部时如何提交?

coder 2023-08-30 原文

我有一个表格单元格工厂,负责在 JavaFX TableView 中创建一个可编辑的单元格。

我正在尝试为 tableview 实现一些附加功能,以便当用户在可编辑单元格外单击时进行提交(保存编辑的文本,而不是按照默认的 tableview 行为丢弃。)

我添加了一个 textField.focusedProperty() 事件处理程序,我在其中提交文本字段中的文本。但是,当在当前单元格外单击时,将调用 cancelEdit() 并调用 commitEdit(textField.getText()); 无效。

我已经意识到,一旦 cancelEdit() 被调用,TableCell.isEditing() 就会返回 false,因此提交永远不会发生。

我怎样才能让用户在可编辑单元格外单击时提交文本?

在提交 setOnEditCommit() 事件处理程序后,将负责验证和数据库逻辑。我没有在此处包含它,因为它很可能会使事情变得更加复杂。

// EditingCell - for editing capability in a TableCell
public static class EditingCell extends TableCell<Person, String> {
private TextField textField;

public EditingCell() {
}

@Override public void startEdit() {
    super.startEdit();

    if (textField == null) {
        createTextField();
    }
    setText(null);
    setGraphic(textField);
    textField.selectAll();
}

@Override public void cancelEdit() {
    super.cancelEdit();
    setText((String) getItem());
    setGraphic(null);
}

@Override public void updateItem(String item, boolean empty) {
    super.updateItem(item, empty);
    if (empty) {
        setText(null);
        setGraphic(null);
    } else {
        if (isEditing()) {
            if (textField != null) {
                textField.setText(getString());
            }
            setText(null);
            setGraphic(textField);
        } else {
            setText(getString());
            setGraphic(null);
        }
    }
}

private void createTextField() {
    textField = new TextField(getString());
    textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
    textField.setOnKeyReleased(new EventHandler<KeyEvent>() {                
        @Override public void handle(KeyEvent t) {
            if (t.getCode() == KeyCode.ENTER) {
                commitEdit(textField.getText());
            } else if (t.getCode() == KeyCode.ESCAPE) {
                cancelEdit();
            }
        }
    });

    textField.focusedProperty().addListener(new ChangeListener<Boolean>() {
         @Override
         public void changed(ObservableValue<? extends Boolean> observable, Boolean oldValue, Boolean newValue) {
             if (!newValue) {
                    commitEdit(textField.getText());
             }
         }
    });
}

private String getString() {
    return getItem() == null ? "" : getItem().toString();
}
} 

最佳答案

由于我找不到 kuaw26 的源代码(死链接),我为 java 8 开发了自己的解决方案。我发现上面代码中的 TextField 从未收到 esc-key 的 keyReleased 事件,因此他的代码没有工作。

不幸的是,我需要从 TextFieldTableCell 和 CellUtils 复制代码并对其进行调整,因为 TextFieldTableCell 使用私有(private) TextField,而 CellUtils 是受包保护的。这可能不是最好的 OO 方式。

这是我的解决方案:

// package yourLib;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.event.Event;
import javafx.scene.control.Label;
import javafx.scene.control.TableCell;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableColumn.CellEditEvent;
import javafx.scene.control.TablePosition;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javafx.util.converter.DefaultStringConverter;

/**
 * A class containing a {@link TableCell} implementation that draws a 
 * {@link TextField} node inside the cell. If the TextField is
 * left, the value is commited.
 * 
 */

public class AcceptOnExitTableCell<S,T> extends TableCell<S,T> {

/***************************************************************************
 *                                                                         *
 * Static cell factories                                                   *
 *                                                                         *
 **************************************************************************/

/**
 * Provides a {@link TextField} that allows editing of the cell content when
 * the cell is double-clicked, or when 
 * {@link TableView#edit(int, javafx.scene.control.TableColumn)} is called. 
 * This method will only  work on {@link TableColumn} instances which are of
 * type String.
 * 
 * @return A {@link Callback} that can be inserted into the 
 *      {@link TableColumn#cellFactoryProperty() cell factory property} of a 
 *      TableColumn, that enables textual editing of the content.
 */
public static <S> Callback<TableColumn<S,String>, TableCell<S,String>> forTableColumn() {
    return forTableColumn(new DefaultStringConverter());
}

/**
 * Provides a {@link TextField} that allows editing of the cell content when
 * the cell is double-clicked, or when 
 * {@link TableView#edit(int, javafx.scene.control.TableColumn) } is called. 
 * This method will work  on any {@link TableColumn} instance, regardless of 
 * its generic type. However, to enable this, a {@link StringConverter} must 
 * be provided that will convert the given String (from what the user typed 
 * in) into an instance of type T. This item will then be passed along to the 
 * {@link TableColumn#onEditCommitProperty()} callback.
 * 
 * @param converter A {@link StringConverter} that can convert the given String 
 *      (from what the user typed in) into an instance of type T.
 * @return A {@link Callback} that can be inserted into the 
 *      {@link TableColumn#cellFactoryProperty() cell factory property} of a 
 *      TableColumn, that enables textual editing of the content.
 */
public static <S,T> Callback<TableColumn<S,T>, TableCell<S,T>> forTableColumn(
        final StringConverter<T> converter) {
    return list -> new AcceptOnExitTableCell<S,T>(converter);
}


/***************************************************************************
 *                                                                         *
 * Fields                                                                  *
 *                                                                         *
 **************************************************************************/    

private TextField textField;
private boolean escapePressed=false;
private TablePosition<S, ?> tablePos=null;


/***************************************************************************
 *                                                                         *
 * Constructors                                                            *
 *                                                                         *
 **************************************************************************/

/**
 * Creates a default TextFieldTableCell with a null converter. Without a 
 * {@link StringConverter} specified, this cell will not be able to accept
 * input from the TextField (as it will not know how to convert this back
 * to the domain object). It is therefore strongly encouraged to not use
 * this constructor unless you intend to set the converter separately.
 */
public AcceptOnExitTableCell() { 
    this(null);
} 

/**
 * Creates a TextFieldTableCell that provides a {@link TextField} when put 
 * into editing mode that allows editing of the cell content. This method 
 * will work on any TableColumn instance, regardless of its generic type. 
 * However, to enable this, a {@link StringConverter} must be provided that 
 * will convert the given String (from what the user typed in) into an 
 * instance of type T. This item will then be passed along to the 
 * {@link TableColumn#onEditCommitProperty()} callback.
 * 
 * @param converter A {@link StringConverter converter} that can convert 
 *      the given String (from what the user typed in) into an instance of 
 *      type T.
 */
public AcceptOnExitTableCell(StringConverter<T> converter) {
    this.getStyleClass().add("text-field-table-cell");
    setConverter(converter);
}



/***************************************************************************
 *                                                                         *
 * Properties                                                              *
 *                                                                         *
 **************************************************************************/

// --- converter
private ObjectProperty<StringConverter<T>> converter = 
        new SimpleObjectProperty<StringConverter<T>>(this, "converter");

/**
 * The {@link StringConverter} property.
 */
public final ObjectProperty<StringConverter<T>> converterProperty() { 
    return converter; 
}

/** 
 * Sets the {@link StringConverter} to be used in this cell.
 */
public final void setConverter(StringConverter<T> value) { 
    converterProperty().set(value); 
}

/**
 * Returns the {@link StringConverter} used in this cell.
 */
public final StringConverter<T> getConverter() { 
    return converterProperty().get(); 
}  



/***************************************************************************
 *                                                                         *
 * Public API                                                              *
 *                                                                         *
 **************************************************************************/

/** {@inheritDoc} */
@Override public void startEdit() {
    if (! isEditable() 
            || ! getTableView().isEditable() 
            || ! getTableColumn().isEditable()) {
        return;
    }
    super.startEdit();

    if (isEditing()) {
        if (textField == null) {
            textField = getTextField(); 
        }
        escapePressed=false;
        startEdit(textField);
        final TableView<S> table = getTableView();
        tablePos=table.getEditingCell();
    }
}

/** {@inheritDoc} */
@Override public void commitEdit(T newValue) {
    if (! isEditing()) 
        return;

    final TableView<S> table = getTableView();
    if (table != null) {
        // Inform the TableView of the edit being ready to be committed.
        CellEditEvent editEvent = new CellEditEvent(
            table,
            tablePos,
            TableColumn.editCommitEvent(),
            newValue
        );

        Event.fireEvent(getTableColumn(), editEvent);
    }

    // we need to setEditing(false):
   super.cancelEdit(); // this fires an invalid EditCancelEvent.

    // update the item within this cell, so that it represents the new value
    updateItem(newValue, false);

    if (table != null) {
        // reset the editing cell on the TableView
        table.edit(-1, null);

        // request focus back onto the table, only if the current focus
        // owner has the table as a parent (otherwise the user might have
        // clicked out of the table entirely and given focus to something else.
        // It would be rude of us to request it back again.
       // requestFocusOnControlOnlyIfCurrentFocusOwnerIsChild(table);
    }
}


/** {@inheritDoc} */
@Override public void cancelEdit() {
    if(escapePressed) {
        // this is a cancel event after escape key
        super.cancelEdit();
        setText(getItemText()); // restore the original text in the view
    }
    else {
        // this is not a cancel event after escape key
        // we interpret it as commit.
        String newText=textField.getText(); // get the new text from the view
        this.commitEdit(getConverter().fromString(newText)); // commit the new text to the model
    }
    setGraphic(null); // stop editing with TextField

}

/** {@inheritDoc} */
@Override public void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);
    updateItem();
}

/***************************************************************************
 *                                                                         *
 *  // djw code taken and adapted from package protected CellUtils.        *
 *                                                                         *
 **************************************************************************/

private TextField getTextField() {

    final TextField textField = new TextField(getItemText());

    // Use onAction here rather than onKeyReleased (with check for Enter),
    // as otherwise we encounter RT-34685
    textField.setOnAction(event -> {
        if (converter == null) {
            throw new IllegalStateException(
                    "Attempting to convert text input into Object, but provided "
                            + "StringConverter is null. Be sure to set a StringConverter "
                            + "in your cell factory.");
        }
        this.commitEdit(getConverter().fromString(textField.getText()));
        event.consume();
    });
    textField.setOnKeyPressed(t -> { if (t.getCode() == KeyCode.ESCAPE) escapePressed = true; else escapePressed = false; });
    textField.setOnKeyReleased(t -> {
        if (t.getCode() == KeyCode.ESCAPE) {
            // djw the code may depend on java version / expose incompatibilities: 
            throw new IllegalArgumentException("did not expect esc key releases here.");
        }
    });
    return textField;
}

private String getItemText() {
    return getConverter() == null ?
            getItem() == null ? "" : getItem().toString() :
                getConverter().toString(getItem());
}

private void updateItem() {
    if (isEmpty()) {
        setText(null);
        setGraphic(null);
    } else {
        if (isEditing()) {
            if (textField != null) {
                textField.setText(getItemText());
            }
            setText(null);
            setGraphic(textField);      
        } else {
            setText(getItemText());
            setGraphic(null);
        }
    }
}

private void startEdit(final TextField textField) {
    if (textField != null) {
        textField.setText(getItemText());
    }
    setText(null);
    setGraphic(textField);
    textField.selectAll();

    // requesting focus so that key input can immediately go into the
    // TextField (see RT-28132)
    textField.requestFocus();
 }
}

关于java - 在 JavaFX 中单击可编辑 TableView 单元格外部时如何提交?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23632884/

有关java - 在 JavaFX 中单击可编辑 TableView 单元格外部时如何提交?的更多相关文章

  1. ruby - 如何使用 Nokogiri 的 xpath 和 at_xpath 方法 - 2

    我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div

  2. ruby - 如何从 ruby​​ 中的字符串运行任意对象方法? - 2

    总的来说,我对ruby​​还比较陌生,我正在为我正在创建的对象编写一些rspec测试用例。许多测试用例都非常基础,我只是想确保正确填充和返回值。我想知道是否有办法使用循环结构来执行此操作。不必为我要测试的每个方法都设置一个assertEquals。例如:describeitem,"TestingtheItem"doit"willhaveanullvaluetostart"doitem=Item.new#HereIcoulddotheitem.name.shouldbe_nil#thenIcoulddoitem.category.shouldbe_nilendend但我想要一些方法来使用

  3. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  4. ruby-on-rails - 如何验证 update_all 是否实际在 Rails 中更新 - 2

    给定这段代码defcreate@upgrades=User.update_all(["role=?","upgraded"],:id=>params[:upgrade])redirect_toadmin_upgrades_path,:notice=>"Successfullyupgradeduser."end我如何在该操作中实际验证它们是否已保存或未重定向到适当的页面和消息? 最佳答案 在Rails3中,update_all不返回任何有意义的信息,除了已更新的记录数(这可能取决于您的DBMS是否返回该信息)。http://ar.ru

  5. ruby-on-rails - 'compass watch' 是如何工作的/它是如何与 rails 一起使用的 - 2

    我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t

  6. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  7. ruby - 如何指定 Rack 处理程序 - 2

    Rackup通过Rack的默认处理程序成功运行任何Rack应用程序。例如:classRackAppdefcall(environment)['200',{'Content-Type'=>'text/html'},["Helloworld"]]endendrunRackApp.new但是当最后一行更改为使用Rack的内置CGI处理程序时,rackup给出“NoMethodErrorat/undefinedmethod`call'fornil:NilClass”:Rack::Handler::CGI.runRackApp.newRack的其他内置处理程序也提出了同样的反对意见。例如Rack

  8. ruby - 如何每月在 Heroku 运行一次 Scheduler 插件? - 2

    在选择我想要运行操作的频率时,唯一的选项是“每天”、“每小时”和“每10分钟”。谢谢!我想为我的Rails3.1应用程序运行调度程序。 最佳答案 这不是一个优雅的解决方案,但您可以安排它每天运行,并在实际开始工作之前检查日期是否为当月的第一天。 关于ruby-如何每月在Heroku运行一次Scheduler插件?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/8692687/

  9. ruby-on-rails - 如何从 format.xml 中删除 <hash></hash> - 2

    我有一个对象has_many应呈现为xml的子对象。这不是问题。我的问题是我创建了一个Hash包含此数据,就像解析器需要它一样。但是rails自动将整个文件包含在.........我需要摆脱type="array"和我该如何处理?我没有在文档中找到任何内容。 最佳答案 我遇到了同样的问题;这是我的XML:我在用这个:entries.to_xml将散列数据转换为XML,但这会将条目的数据包装到中所以我修改了:entries.to_xml(root:"Contacts")但这仍然将转换后的XML包装在“联系人”中,将我的XML代码修改为

  10. ruby - 如何使用文字标量样式在 YAML 中转储字符串? - 2

    我有一大串格式化数据(例如JSON),我想使用Psychinruby​​同时保留格式转储到YAML。基本上,我希望JSON使用literalstyle出现在YAML中:---json:|{"page":1,"results":["item","another"],"total_pages":0}但是,当我使用YAML.dump时,它不使用文字样式。我得到这样的东西:---json:!"{\n\"page\":1,\n\"results\":[\n\"item\",\"another\"\n],\n\"total_pages\":0\n}\n"我如何告诉Psych以想要的样式转储标量?解

随机推荐