B4J Library B4j Print JavaFX8

Here is a B4j library written in B4j to access the full Printer modules provided with JavaFX8. Full source code is available.

At it's simplest, you can print a node using:
B4X:
    Dim P As Printer = Printer_Static.GetDefaultPrinter
    Dim PJ As PrinterJob = PrinterJob_Static.CreatePrinterJob2(P)
    PJ.PrintPage(lblTest)
    PJ.EndJob

Or with dialogs:

B4X:
    Dim PJ As PrinterJob = PrinterJob_Static.CreatePrinterJob
    PJ.ShowPageSetupDialog(Null)
    PJ.ShowPrintDialog(Null)
    PJ.PrintPage(MainForm.RootPane)
    PJ.EndJob

Code to scale a view to print on a single page.
Scale output:
Sub ScaleOutput(P As Printer,N As Node) As Node
    Dim PL As PageLayout = P.GetDefaultPageLayout
    Dim ScaleX,ScaleY As Double
    Dim NJO As JavaObject = N
    Dim JO As JavaObject = N
    ScaleX = PL.GetPrintableWidth / JO.RunMethodJO("getBoundsInParent",Null).RunMethod("getWidth",Null)
    ScaleY = PL.GetPrintableHeight / JO.RunMethodJO("getBoundsInParent",Null).RunMethod("getHeight",Null)
    Dim SJO As JavaObject
    SJO.InitializeNewInstance("javafx.scene.transform.Scale",Array(ScaleX,ScaleY))
    NJO.RunMethodJO("getTransforms",Null).RunMethod("add",Array(SJO))
    Return NJO
End Sub

Usage:
    'Print with dialogs
    Dim PJ As PrinterJob = PrinterJob_Static.CreatePrinterJob
    If PJ.ShowPageSetupDialog(Null) Then
        If PJ.ShowPrintDialog(Null) Then
'            PJ.PrintPage(MainForm.RootPane)
            PJ.PrintPage(ScaleOutput(PJ.GetPrinter,MainForm.RootPane))
            PJ.EndJob
        Else
            PJ.CancelJob
        End If
    Else
        PJ.CancelJob
    End If

Reset node size after scaling:
'where N would be MainForm.Rootpane in the above example
N.As(JavaObject).Runmethod("getTransforms",Null).as(List).Clear

Depends on: JavaFX8, JavaObject

Documentation (apart from that in the library) is available here: http://docs.oracle.com/javase/8/javafx/api/index.html?javafx/print/Printer.html
Click on javafx.print in the All Classes Packages frame to see all the relevant classes.

I've ignored Enums where strings are acceptable for simplicity's sake.

V0.6 fix improperly formed XMLmessage displayed in IDE help (post #6)
V0.7 added #RaisesSynchronousEvents to PrinterJob module as described here : https://www.b4x.com/android/forum/t...ble-getting-jfx8print-dialogs-to-work.133964/
V 0.8 Released as a B4xLib Enums now return string values instead of ENum objects.

Full code is in Printer.zip if you just want to look at the project without unzipping the B4xlib
Enjoy
 

Attachments

  • jFX8Print.b4xlib
    11.1 KB · Views: 671
  • Printer.zip
    14.2 KB · Views: 377
Last edited:

Star-Dust

Expert
Licensed User
Longtime User
I only print what goes into an A4 page. Those lines that exceed the border edge at the bottom do not print on a subsequent page but are not printed at all.
 

Star-Dust

Expert
Licensed User
Longtime User
Yes, I had seen Peter's example, and i already use a panel that fits the A4 format.

My question is this: if I need to print more page with different contents, should I use one panel for each page?
Or could I use a single panel twice the size of A4 and get two-page printing?
 

stevel05

Expert
Licensed User
Longtime User
You can do either, but if you use a pane that is larger than A4, you will have no control over where the page splits. If you use a single pane you can stop longer paragraphs splitting. You would need to separate the paragraph blocks and measure them.
 

Star-Dust

Expert
Licensed User
Longtime User
Ok, . thank's. I'll try It.
 

Star-Dust

Expert
Licensed User
Longtime User
... but if you use a pane that is larger than A4, you will have no control over where the page splits...
Unfortunately it is not like that. If I open another panel 2 times the A4 format does not divide it into 2 pages. But it only produces the first page and the rest is ignored.

I'll have to divide the prints into multiple panels.
 

stevel05

Expert
Licensed User
Longtime User
Of course you are correct, but you can reuse the same pane.
 

Star-Dust

Expert
Licensed User
Longtime User
If it can be useful to someone else:

I have created a procedure that by selecting the printer (Better if physical, does not work well with the PDF virtual printer) extracts all the nodes (or views) corresponding to a page (in this case A4) from the panel and attach them to another panel, print them and put them back in the original panel, then do the same with the views that did not enter the first page perform a second print and so on until all the views are printed


B4X:
Sub PrintMultiplePage(Printer As Printer,PL As PageLayout,Panel As Pane, HeightPrinterPage As Double)
    Dim Left,Top,Height,Width As Int
    Dim TopPage=0,IndexNode  As Int
   
    Do While TopPage<Panel.Height
        Dim PanePrint As Pane
        Dim PJ As PrinterJob= PrinterJob_Static.CreatePrinterJob2(Printer)
        PanePrint.Initialize("")
        IndexNode=0
        Do While IndexNode<Panel.NumberOfNodes
            Dim V As Node = Panel.GetNode(IndexNode)
            If V.Top>=TopPage And V.Top<(TopPage+HeightPrinterPage) Then
                Left=V.Left
                Top=V.Top-TopPage
                Height=V.PrefHeight
                Width=V.PrefWidth
                V.RemoveNodeFromParent
                PanePrint.AddNode(V,Left,Top,Width,Height)
            Else
                IndexNode=IndexNode+1
            End If
        Loop
        PJ.PrintPage2(PL,PanePrint)
        PJ.EndJob

        For IndexNode=0 To PanePrint.NumberOfNodes-1
            Dim V As Node = PanePrint.GetNode(0)
            Left=V.Left
            Top=V.Top+TopPage
            Height=V.PrefHeight
            Width=V.PrefWidth
            V.RemoveNodeFromParent
            Panel.AddNode(V,Left,Top,Width,Height)
        Next
        TopPage=TopPage+HeightPrinterPage
    Loop
   
End Sub

Sub PrintMultiplePage(Printer As Printer,PL As PageLayout,Panel As Pane, HeightPrinterPage As Double)
    Dim Page As Int = Round(Panel.Height/HeightPrinterPage)
    Dim Left,Top,Height,Width, IndexNode As Int
 
    For i=0 To Page-1
        Dim PanePrint As Pane
        Dim TopPage As Int = I * HeightPrinterPage
        Dim PJ As PrinterJob= PrinterJob_Static.CreatePrinterJob2(Printer)
        PanePrint.Initialize("")
        Log(":::" & TopPage & "--" & Panel.NumberOfNodes)
        IndexNode=0
        Do While IndexNode<Panel.NumberOfNodes
            Dim V As Node = Panel.GetNode(IndexNode)
            Log("_" & V.Top)
            If V.Top>=TopPage And V.Top<(TopPage+HeightPrinterPage) Then
                Left=V.Left
                Top=V.Top-TopPage
                Height=V.PrefHeight
                Width=V.PrefWidth
                V.RemoveNodeFromParent
                PanePrint.AddNode(V,Left,Top,Width,Height)
            Else
                IndexNode=IndexNode+1
            End If
        Loop
        PJ.PrintPage2(PL,PanePrint)
        PJ.EndJob

        For IndexNode=0 To PanePrint.NumberOfNodes-1
            Dim V As Node = PanePrint.GetNode(0)
            Left=V.Left
            Top=V.Top+TopPage
            Height=V.PrefHeight
            Width=V.PrefWidth
            V.RemoveNodeFromParent
            Panel.AddNode(V,Left,Top,Width,Height)
        Next
    Next
 
End Sub

upload_2018-12-19_18-15-41.png
 
Last edited:

Pedro Caldeira

Active Member
Licensed User
Longtime User
Hello All
I Built a form to act as my report layout to print using JavaFX8, and I am printing in a USB windows POS printer.

It has no standard paper size,like A4, A5, etc, instead it used a 80mm X user defined length

I can print everthing correctly with the execption of the margins.
I am getting huge margins when i should get 0. is there a way to set 0 margins so it prints the form near to the paper margin ?

Regards
 
Last edited:

Peter Simpson

Expert
Licensed User
Longtime User
Hello @stevel05,
I was just wondering if it is at all possible to send the document name to the printer?
For example when printing to PDF files so that the PDF document is automatically saved/named as the document name you chose in the B4J application, also when sending to printers too.

Thank you...

Question answered:
I meant to reply to my original post months ago @stevel05, but it must have slipped my mind.

Anyway I remember looking at the JavaFX modules when I spotted the JobSettings object, I then noticed the GetJobName and SetJobName methods. After thinking about it for a few minutes, I just added the following 2 lines of code before 'PJ.PrintPage/PJ.PrintPage2' when printing the JavaFX document.

B4X:
Dim JS As JobSettings = PJ.GetJobSettings
    JS.SetJobName("Document name")

Now when printing to PDF files or to the print spooler, the document is named 'Document name.pdf' or to whatever you called each individual JobName. It's easy enough to automate emailing invoices to clients, stock item descriptions etc. Now when printing multiple automated documents you can see each individual document names in the print spooler or for individual pdf file names.

Once again, this is an excellent library, thank you Steve...
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
That is very useful Peter, thanks for sharing.
 

stevel05

Expert
Licensed User
Longtime User
Hello All
I Built a form to act as my report layout to print using JavaFX8, and I am printing in a USB windows POS printer.

It has no standard paper size,like A4, A5, etc, instead it used a 80mm X user defined length

I can print everthing correctly with the execption of the margins.
I am getting huge margins when i should get 0. is there a way to set 0 margins so it prints the form near to the paper margin ?

Regards


Sorry for the delay in replying Pedro,

The JavaFX8 print routine will print the passed node as it is laid out on the screen, so the actual margins will be controlled by the margins given in the dialog or print layout plus the x/y position of the nodes in the layout. Ideally you should layout the screen starting at 0,0 and control the margins with the Layout or dialog.
 

Pedro Caldeira

Active Member
Licensed User
Longtime User
Sorry for the delay in replying Pedro,

The JavaFX8 print routine will print the passed node as it is laid out on the screen, so the actual margins will be controlled by the margins given in the dialog or print layout plus the x/y position of the nodes in the layout. Ideally you should layout the screen starting at 0,0 and control the margins with the Layout or dialog.

thanks Stevel05
it was a printer problem. its already solved.
By the way, what is the best way to recevice errors from the usb printer ? not with this lib, i assume !?
 

Peter Simpson

Expert
Licensed User
Longtime User
what is the best way to recevice errors from the usb printer ? not with this lib, i assume !?

Not tested but try using the following
B4X:
PJ.GetJobStatus

At a quick glance 'GetJobStatus' looks like it will report the current printer status as long as you place it the correct place(s). 'GetJobStatus' looks like it will show you then the printer is printing, when the printer has finished printing and also if there is an error whist trying to print.

Enjoy...
 

Pedro Caldeira

Active Member
Licensed User
Longtime User
Not tested but try using the following
B4X:
PJ.GetJobStatus

At a quick glance 'GetJobStatus' looks like it will report the current printer status as long as you place it the correct place(s). 'GetJobStatus' looks like it will show you then the printer is printing, when the printer has finished printing and also if there is an error whist trying to print.

Enjoy...

Thanks,
it works, but the printer itself should return an out of paper error, and it does not. But i get the PRINTING or the DONE status, nevertheless, so the function does
what it is supposed to. The problem is the printer sensor itself.
 

stevel05

Expert
Licensed User
Longtime User
I think that Java will consider the job completed once it has delivered it to the system spooler, I can't remember seeing anything that will communicate with a printer directly.
 

MarcTG

Active Member
Licensed User
Longtime User
I am having some issues while wanting to print something drawn in a canvas...

Everything works fine if I run the software when scaling in Windows is set to 100% or 125% (Under Display Setting in Windows 10), but when I change Windows scaling to 150% or higher the images drawn in the canvas are larger than what I set the scale to be when printed to pdf (the image appear fine in the layout but print results are larger size). Note that this only happens if you change the windows scale and then run the software and try to print.

Any advice on how to handle this stevel05 ?

Thanks!
 

Peter Simpson

Expert
Licensed User
Longtime User
Here is a B4j library written in B4j to access the full Printer modules provided with JavaFX8. Full source code is available.

Hello Steve,
It appears that your JavaFX8 library stops working when compiled in B4J 8.30/8.31. I've only just found out because I was asked by a client to update the wording on a form, and when they got the compiled app back app was no longer printing, just crashing. Actually none of my app are printing anymore that use Print JavaFX8, not even your example in the first post is working for me anymore.

Anyway, I just thought that I would let you know that for me using B4J V8.31 your example jFX8Print0-6.zip is no longer working.

Please let me know what your thoughts are on this matter. It's most probably something my end but either way it would be nice to try to get this looked at from the library wrappers point of view.

I'll have a proper look at your library later this evening as you have been so kind as to post both all the modules and also the compiled library itself.

I already know what developers are going to say. Have you initialized the JavaObject "Object should first be initialized (JavaObject).", I'll just say to please check the original example above which has been working since 2015 ;)

New error message that appears in every single app that is using your library when I try to print.
Tested using JDK 10.0.2, 11.0.1 and 14.0.1 (the last two are from the forum).
Using B4J V8.31.

B4X:
Picked up _JAVA_OPTIONS: -Xmx1024m -Xms1024m -XX:-UseGCOverheadLimit
Waiting for debugger to connect...
Program started.
Error occurred on line: 52 (PrinterJob)
java.lang.RuntimeException: Object should first be initialized (JavaObject).
    at anywheresoftware.b4a.AbsObjectWrapper.getObject(AbsObjectWrapper.java:32)
    at anywheresoftware.b4j.object.JavaObject.getCurrentClass(JavaObject.java:259)
    at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:119)
    at b4j.example.printerjob._showpagesetupdialog(printerjob.java:53)
    at b4j.example.main._appstart(main.java:86)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at anywheresoftware.b4a.shell.Shell.runMethod(Shell.java:632)
    at anywheresoftware.b4a.shell.Shell.raiseEventImpl(Shell.java:237)
    at anywheresoftware.b4a.shell.Shell.raiseEvent(Shell.java:167)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:564)
    at anywheresoftware.b4a.BA.raiseEvent2(BA.java:91)
    at anywheresoftware.b4a.shell.ShellBA.raiseEvent2(ShellBA.java:98)
    at anywheresoftware.b4a.BA.raiseEvent(BA.java:78)
    at b4j.example.main.start(main.java:38)
    at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:919)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$11(PlatformImpl.java:449)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$9(PlatformImpl.java:418)
    at java.base/java.security.AccessController.doPrivileged(Native Method)
    at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:417)
    at javafx.graphics/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:96)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:175)
    at java.base/java.lang.Thread.run(Thread.java:844)

Everything else works perfect, just the printing now crashes in all the apps that uses the library. I'll try reinstalling B4J when I get back just to be on the safe side, but I've already rebooted my system and it's still not working.



Thank you
 

Peter Simpson

Expert
Licensed User
Longtime User
Try it with Java 8.

Java 8?
But the app I'm talking about was created and assembled using OpenJDK 11.x downloaded from the b4x.com download page only about 3 months ago when I last formatted my laptop. Actually I've not had JDK 8 installed on any of my machines for a couple of years now. I've been using B4J with B4JPacker11 and OpenJDK 11. I've been using JDK 10.x from Oracle and also OpenJDK 11.x from this forum with absolutely no issues whatsoever.
 
Top