combobox - Does JavaFX 8.0 TableView have a sort bug? -
java 8.0 x64, win7 x64, clojure, emacs.
i'm doing stuff in clojure tableview
wherein i'm proxy
ing tablecell
can render , edit arbitrary things in it. values fields of map inside atom. code below. makes use of plenty of utility functions , macros make simpler, gist. main thing management of cell's graphic , text properties.
there keyboard handler attached combobox
knows when user presses enter
, etc. handler removed on defocus cell, don't end multiple handlers in object.
in example have 3 columns, 1 name of field (a simple cell factory shows text , not editable), 1 value (fancy cell factory), , 1 type (simple cell factory). output, using sample data, looks this:
when sort table based on value things seem work fine, follows:
normally, when keyboard handler triggers, calls cell's commitedit
function, calls tablecell
superclass commitedit
. tableview
magic behind scenes calls column's oneditcommit
handler, commits edit database. after superclass commitedit
returns, there nothing left in cell's commitedit
. cell's updateitem
automatically called tableview
replaces combobox
normal contents of cell.
problem
when sort table based on field
column 1 or more times, or type
column two or more times , try edit combobox
(in case color selector), takes click combobox
drop down, , enter
key doesn't work, follows:
cause
in broken case, tablecell
's superclass appears return , not call column's oncommitedit
handler, nor cell's updateitem
called, cell not rendered normal non-editing state, ie, without combobox
.
the debug text output in normal case , broken case shown here.
the weird thing problem appears non-color combobox
(the sides
field has combobox
editor numbers, example).
so bug in javafx tableview
? or doing wrong?
(defn add-handlers! "adds common keyboard handler , focus listener temporary editing graphic. graphic typically textfield or combo-box. cell tablecell being edited. getterfn function value graphic can commited database." [graphic cell getterfn] (let [focus-listener (make-focus-change-listener cell getterfn)] (println "adding focus , keyboard listener") (add-listener! graphic :focused focus-listener) (.setonkeypressed graphic (eventhandler [e] ;; here "cell" still refers 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))))) ;; nothing (defn make-combobox "implements dropdown combobox. 'cell' fancy table cell in question. 'items' list of things dropdown, can dropdown can render , choose 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 editing such drop-down, handled in startedit function; function renders cell when called updateitem or canceledit." [cell item] (cond (instance? node item) (set-graphic-text! cell item nil) ;; 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)) ;; other types go here, presumably text types, assume editable :else (set-graphic-text! cell nil (si/to-normstr item)))) ;; else set underlying text (def fancy-tablecell-factory "the main callback interface constructs actual each cell arbitrary types. assumes editable cell text representations." (callback [column] (proxy [tablecell] [] (updateitem [item empty] (proxy-super updateitem item empty) (when (not empty) (render-cell-with-item! item))) (startedit [] (proxy-super startedit) ;; change 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)] ;; nil ... (if-let [combo-items (:combo-items options)] ;; ... put argument :combo-items (let [cmb (make-combobox item combo-items)] (set-graphic-text! cmb nil) (.requestfocus cmb) (.show cmb)) ;; makes drop-down appear without clicking twice. (when (textish? item) (let [tf (make-textfield-editor this)] (set-graphic-text! tf nil) ;; set tf graphic; leave existing text alone (.requestfocus tf) (.selectall tf)))))) (canceledit [] ;; canceledit gets called either defocus or esc. ;; in case, use item in database ;; cell , render in updateitem (proxy-super canceledit) (let [item (apply access-db (calc-full-accesspath this))] (render-cell-with-item! item))) (commitedit [value] ;; nothing here. commits happen either in textfield callback or in 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 use inspector-view , table-view" [var accesspath columns] (let [obslist (observable (var-snapshot var accesspath))] (jfxnode tableview :user-data {:var var ;; actual var... :accesspath accesspath } ;; ... , how displayed data :items obslist :columns columns :editable (mutable? var)))) (defn inspector-view "takes plain map or atom/var/ref/agent of map , displays fields , values in jfx tableview. compound values (ie maps, vectors, etc., displayed string value. if access supplied, assumes m var/ref/atom , assigns appropriate linkage between m , view contents. topmost available var or map assigned tableview, , accessor each field assigned 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 fn accessing cell values data item :on-edit-start (eventhandler [e] (println "editing column " (.getoldvalue e) (.getnewvalue e))) :on-edit-cancel (eventhandler [e] (println "canceling column 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 table's userdata. inspector-view ;; not table-view, don't put in inner-table-view ;; function (let [userdata (.getuserdata tv) newuserdata (conj userdata {:field-options field-options})] (.setuserdata tv newuserdata)) ;; add watches, use tv instance key can remove later ;; gets called each time db changed. (if (mutable? var) (add-watch var tv (fn [k r o n] ;; key ref old new (println "inside kron new var" n) ;; capture existing sort order , type ;; taken http://stackoverflow.com/questions/11096353/javafx-re-sorting-a-column-in-a-tableview (let [sort-order (vec (.getsortorder tv)) ;; need remember observablelist<tablecolumn> , vectorize or gets reset underneath sort-types (map #(.getsorttype %) sort-order) sortables (map #(.issortable %) sort-order)] ;; here put items tableview after change (.setitems tv (observable (var-snapshot var accesspath))) ;; sort order empty put in (let [new-sort-order (.getsortorder tv)] ;; observablelist<tablecolumn> (.setall new-sort-order (into-array sort-order)) ;; reset sort order based on there before ;; assign sorting each column (doseq [col sort-order, sort-type sort-types, sortable sortables] (.setsorttype col sort-type) (.setsortable col sortable))))))) tv))
i found problem, was, of course, in code.
because jfx reuses cells, editable
property of cell persists when there different contents rendered in cell. in case had boolean member of databased rendered checkbox. checkbox clickable, cell in rendered not editable. when cell got re-rendered after sort different item, non-editing state persisted , screwed editing of new item, somehow led drop-down box not going away properly. bug showed in non-combobox items too, such text edits, etc.
so solution explicitly set editable property of cell each item type rendered.
Comments
Post a Comment