- in a REPL - being able to modify the window
- as console app executable
The solution to both scenarios are not quite obvious if you're not familiar with WPF or the threading models. For WPF and COMInterop the threading model has to be a Single Threaded Apartment. My first thought was to use the documented STAThreadAttribute which the .NET compilers are using. In ClojureCLR .NET attributes can be applied with the meta data syntax ^{}. Either Clojure.Compile.exe isn't aware of the STAThread feature at the moment or I haven't found the option yet. Have to digg into the code later. My first try to apply it to the -main function failed.
Kick starting my approach from Marius Kjehldahl's ClojureCLR post on running a WPF Window in a REPL you'll get option 1. I've shamelessly copied his solution and created my own quick workaround where a new Thread is spawn setting the AppartmentState to STA with the ability to load a .xaml.
For option 1, I created two gists from hist post for the REPL solution with some minor modifications:
For option 2 have a look at the following gist for compiling instructions and some background information regarding the threading model.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
; ClojureCLR 1.4 | |
; to compile the wpfapp.clj into an executable run: "Clojure.Compile.exe wpfapp" | |
; see also: http://clojureclr.blogspot.de/2012/01/compiling-and-loading-in-clojureclr.html | |
; be aware of the namespace and the filename! atm it should stick to exactly the same name | |
(assembly-load-with-partial-name "PresentationFramework") | |
(assembly-load-with-partial-name "PresentationCore") | |
(assembly-load-with-partial-name "WindowsBase") | |
(assembly-load-with-partial-name "System.Xml") | |
(ns wpfapp | |
(:import (System.Windows Application Window)) | |
(:import (System.Windows.Threading Dispatcher DispatcherPriority)) | |
(:import (System.Threading ApartmentState ThreadStart Thread AutoResetEvent)) | |
(:import (System.IO File)) | |
(:import (System.Xml XmlReader)) | |
(:import (System.Windows.Markup XamlReader)) | |
(:import (System)) | |
(:gen-class)) | |
(defn run[& args] | |
(let [app (new Application) | |
win (-> "MyWpfUI.xaml" | |
File/OpenText | |
XmlReader/Create | |
XamlReader/Load)] | |
(pr "running app!") | |
(.Run app win))) | |
; STAThreadAttribute should instrcut the compiler to run the main thread | |
; as STA. WPF and COM interop needs STA. For further details read | |
; STAThreadAttribute: http://msdn.microsoft.com/en-us/library/ms680112(v=vs.85).aspx | |
; STA: http://msdn.microsoft.com/en-us/library/ms680112(v=vs.85).aspx | |
; For Clojure.Compile.exe it doesn't work with gen-class and -main. | |
; therefore I'm creating a new thread setting it to STA as workaround | |
; atm we're only able to run a console app! | |
; To be a first class .NET citizen the Clojure.Compile.exe | |
; should have options like C#: | |
; - Console Application | |
; - Class Library | |
; - Windows Application | |
(defn sta-thread [func] | |
(let [delegate (gen-delegate ThreadStart [] (func)) | |
thread (doto (new Thread delegate) | |
(.SetApartmentState ApartmentState/STA) | |
(.Start))] | |
thread)) | |
(defn -main [& args] | |
(let [uithread (sta-thread (partial run args))])) |
UPDATE: Here is a quickfix for the Clojure.Compile.exe allways appending the STAThread attribute to the main executable. There is the generation from the clojure source file left.
No comments:
Post a Comment