Java 8.0 x64, Win7 x64, Clojure, Emacs.
I'm doing some stuff in Clojure with TableView wherein I'm proxying TableCell so I can render and edit arbitrary things in it. The values are the fields of a map which is inside an atom. Code is below. It makes use of plenty of utility functions and macros to make this simpler, but you get the gist. The main thing is the management of the cell's graphic and text properties.
There is a keyboard handler which is attached to the ComboBox so it knows when the user presses ENTER, etc. This handler is removed on defocus from the cell, so we don't end up with multiple handlers in the object.
In this example I have three columns, one for the name of a field (A simple cell factory which only shows text and is not editable), one for the value (fancy cell factory), and one for the type (simple cell factory). The output, using some sample data, looks like this:
When I sort the table based on Value things seem to work fine, as follows:
Normally, when the keyboard handler triggers, it calls the cell's commitEdit function, which calls its TableCell superclass commitEdit. The TableView magic behind the scenes then calls the column's onEditCommit handler, which actually commits the edit to the database. After the superclass commitEdit returns, there is nothing left to do in the cell's commitEdit. The cell's updateItem is then automatically called by TableView which replaces the ComboBox with the normal contents of the cell.
PROBLEM
When I sort the table based on the Field column one or more times, or the Type column two or more times and try to edit something with a ComboBox (in this case the color selector), it takes an extra click to get the ComboBox to drop down, and the ENTER key doesn't work, specifically as follows:
CAUSE
In the broken case, the TableCell's superclass appears to return immediately and does not call column's onCommitEdit handler, nor does the cell's updateItem get called, so the cell is not rendered back to its normal non-editing state, ie, without the ComboBox.
Normal and broken cases look like this:
The debug text output in the normal case and broken case are shown here.
The weird thing is this problem sometimes appears with the non-color ComboBox as well (the sides field is has a ComboBox editor with numbers, for example).
So is this a bug in JavaFX TableView? Or am I doing something wrong?
(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))
I found the problem, which was, of course, in my code.
Because JFX reuses cells, the editable property of the cell persists even when there are different contents rendered in the cell. In my case I had a boolean member of my databased which I rendered as a checkbox. The checkbox itself was clickable, but the cell in which it was rendered was not editable. When this cell got re-rendered after sort with a different item, the non-editing state persisted and screwed up the editing of the new item, which somehow led to the drop-down box not going away properly. Actually the bug showed up in non-combobox items too, such as for text edits, etc.
So the solution was to explicitly set the editable property of the cell for each item type that is rendered.
Related
I am trying to make an easy to use button API in Lua with ComputerCraft, and I'm having some trouble. When I do:
os.loadAPI("button")
action=function()
term.clear()
term.setCursorPos(1,1)
print("Hello!")
end
button.newButton("B1",5,5,20,10)
button.drawButton("B1",colors.orange,colors.white)
button.onClick("B1",action,true)
Nothing happens, it doesn't even draw the colors. I have done tests, and when I store something like colors.white as a variable, then print the variable, it returns the number code of that color, which comes from the colors API. Here is what I have:
--to use the newButton function, do this:
--button.newButton(exampleButton)
--to use onClick function, create a variable like this:
--exampleFunc=function()
--(code)
--end
--Then call onClick with the same variable:
--button.onClick(exampleButton,exampleFunc)
buttons={}
xPos=0
yPos=0
function removeButton(buttonName)
for key, fields in pairs(buttons) do
if key == buttonName then
table.remove(button,buttonName)
else
print("ERROR: button name not available")
end
end
end
function onClick(buttonName,action,boolean)
for key, fields in pairs(buttons) do
if boolean then
testClick(action)
end
end
end
function drawSeparateButton(x,y,w,h,outLineColor,fillColor)
if key == buttonName then
x=buttons[buttonName]["x"]
y=buttons[buttonName]["y"]
w=buttons[buttonName]["w"]
h=buttons[buttonName]["h"]
paintutils.drawBox(x,y,x+(w-1),y+(h-1),outLineColor)
paintutils.drawFilledBox(x+1,y+1,x+(w-2),y+(h-2),fillColor)
end
end
function testClick(action)
for key, fields in ipairs(buttons) do
x=buttons[buttonName]["x"]
y=buttons[buttonName]["y"]
w=buttons[buttonName]["w"]
h=buttons[buttonName]["h"]
x2=x+(w-1)
y2=y+(h-1)
button,xPos,yPos=os.pullEvent("mouse_click")
if xPos>=x and xPos<=x2 and yPos>=y and yPos<=y2 then
action()
end
end
end
function newButton(buttonName,X,Y,W,H)
buttons[buttonName] = {x=X,y=Y,w=W,h=H}
end
function drawButton(buttonName,outLineColor,fillColor)
for key, fields in ipairs(buttons) do
if key == buttonName then
x=buttons[buttonName]["x"]
y=buttons[buttonName]["y"]
w=buttons[buttonName]["w"]
h=buttons[buttonName]["h"]
x2=x+w-1
y2=y+h-1
x3=x+1
y3=y+1
x4=x+w-2
y4=y+h-2
paintutils.drawBox(x,y,x2,y2,outLineColor)
paintutils.drawFilledBox(x3,y3,x4,y4,fillColor)
elseif key ~= buttonName then
print("Button name not availabel")
end
end
end
I just need to be able to store a color like colors.white in a variable and have it returned as colors.white, and not the color code. I also need to be able to check which button is clicked and run a function specified by the user when one of the buttons are clicked.
I'm going to walk through your prototype code and point out some errors I see and also try to answer your question. I'm going to assume that you want to set a key value in a table to an array and access that externally.
A quick and short answer to your question is that you can store tables within tables and access them through keys or indices. A design change I would make, however, is to store your exampleFunc as a member of each button table to associate it with a specific button.
Example:
buttons = {}
buttons.playButton = {x=0, y=0, w=10, h=10, func=function() return end}
buttons.quitButton = {x=0, y=30, w=10, h=10, func=function() return end}
...
buttons.quitButton.x = 10
buttons.playButton.func()
Tables have a key-value structure, where keys can be strings or numbers. There are multiple ways to access an array using a key depending on the data type of the key.
For example, instead of writing buttons.quitButton.x = 10 we could've written buttons["quitButton"].x = 10 or buttons["quitButton"]["x"] = 10 or buttons.quitButton["x"] = 10.
This page is a great starting point for learning about Lua's tables.
According to this page, os.pullEvent() is blocking, and you will only be able to check if one button is clicked per mouse click. Consider looping through your buttons table and checking every button to see if the mouse falls within its rectangular bounds. Once you find which button the mouse clicked, you can call its func member. While we're still discussing this method, the while true do loop is completely unnecessary.
function removeButton(buttonName)
for buttonName in pairs(button) do
...
function newButton(buttonName)
state=true
for buttonName in pairs(buttons) do
...
You may come from a Python background where the statement if element in list exists but Lua has no such statement. The for loops you use are looping through every member of the list. You also aren't capturing all of the variables returned by the pairs() function. A fix for that would look something like the following:
function buttonFunction(buttonName)
for key, fields in pairs(buttons) do
if key == buttonName then
...
end
end
There are multiple instances where you refer to the variable button when you mean buttons.
I have a View controller, with a tableview inside of it. I have a navigation button called "create". After the user has filled in a couple textfields and selected a cells from the UITableview i want the create button to create a parse object with the selected and inputed information.
I have these arrays..
var name = [String]()
var address = [String]()
var theseItems:[String] = [] //globalArray
im appending the selected cells to "theseItems" array.
//i have already queried and added what i wanted, to the name and address array..so they are filled with information.
didSelectRowAtIndexPath {
self.theseItems.append(name[indexPath.row] as String)
self.theseItems.append(address[indexPath.row] as String)
now with the create Button i want to create an object from this information but am having a hard time accessing the selected cell index path in the button...
#IBAction func createButton(sender: AnyObject) {
let thisObject = PFObject(className:"thisObject")
thisObject["name"] = name.text
thisObject["address"] = address.text
thisObject["selectedCell"] = theseItems(name[indexPath.row])
thisObject["selectedCell2"] = theseItems(address[indexPath.row])
//error** unresolved identifier "indexPath"
I'm not sure the correct way to access the array with a cells information.. to save it to parse. thanks in advance!
thisObject.saveEventually()
You can get the index paths of the currently selected cells by calling the indexPathsForSelectedRows() method on the table view (or indexPathForSelectedRow() if constrained to a single selection at one time).
You're trying to access a an array position from text, but text is a textField, not an array
I need to know whether it is possible to add values to a ComboBox at different times.
ObservableList<String> options1
= FXCollections.observableArrayList(
"Civil (CE)", "Computer (CT)", "Electrical (EEE)", "Electronics (ELS)",
"Mechanical (ME)");
comboBox1 = new ComboBox(options1);
comboBox1.setPrefSize(280, 30);
This is my code of ComboBox. In it I added 5 values at a time. But is it possible to add each value at different times, for example, add values one by one in a while loop. I tried it and the result was that each value overlapped previously added values, and as a result only one value was present in the ComboBox at the end. This the code with while loop -
while (rs.next()) {
subject = rs.getString("subname");
ObservableList<String> options1 = FXCollections.observableArrayList(subject);
comboBox1 = new ComboBox(options1);
}
Can I add values to ComboBox at different times, one after another, without overlapping the previous value ?
Yes, you can add/remove items at any moment using getItems()
comboBox1.getItems().add("Beer");
comboBox1.getItems().add("Whiskey");
comboBox1.getItems().add("Water");
or directly updating list:
options1.add("Milk");
I need to create a QTreeView based on requests.
So, when the user open the application, it should make a request to get the root item for the tree. Once the user clicks on that item, it should ask for the children, and so on.
I couldn't find any working example with requests as I want and I don't even know if this is possible.
It's quite simple really.
Firstly, connect the tree's expanded signal to a handler, and populate the tree with the root's top-level items.
When the signal is fired, it will pass the index of the expanded item to the handler. The handler can then use this to check whether the item has any children using, say, the model's hasChildren method.
If the item already has children, do nothing; otherwise, populate it with whatever top-level items are appropriate for that item.
UPDATE
Below is a script demonstrates how to build a tree dynamically.
For simplicity, the demo uses a QTreeWidget, thus avoiding the need for a separate model. Additional data is stored within the tree by using QTreeWidgetItem.setData.
Note that the sip import at the top is only needed for compatibilty between Python 2 and 3 (see here for details). If you're using Python 2, it's not needed.
import sip
sip.setapi('QVariant', 1)
from PyQt4 import QtGui, QtCore
class Window(QtGui.QTreeWidget):
def __init__(self):
QtGui.QTreeWidget.__init__(self)
self.setHeaderHidden(True)
self.itemExpanded.connect(self.handleExpanded)
self.itemClicked.connect(self.handleClicked)
self.handleExpanded(self.invisibleRootItem())
def depth(self, item):
depth = 0
while item is not None:
item = item.parent()
depth += 1
return depth
def requestData(self):
for title in 'One Two Three Four Five'.split():
yield title, 'additional data'
def addItems(self, parent):
depth = self.depth(parent)
for title, data in self.requestData():
item = QtGui.QTreeWidgetItem(parent, [title])
item.setData(0, QtCore.Qt.UserRole, data)
if depth < 3:
item.setChildIndicatorPolicy(
QtGui.QTreeWidgetItem.ShowIndicator)
def handleExpanded(self, item):
if item is not None and not item.childCount():
self.addItems(item)
def handleClicked(self, item, column):
print(item.data(column, QtCore.Qt.UserRole).toPyObject())
if __name__ == '__main__':
import sys
app = QtGui.QApplication(sys.argv)
window = Window()
window.show()
sys.exit(app.exec_())
I have a listview with a dozen of rows binded to xml. I'd like to have a possibility to locate and position the cursor to a certain record. For example:
I have a with these ID, Name, Value:
1, Johny, Cash, USA
2, Jean-Michel, Jarre, France
3, Jeanette, , USA
When I would type "Je", the SelectedRow would be positioned to ID 2. When I would type "Jeane", the SelectedRow would be positioned to ID 3. Simply I'd like to have a possibility to search and go to the proper record in the listview. I started building the SearchString and at this point I got stuck:
The one and only possibility in WPF is to use the KeyDown event. Unfortunately, this event return a kind of Key, which I was not able to convert to a string. E.g. when I press "A", SearchString would be "A". When I continue typing "B", SearchString would be "AB" etc. When SelectedItem changes, SearchString will be set to String.Empty. No KeyCode or other useful property/method is available.
And here comes the head rubbing. How can I build the SearchString I need?
When I tried e.Key.ToString(), I got really funny strings - e.g. for 0 on Numpad I get a Key "Numpad0", for "," I get "OemComma" etc. I was trying also the TryParse method to char, for key "3" I get a value of "#" etc, it only works flawlessly just for letter A through Z, for other keys TryParse returns false.
The one and only way how to solve this is to build a translation table with a very long kind of "case e.Key.ToString() of":
"A": SearchString = SearchString + "A";
"System", SearchString = SearchString + " ";
"Numpad0", SearchString = SearchString + "0";
"ArrowUp", do nothing
etc etc etc.
Isn't there a more clever and simple way to do this?? Or I just don't see the trees because of the forest?
Handle PreviewTextInput instead. Reference: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/3fcfbe53-2c72-4fec-a997-dc881d1de72a.
EDIT:
Note: The ListView (and ListBox also) internally handles the KeyDown event for some keys to perform selection and navigation. Below are the keys that are internally handled by the ListView:
Key.Space:
Key.Return:
Key.Prior:
Key.Next:
Key.End:
Key.Home:
Key.Left:
Key.Up:
Key.Right:
Key.Down:
Key.Space is of particular interest because when space is pressed on a ListView, the PreviewTextInput event will not get fired. So to complete your solution, you would have to also add a handler for the ListView.PreviewKeyDown event and check if the space key was pressed in order for you to append the correct whitespace text.