I'm trying to implement the basic JavaFX example shown here: http://docs.oracle.com/javafx/2/get_started/fxml_tutorial.htm . I was able to get the basic stuff working (programmatically creating the gui) and using css, but I'm having trouble with the FXMLLoader.
The java version is this:
#Override
public void start(Stage stage) throws Exception {
Parent root = FXMLLoader.load(getClass().getResource("fxml_example.fxml"));
stage.setTitle("FXML Welcome");
stage.setScene(new Scene(root, 300, 275));
stage.show();
}
I'm not a Java expert, but I don't think an FXMLLoader object is instantiated ie. there is not a new FXMLLoader(); statement. So where is the load coming from?
When i try the following clojure code:
(ns jfxtwo.core
(:gen-class
:extends javafx.application.Application)
(:import (javafx.application Application)
(javafx.fxml FXMLLoader)
(javafx.scene Parent Scene)
(javafx.stage Stage)))
(defn -main []
(javafx.application.Application/launch jfxtwo.core (into-array String [])))
(defn -start [this primaryStage]
(let [loc (clojure.java.io/resource "fxml_example.fxml")
root (.load FXMLLoader ^java.net.URL loc)
scene (Scene. root 300 250)]
(.setScene primaryStage scene)
(.show primaryStage)))
...i get Caused by: java.lang.IllegalArgumentException: No matching method found: load for class java.lang.Class .
So I put a dot after the FXMLLoader to create an instance: (FXMLLoader.) I get this: ClassCastException java.net.URL cannot be cast to java.io.InputStream
So this tell me I'm onto something since one of the load methods for FXMLLoader supports an InputStream. I tried forcing the compiler to know the resource is a java.net.URL because that's one of the supported overloads for FXMLLoader.load, by placing the call to (clojure.java.io/resource...) directly in the call to (.load...) but it still doesn't like it (I knew it was a long shot). I also tried type-hinting, (.load (FXMLLoader.) ^java.net.URL loc) and (.load (FXMLLoader.) #^java.net.URL loc), but no dice; it still tries to use the java.io.InputStream version of load.
There is also the getClass() call in java which I think is getting the superclass of Application, but I'm not sure what to do with that in clojure-land.
Any ideas on how to load the fxml file?
After that, the java code has #FXML annotation for allowing the FXML into private class members. Is this necessary in clojure (java code breaks when I remove it)? The #Override annotation doesn't seem to be used in clojure.
thanks
Given the Java syntax, the load method being called here appears to be a static method of the FXMLLoader class. To call static methods in Clojure, you need to use (ClassName/methodName args...) syntax:
(FXMLLoader/load ...)
(Just checked: FXMLLoader has both static and instance load methods with multiple signatures. You'll want to call the same method the Java code does; static methods will be called with the FXMLLoader.load syntax in Java, instance methods -- someFXMLLoaderInstance.load.)
As for the getClass method call, it's target is implicitly this in Java; in Clojure, you'll have to make the target explicit ((.getClass this)).
I was able to work around my problem by creating the FXMLLoader separately from setting the location.
(defn -start [this primaryStage]
(let [loc (clojure.java.io/resource "fxml_example.fxml")
fxmlloader (FXMLLoader.)]
(.setLocation fxmlloader loc)
(let [root (.load fxmlloader )
scene (Scene. root 300 250)]
(.setScene primaryStage scene)
(.show primaryStage))))
Related
I have a basic Java interface defined as follows:
public interface Action {
void execute(Metadata var1, Parameter var2);
}
I'm trying to extend it in Clojure but keep getting errors. After importing the class into my namespace, I've tried using reify as follows:
(defn action [action-fn]
(reify Action
(execute [metadata parameter] (action-fn metadata parameter))))
but that throws a compiler illegal argument exception:
CompilerException java.lang.IllegalArgumentException: Can't define method not in interfaces: execute
Next I tried using proxy
(defn action [action-fn]
(proxy [Action] []
(execute [metadata parameter] (action-fn metadata parameter))))
That compiles successfully, and my editor (IntelliJ + Cursive) navigates to the interface definition via a border decoration, but trying to invoke execute on a generate proxy fails:
(.execute (action (fn [_ _] "Test action")))
throws the following:
IllegalArgumentException No matching field found: execute for class
Finally I tried using deftype as follows:
(deftype cljAction [action-fn]
Action
(execute [metadata parameter] (action-fn metadata parameter)))
which throws the same compiler error as for reify, e.g:
CompilerException java.lang.IllegalArgumentException: Can't define method not in interfaces: execute
Trawling through various blog posts and SO answers seems to suggest it's a problem with the arity of arguments, but I'm not sure how to resolve it. What am I doing wrong??
You are missing the this reference from the function. So what you want is this:
(defn action [action-fn]
(reify Action
(execute [this metadata parameter] (action-fn metadata parameter))))
Obviously because you are not using it you can just call it _ or whatever makes the most sense in your opinion. When you are calling the function you want this:
(.execute (action action-fn) metadata parameter)
This differs slightly from when you are implementing a protocol. See https://clojuredocs.org/clojure.core/definterface for more information.
ponzao's answer is correct. But note that Cursive can actually fill in the stubs for you: you can write (reify Action) and then (with the cursor in that form somewhere) choose Code->Generate... and choose to implement methods. Cursive will then fill in the stubs with the correct form. This currently only works when implementing interfaces, not protocols.
I have this function which load correctly my namespace :
(defn load-module [module-name]
(use module-name)
)
And my "equivalent" macro that doesn't work :
(defmacro load-module-macro [module-name]
`(
(use '~module-name)
)
)
I don't understand the problem.
Moreover, I want to use this macro for load a module choose in configuration. In my config.clj I define a var with the namespace of my logger module which contains "save-data" function. Then I want to load the specified logger in my core program. So I can choose the logger to use directly in my configuration file (logger on disk, logger in database...). Is it the best way to do that ?
EDIT :
Error message
IllegalArgumentException Don't know how to create ISeq from: java.lang.Character clojure.lang.RT.seqFrom (RT.java:505)
No, in fact you don't want to use "use" directly in code at all. Use modifies the entire namespace it is called in and that could break your code in ways that are hardly predictable.
Instead what you should do is:
Implement a logging interface (Protocol), write a "meta-constructor" that dispatches whatever you set in config.clj as keyword. Code example
(defprotocol ILog
(save-data [this msg] "Logs message in msg."))
(defn create-file-log
"Returns an object implementing ILog, opens and flushes java.io.File file."
[file]
(let [f ... ;; create file writer here
]
(reify ILog
(save-data [this msg] ;; Write code that writes data to file here
))))
;; create other implementations like database here or elsewhere
(defn create-log
"Creates a log of of the type passed in type-kw."
[type-kw]
(case type-kw
:file (create-file-log "./app-log.txt")
;; other types
))
Now you would simply invoke create-log with whatever keyword is set in your config file and pass the returned object around to functions that need to do logging. Obviously, you could also def it as a global object but I don't recommend to do that.
Eventually you don't just want to set a keyword (type-kw) for the desired logging method in your config, but also other parameters like the file-name or a database uri so that you can pass something like
{:log-method :file
:data {:fname "app-log.txt"}}
or
{:log-method :db
:data {:uri "....
...to your create-log function that uses this structure to get the parameters for the reify constructors create-file-log, create-db-log, etc.
EDIT:
Because you don't like the switch statement, here is how to do it with multi-methods:
(defmulti create-log :logging-method)
(defmethod create-log :file
[arg-map]
(let [file (java.io.File. (:fname arg-map))]
(if (.exists file)
...
Then you simply have an entry in your config.clj
{...
:log {:logging-method :file
:fname "./log-file.txt"}}
To create a new logging type all you have to do now is to imagine an argument map like the one above and a constructor method for create-log.
I think you can also use multimethods for this. It's clojure's other polymorphism strategy, and might work well for your use case as you're only looking to implement a single method (save-data).
;; set up a config map
(def config {:logger :db-logger)
;; set up the dispatch function to read the logger from the config map
(defmulti save-data (fn [] (:logger config))
;; define methods as required - database logging
(defmethod save-data :db-logger []
(println "Save to database"))
;; some other logging - can be in another file
(defmethod save-data :other-logger []
(println "Save to other thing"))
Note: I'm still quite new to Clojure - so I'm not sure if this is a 'proper' way to use multimethods. Most of the examples I've seen dispatch on the type of the arguments, not on a config setting. Any experts, please correct me if I've got the wrong idea.
I'm trying to use Swing from Clojure, and I'm getting confused by gen-class and I can't tell from the documentation if this is supposed to work - paintComponent is a protected method on JPanel, and I'm able to override it, but when I try to call the exposed superclass's method, I get java.lang.IllegalArgumentException: No matching method found: parentPaintComponent for class project.PicturePanel . Can anyone clarify why I don't seem to have access to this method?
(ns project.PicturePanel
(:gen-class
:extends javax.swing.JPanel
:name project.PicturePanel
:exposes-methods {paintComponent parentPaintComponent}))
(defn -paintComponent [this g]
(println this)
(println g)
(.parentPaintComponent this g))
Yes! The code works correctly if you make sure that your compiled .class files are up to date. Try recompiling them!
I'm placing Clojure into an existing Java project which heavily uses Jersey and Annotations. I'd like to be able to leverage the existing custom annotations, filters, etc of the previous work. So far I've been roughly using the deftype approach with javax.ws.rs annotations found in Chapter 9 of Clojure Programming.
(ns my.namespace.TestResource
(:use [clojure.data.json :only (json-str)])
(:import [javax.ws.rs DefaultValue QueryParam Path Produces GET]
[javax.ws.rs.core Response]))
;;My function that I'd like to call from the resource.
(defn get-response [to]
(.build
(Response/ok
(json-str {:hello to}))))
(definterface Test
(getTest [^String to]))
(deftype ^{Path "/test"} TestResource [] Test
(^{GET true
Produces ["application/json"]}
getTest
[this ^{DefaultValue "" QueryParam "to"} to]
;Drop out of "interop" code as soon as possible
(get-response to)))
As you can see from the comments, I'd like to call functions outside the deftype, but within the same namespace. At least in my mind, this allows me to have the deftype focus on interop and wiring up to Jersey, and the application logic to be separate (and more like the Clojure I want to write).
However when I do this I get the following exception:
java.lang.IllegalStateException: Attempting to call unbound fn: #'my.namespace.TestResource/get-response
Is there something unique about a deftype and namespaces?
... funny my hours on this problem did not yield an answer until after I asked here :)
It looks like namespace loading and deftypes was addressed in this post. As I suspected the deftype does not automatically load the namespace. As found in the post, I was able to fix this by adding a require like this:
(deftype ^{Path "/test"} TestResource [] Test
(^{GET true
Produces ["application/json"]}
getTest
[this ^{DefaultValue "" QueryParam "to"} to]
;Drop out of "interop" code as soon as possible
(require 'my.namespace.TestResource)
(get-response to)))
I have a Clojure proxy statement that was getting large and messy, so I decided to try factoring the code of the beginDrag method redefinition out of the proxy statement, like this:
(defn enhanced-start-drag
""
[pie]
(let [pobj (. pie getPickedNode)
pobj-coll (seq (.. pie getInputManager
getKeyboardFocus getSelection))]
(println pobj)
(println pobj-coll)
(println "----------")
(proxy-super startDrag pie))) ; THIS IS LINE 94 (SEE ERROR MSG)
(defn custom-selection-event-handler [marqueeParent selectableParent]
(proxy [PSelectionEventHandler] [marqueeParent selectableParent]
(decorateSelectedNode [node]
(let [stroke-color (Color/red)]
(.setStrokePaint node stroke-color)))
(undecorateSelectedNode [node]
(let [stroke-color (Color/black)]
(.setStrokePaint node stroke-color)))
(startDrag [pie] ; pie is a PInputEvent
(enhanced-start-drag pie))
(endStandardSelection [pie] ; pie is a PInputEvent
(let [pobj (.getPickedNode pie)
slip (. pobj getAttribute "slip")
]
(swap! *last-slip-clicked*
(fn [x] slip))))))
I get the following compile error:
cd /Users/gw/tech/clojurestuff/cljprojects/infwb/src/infwb/
1 compiler notes:
Unknown location:
error: java.lang.Exception: Unable to resolve symbol: this in this context
core.clj:94:5:
error: java.lang.Exception: Unable to resolve symbol: this in this context
(core.clj:94)
Compilation failed.
As soon as I restore the body of enhanced-start-drag into the body of the proxy statement, everything works.
My question: Is there a way to move the messy details out to a separate function to improve the readability of my code?
Thanks for all your ideas and solutions.
UPDATE, 10/27/11: See the comments below. Arthur Ulfeldt was sharp in pointing out that the issue is captured references, and Dave Ray is also correct in saying that all you have to do is add this as a parameter to enhanced-start-drag and then proxy-super will work correctly. When I made the following two changes (without any changes to the body of enhanced-start-drag), my code was working again:
(defn enhanced-start-drag
""
[pie this]
and
(startDrag [pie] ; IN THE PROXY STMT IN custom-selection-event-handler
(enhanced-start-drag pie this))
BTW, my project uses Dave Ray's seesaw project to get a Java Swing UI. seesaw is awesome, as are its docstrings and sample code (which are much better than most commercial software). I highly recommend it! And thank you, Dave!
You have been bitten by symbol capture. In this case it is intentaional though you need to stay aware of it. From the doc for proxy-super
Use to call a superclass method in the body of a proxy method.
Note, expansion captures 'this`
proxy is creating a class that calls a function, when the call gets into enhanced-start-drag the value of this is not where proxy-super expects
you may needs to pass this as another argument into enhanced-start-drag and then call (. saved-this ...) instead of using proxy-super.