Combobox JavaFX8.0TableView是否存在排序错误?

Combobox JavaFX8.0TableView是否存在排序错误?,combobox,javafx,clojure,javafx-2,javafx-8,Combobox,Javafx,Clojure,Javafx 2,Javafx 8,Java8.0x64,Win7x64,Clojure,Emacs 我在Clojure中使用TableView做一些事情,其中我使用proxyingTableCell来渲染和编辑其中的任意内容。这些值是原子内部映射的字段。代码如下。它使用了大量的实用函数和宏来简化这一过程,但您可以理解其中的要点。主要是管理单元格的图形和文本属性 有一个键盘处理程序连接到组合框,因此它知道用户何时按下ENTER,等等。该处理程序在单元格散焦时被删除,因此我们不会在对象中使用多个处理程序 在本例中,我有三列,一列表示

Java8.0x64,Win7x64,Clojure,Emacs

我在Clojure中使用
TableView
做一些事情,其中我使用
proxy
ing
TableCell
来渲染和编辑其中的任意内容。这些值是原子内部映射的字段。代码如下。它使用了大量的实用函数和宏来简化这一过程,但您可以理解其中的要点。主要是管理单元格的图形和文本属性

有一个键盘处理程序连接到
组合框
,因此它知道用户何时按下
ENTER
,等等。该处理程序在单元格散焦时被删除,因此我们不会在对象中使用多个处理程序

在本例中,我有三列,一列表示字段名称(一个仅显示文本且不可编辑的简单单元格工厂),一列表示值(花式单元格工厂),一列表示类型(简单单元格工厂)。使用一些示例数据的输出如下所示:

当我根据值对表进行排序时,似乎一切正常,如下所示:

通常,当键盘处理程序触发时,它调用单元格的
committedit
函数,该函数调用其
TableCell
超类
committedit
。然后,
TableView
magic幕后调用列的
onEditCommit
处理程序,该处理程序实际将编辑提交到数据库。在超类
committedit
返回后,在单元格的
committedit
中没有什么可以做的了。然后,
TableView
会自动调用单元格的
updateItem
,用单元格的正常内容替换组合框

问题

当我根据
字段
列对表格进行一次或多次排序,或者根据
类型
列进行两次或多次排序,并尝试使用
组合框
(在本例中为颜色选择器)编辑某个内容时,需要额外单击以获得下拉的
组合框
,而
ENTER
键不起作用,具体如下:

原因

在这种情况下,
TableCell
的超类会立即返回,并且不会调用列的
oncommittedit
处理程序,也不会调用单元格的
updateItem
,因此单元格不会恢复到正常的非编辑状态,即没有
组合框

正常情况和破损情况如下所示:

此处显示了正常情况下和断开情况下的调试文本输出。

奇怪的是,这个问题有时也会出现在非彩色的组合框上(例如,
sides
字段有一个带有数字的组合框编辑器)

那么这是JavaFX
TableView
中的一个bug吗?还是我做错了什么

(defn add-handlers!
  "Adds common keyboard handler and focus listener to temporary editing graphic.
  graphic is typically textfield or combo-box. cell is tablecell which
  is being edited.  getterfn is function to get value from graphic so
  it can be commited to database."
  [graphic cell getterfn]
  (let [focus-listener (make-focus-change-listener cell getterfn)]
    (println "adding focus and keyboard listener")
    (add-listener! graphic :focused focus-listener)
    (.setOnKeyPressed graphic (eventhandler [e] ;; here "cell" still refers to the tablecell
                                            (condp = (.getCode e)
                                              KeyCode/ENTER (do (println "ENTER pressed.  Removing focus listener")
                                                                (remove-listener! graphic :focused focus-listener) ;; Prevent double-commit on defocus
                                                                (.commitEdit cell (getterfn)))
                                              KeyCode/ESCAPE (do (println "ESC pressed. Removing focus listener")
                                                                 (remove-listener! graphic :focused focus-listener) ;; Prevent double-commit on defocus
                                                                 (.cancelEdit cell)) ;; Removes textfield
                                              KeyCode/TAB (let [index (.. cell getTableRow getIndex)
                                                                next-column (get-next-column cell (not (.isShiftDown e)))]
                                                            (println "TAB pressed.  Removing focus listener")
                                                            (remove-listener! graphic :focused focus-listener) ;; Prevent double-commit on defocus
                                                            (.commitEdit cell (getterfn))
                                                            (.edit (.getTableView cell) index next-column))
                                              nil))))) ;; do nothing


(defn make-combobox
  "Implements dropdown combobox.  'cell' is fancy table cell in
  question.  'items' is list of things for dropdown, which can be
  anything that the dropdown can render and choose as the final item"
  [cell initvalue & [items]]
  (let [cmb (jfxnode ComboBox (observable items))
        cell-factory FANCY-LISTCELL-FACTORY
        blank-cell (.call cell-factory nil)]
    (doto cmb
      (add-handlers! cell #(.getValue cmb))
      (.setValue initvalue)
      (.setButtonCell blank-cell)
      (.setCellFactory cell-factory))))


(defn render-cell-with-item!
  "Puts correct item in cell graphic and/or text property based on item
  type.  Additional arguments for editing such as drop-down, are
  handled in the startEdit function; this function just renders the
  cell when called by updateItem or cancelEdit."
  [cell item]
  (cond
    (instance? Node item) (set-graphic-text! cell item nil) ;; for a graphic/Node item
    (instance? Boolean item) (let [[var full-accesspath] (calc-full-accesspath cell)
                                   cb (jfxnode CheckBox
                                               :text (str item)
                                               :selected item
                                               :disable (not (mutable? var)))]
                               (.setEditable cell false)
                               (set-graphic-text! cell cb nil)
                               (when (mutable? var)
                                 (uni-bind! (.selectedProperty cb) var full-accesspath)))
    (instance? clojure.lang.PersistentVector item) (set-graphic-text! cell (Label. "Put vector editor here") nil)
    (instance? Color item) (set-graphic-text! cell (make-color-box item) (color-map-inverse item))
    ;; All other types go here, presumably text types, so assume editable
    :else (set-graphic-text! cell nil (si/to-normstr item))))   ;; else set underlying text


(def FANCY-TABLECELL-FACTORY
  "The main callback interface which constructs the actual each cell
  for arbitrary types.  Assumes an editable cell for text representations."
  (callback [column]  
            (proxy [TableCell] []
              (updateItem [item empty]
                (proxy-super updateItem item empty)
                (when (not empty) 
                  (render-cell-with-item! this item)))

              (startEdit []
                (proxy-super startEdit)
                ;; Change to appropriate graphic when editing
                (println "in proxy's startEdit.  Column commitHandler is" (.getOnEditCommit column))
                (let [item (apply access-db (calc-full-accesspath this))
                      options (get-field-options this)] ;; could be nil ...
                  (if-let [combo-items (:combo-items options)] ;; ... so put as argument to :combo-items
                    (let [cmb (make-combobox this item combo-items)]
                      (set-graphic-text! this cmb nil)
                      (.requestFocus cmb)
                      (.show cmb)) ;; This makes drop-down appear without clicking twice.
                    (when (textish? item)
                      (let [tf (make-textfield-editor this)]
                        (set-graphic-text! this tf nil) ;; just set tf as graphic; leave existing text alone
                        (.requestFocus tf)
                        (.selectAll tf))))))
              (cancelEdit []
                ;; CancelEdit gets called either by defocus or by ESC.
                ;; In any case, use the item currently in the database
                ;; for this cell and just render as in updateItem
                (proxy-super cancelEdit)
                (let [item (apply access-db (calc-full-accesspath this))]
                  (render-cell-with-item! this item)))
              (commitEdit [value]
                ;; Nothing to do here.  All commits happen either in the textField callback or in the column edit callback
                (println "in cell's commitEdit, before super")
                (proxy-super commitEdit value)
                (println "in cell's commitEdit, after super")))))


(defn inner-table-view*
  "Make inner table view for use by inspector-view and table-view"
  [var accesspath columns]
  (let [obslist (observable (var-snapshot var accesspath))]
    (jfxnode TableView
             :user-data {:var var ;; the actual var... 
                         :accesspath accesspath }  ;; ... and how to get to the displayed data
             :items obslist
             :columns columns
             :editable (mutable? var))))

(defn inspector-view
  "Takes plain map or atom/var/ref/agent of map and displays fields
  and values in JFX TableView. Compound values (ie maps, vectors,
  etc., for now are just displayed as their string value.  If access
  is supplied, assumes m is var/ref/atom and assigns appropriate
  linkage between m and view contents.  The topmost available var or
  map is assigned to the TableView, and the accessor for each field is
  assigned to each column."
  [var & {:keys [accesspath field-options]}]
  (let [ismutable (mutable? var)
        field-col (jfxnode TableColumn "Field"
                           :cell-value-factory CELL-VALUE-FACTORY
                           :cell-factory SIMPLE-TABLECELL-FACTORY
                           :user-data {:accessfn key } ;; label-only option not relevant yet
                           :editable false
                           :sortable true)
        value-col (jfxnode TableColumn "Value"
                           :cell-value-factory CELL-VALUE-FACTORY
                           :cell-factory FANCY-TABLECELL-FACTORY
                           :user-data {:accessfn val} ;; val is fn for accessing cell values from data item
                           :on-edit-start (eventhandler [e] (println "editing column " (.getOldValue e) (.getNewValue e)))
                           :on-edit-cancel (eventhandler [e] (println "canceling column with event" e))
                           :on-edit-commit (eventhandler [e] (do (println "column's on-edit-commit handler calling column-commit") (column-commit e)))
                           :editable ismutable
                           :comparator columnComparator)
        type-col (jfxnode TableColumn "Type"
                          :cell-value-factory CELL-VALUE-FACTORY
                          :cell-factory SIMPLE-TABLECELL-FACTORY
                          :user-data {:accessfn #(type (val %))}
                          :editable false
                          :sortable true)
        cols [field-col value-col type-col]

        tv (inner-table-view* var accesspath cols)]
    ;; Add options to table's userData.  This is for inspector-view
    ;; not table-view, so we don't put this in inner-table-view
    ;; function
    (let [userdata (.getUserData tv)
          newuserdata (conj userdata {:field-options field-options})]
      (.setUserData tv newuserdata))

    ;; Add watches, use tv instance as key so we can remove it later
    ;; This gets called each time db is changed.
    (if (mutable? var)
      (add-watch var tv (fn [k r o n] ;; Key Ref Old New
                          (println "Inside KRON with new var" n)
                          ;; Capture the existing sort order and type
                          ;; Taken from http://stackoverflow.com/questions/11096353/javafx-re-sorting-a-column-in-a-tableview
                          (let [sort-order (vec (.getSortOrder tv)) ;; need to remember ObservableList<TableColumn> and vectorize or it gets reset from underneath us
                                sort-types (map #(.getSortType %) sort-order)
                                sortables (map #(.isSortable %) sort-order)]

                            ;; Here we actually put the items into the tableview after the change
                            (.setItems tv (observable (var-snapshot var accesspath))) 

                            ;; Sort order is now empty up so we put back what was in it
                            (let [new-sort-order (.getSortOrder tv)] ;; get ObservableList<TableColumn>
                              (.setAll new-sort-order (into-array sort-order)) ;; reset the sort order based on what was there before

                              ;; Assign sorting to each column
                              (doseq [col sort-order, sort-type sort-types, sortable sortables]
                                (.setSortType col sort-type)
                                (.setSortable col sortable)))))))
    tv))
(defn添加处理程序!
将通用键盘处理程序和焦点侦听器添加到临时编辑图形。
图形通常是文本字段或组合框。单元格是tablecell,其中
正在编辑。getterfn是从图形中获取值的函数
它可以提交到数据库。”
[图形单元getterfn]
(让[焦点侦听器(使焦点更改侦听器单元格getterfn)]
(println“添加焦点和键盘侦听器”)
(添加监听器!图形:聚焦监听器)
(.setOnKeyPressedGraphic(eventhandler[e];;这里的“cell”仍然是指tablecell
(condp=(.getCode e)
按键编码/输入(执行(打印“按下输入键。删除焦点侦听器”)
(删除侦听器!图形:聚焦侦听器);;防止在离焦时重复提交
(.committedit单元(getterfn)))
按键代码/转义(do(println“ESC按下。移除焦点侦听器”)
(删除侦听器!图形:聚焦侦听器);;防止在离焦时重复提交
(.cancelEdit单元格));;删除文本字段
KeyCode/TAB(let[index(…cell getTableRow getIndex)
下一列(获取下一列单元格(不是(.isshift down e)))]
(println“按下选项卡。移除焦点侦听器”)
(删除侦听器!图形:聚焦侦听器);;防止在离焦时重复提交
(.Committedit单元(getterfn))
(.edit(.getTableView单元格)索引下一列))
零(()());;;无所事事
(定义制作组合框
实现下拉组合框。“单元格”是
问题。'items'是下拉列表,可以是
下拉列表可以渲染并选择作为最终项的任何内容“
[单元格初始值和[项目]]
(让[cmb(jfxnode组合框(可观察项目))
细胞工厂
空白单元格(.call cell factory nil)]
(doto中巴)
(添加处理程序!单元格#(.getValue cmb))
(.setValue initvalue)
(.setButtonCell空白单元格)
(.setCellFactory细胞工厂)
(使用项目定义渲染单元!
“根据项目在单元格图形和/或文本属性中放置正确的项目
类型。用于编辑的其他参数(如下拉列表)包括
在startEdit函数中处理;此函数仅呈现
由updateItem或cancelEdit调用时的单元格。“
[单元格项目]
(续)
(实例?节点项)(设置图形文本!单元格项为零);;对于gr