B4J Tutorial FXThemes

Now I have a windows 11 PC and thanks to a prod from one of @Cableguy posts on another thread, I finally got round to looking at an alternative to my Replacement Titlebar lib.

This is a quick example of how to use this FXThemes library on Github.

It is not difficult to use, and probably not worth wrapping as a library. The example uses JavaObject. You can read the javaDoc to see what else is available.

You need to download the fxthemes-1.6.0 jar from Maven Central, and the jna-5.13.0 and jna-platform-5.13.0 if you don't already have them. The library was compiled with Java 21, but it is working for my test on Java 19. Uncomment the #JavaCompilerPath assignment and point it to a suitable java compiler.

After about 15 mins I got this:

1740694265411.png

Which is probably what we've all been after.

This is the code:

ThemesFX with JavaObject:
#Region Project Attributes
    #MainFormWidth: 600
    #MainFormHeight: 600
#End Region

#AdditionalJar:fxthemes-1.6.0
#AdditionalJar: jna-5.13.0
#AdditionalJar: jna-platform-5.13.0

'#JavaCompilerPath: 19,C:\Java\jdk-19.0.2\bin\javac.exe ' Or wherever a suitable java version lives on your machine.

#VirtualMachineArgs: --add-opens javafx.graphics/javafx.stage=ALL-UNNAMED --add-exports javafx.graphics/com.sun.javafx.tk.quantum=ALL-UNNAMED

Sub Process_Globals
    Private fx As JFX
    Private MainForm As Form
    Private xui As XUI
    Private Button1 As B4XView
End Sub

Sub AppStart (Form1 As Form, Args() As String)
    MainForm = Form1
    MainForm.RootPane.LoadLayout("Layout1")
    MainForm.Show
 
 
    Dim MF As JavaObject = MainForm
    Dim Stage As JavaObject = MF.GetField("stage")
 
 
    Dim Manager As JavaObject
    Dim Factory As JavaObject
    Factory.InitializeStatic("com.pixelduke.window.ThemeWindowManagerFactory")
    Manager = Factory.RunMethod("create",Null)
    Manager.RunMethod("setWindowBackdrop",Array(Stage,"ACRYLIC"))
    Manager.RunMethod("setWindowCornerPreference",Array(Stage,"ROUND"))
    Manager.RunMethod("setWindowFrameColor",Array(Stage,fx.Colors.Black))
 
End Sub

Have fun and let me know how you get on with it. As always, check the Licence if you are going to use it commercially.

If you want to use it in a Packaged app, you will need to add the complimentary VMARGS: for the add-opens and --add-exports. As this requires at least Java 19, and possibly 21 the packager won't currently be available.
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
Styling themes for the window. Also maintains compatibility with the Windows Layout options.
 

Magma

Expert
Licensed User
Longtime User
Hi there @stevel05
you are always going a step higher !

1) Well tried at "Windows 10" need to check if Windows 11 (so added an if windows11) and then use the javaobject commands..
But also if you go to compile it in windows 10 i am getting this - is it possible to compile it in windows 10?

errors at debug with including windows check... after adding i can run it
b4xpagesmanager._vvvvvvvvvvvv2 (java line: 307)
java.lang.RuntimeException: java.lang.RuntimeException: Method: setWindowBackdrop not found in: com.pixelduke.window.Win10ThemeWindowManager
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:522)
at anywheresoftware.b4a.keywords.Common.CallSubNew2(Common.java:468)
at b4j.example.b4xpagesmanager._vvvvvvvvvvvv2(b4xpagesmanager.java:307)
at b4j.example.b4xpagesmanager._vvv2(b4xpagesmanager.java:722)
at b4j.example.b4xpagesmanager._v7(b4xpagesmanager.java:113)
at b4j.example.b4xpagesmanager._v0(b4xpagesmanager.java:120)
at b4j.example.b4xpagesmanager._initialize(b4xpagesmanager.java:491)
at b4j.example.main._appstart(main.java:60)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:578)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:98)
at b4j.example.main.start(main.java:37)
at javafx.graphics/com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$9(LauncherImpl.java:847)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runAndWait$12(PlatformImpl.java:484)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
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:184)
at java.base/java.lang.Thread.run(Thread.java:1589)
Caused by: java.lang.RuntimeException: Method: setWindowBackdrop not found in: com.pixelduke.window.Win10ThemeWindowManager
at anywheresoftware.b4j.object.JavaObject$MethodCache.getMethod(JavaObject.java:363)
at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:120)
at b4j.example.b4xmainpage._b4xpage_created(b4xmainpage.java:62)
at b4j.example.b4xmainpage.callSub(b4xmainpage.java:156)
at anywheresoftware.b4a.keywords.Common.CallSub4(Common.java:497)
... 21 more


compiling errors after including windows check...
B4JPackager11 Version 1.40
Exe name: snap_to_layout.exe
Only Java 11, 14 and 16 are supported (Tools - Configure Paths).
build folder: D:\B4J\SNAP_T~1\B4J\Objects\temp\build
InputJar: D:\B4J\snap_to_layout\B4J\Objects\snap_to_layout.jar
Running: D:\B4J\SNAP_T~1\B4J\Objects\temp\FindDosPath.exe
Running: C:\java\bin\jar
Package name: b4j.example
Running: C:\java\bin\jdeps
Exception in thread "main" java.lang.module.FindException: Module com.sun.jna not found, required by com.pixelduke.fxthemes
at java.base/java.lang.module.Resolver.findFail(Resolver.java:892)
at java.base/java.lang.module.Resolver.resolve(Resolver.java:192)
at java.base/java.lang.module.Resolver.resolve(Resolver.java:141)
at java.base/java.lang.module.Configuration.resolve(Configuration.java:420)
at java.base/java.lang.module.Configuration.resolve(Configuration.java:254)
at jdk.jdeps/com.sun.tools.jdeps.JdepsConfiguration$Builder.build(JdepsConfiguration.java:564)
at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.buildConfig(JdepsTask.java:604)
at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:558)
at jdk.jdeps/com.sun.tools.jdeps.JdepsTask.run(JdepsTask.java:534)
at jdk.jdeps/com.sun.tools.jdeps.Main.main(Main.java:50)


2) Thought that CableGuy need a total custom title bar (without the default bar)... and showing "snap layout"
I gave some tries but i think i am too far...
and getting some errors too, I am not a java developer so i am guessing what to use...

I am attaching my example that also includes your code too with comments..

If you have time - please check...

here the errors getting for my snaptolayout:
Call B4XPages.GetManager.LogEvents = True to enable logging B4XPages events.
b4xmainpage._vvvv3 (java line: 155)
java.lang.RuntimeException: Method: GetSystemMenu not found in: jdk.proxy1.$Proxy0
at anywheresoftware.b4j.object.JavaObject$MethodCache.getMethod(JavaObject.java:363)
at anywheresoftware.b4j.object.JavaObject.RunMethod(JavaObject.java:120)
at b4j.example.b4xmainpage._vvvv3(b4xmainpage.java:155)
at b4j.example.b4xmainpage._button1_mouseentered(b4xmainpage.java:89)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:104)
at java.base/java.lang.reflect.Method.invoke(Method.java:578)
at anywheresoftware.b4a.BA.raiseEvent2(BA.java:111)
at anywheresoftware.b4a.BA.raiseEvent(BA.java:98)
at anywheresoftware.b4j.objects.NodeWrapper$6.handle(NodeWrapper.java:164)
at anywheresoftware.b4j.objects.NodeWrapper$6.handle(NodeWrapper.java:1)
at javafx.base/com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:234)
at javafx.base/com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
at javafx.base/com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base/com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
at javafx.base/com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
at javafx.base/com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
at javafx.base/com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
at javafx.base/javafx.event.Event.fireEvent(Event.java:198)
at javafx.base/com.sun.javafx.event.EventQueue.fire(EventQueue.java:48)
at javafx.graphics/javafx.scene.Scene$MouseHandler.handleEnterExit(Scene.java:3811)
at javafx.graphics/javafx.scene.Scene$MouseHandler.process(Scene.java:3882)
at javafx.graphics/javafx.scene.Scene.processMouseEvent(Scene.java:1878)
at javafx.graphics/javafx.scene.Scene$ScenePeerListener.mouseEvent(Scene.java:2623)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:411)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler$MouseEventNotification.run(GlassViewEventHandler.java:301)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleMouseEvent$2(GlassViewEventHandler.java:450)
at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:424)
at javafx.graphics/com.sun.javafx.tk.quantum.GlassViewEventHandler.handleMouseEvent(GlassViewEventHandler.java:449)
at javafx.graphics/com.sun.glass.ui.View.handleMouseEvent(View.java:557)
at javafx.graphics/com.sun.glass.ui.View.notifyMouse(View.java:943)
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:184)
at java.base/java.lang.Thread.run(Thread.java:1589)

Thanks in advance..

ps: In start i was think using somehow the sendkeys "win-z" which is the way to show snap to layout, but this works only if showing titlebar of a window...
 

Attachments

  • snap_to_layout.zip
    5 KB · Views: 21

stevel05

Expert
Licensed User
Longtime User
FXThemes apparently does work with Windows 10, can't try it.

I got your project running and it displays the Window Menu, but it is not responsive. I don't think that was what you were after. But there were two issues with the code. You had created a User32Extended class, but you had loaded the standard class, not your extended one. GetSystemMenu returns an object pointer not a Long.
 

Attachments

  • stl.zip
    4.4 KB · Views: 21

Magma

Expert
Licensed User
Longtime User
Trying to compile at win10 to move it, at my virtualbox (win11) but can't compile it...
FXThemes apparently does work with Windows 10, can't try it.

I got your project running and it displays the Window Menu, but it is not responsive. I don't think that was what you were after. But there were two issues with the code. You had created a User32Extended class, but you had loaded the standard class, not your extended one. GetSystemMenu returns an object pointer not a Long.
1740741066803.png
 

stevel05

Expert
Licensed User
Longtime User
Ohh, you're trying to package it? I don't think the packager currently supports java > 14. Unless I missed an announcement.
 

Magma

Expert
Licensed User
Longtime User
ok pass it, running it like that:
c:\java\bin\javaw.exe" -jar --add-opens javafx.controls/com.sun.javafx.scene.control.skin=ALL-UNNAMED --module-path c:\java\javafx\lib --add-modules javafx.controls,javafx.web "snap_to_layout.jar"

ohhhh...

I didn't want to show that "menu"... i wanted to show "snap to layout"

is it possible ?
 

stevel05

Expert
Licensed User
Longtime User
i wanted to show "snap to layout"

is it possible ?
I don't know, but if you can find a way you might have the same problem as with the menu that is displayed now, displaying it does not automatically set up the process of interacting with it.
 

Magma

Expert
Licensed User
Longtime User
Tried with AI to find solution...
saying that with PostMessage i can simulate the mouseover of maximize button... but...
B4X:
#if java
import com.sun.jna.*;
import com.sun.jna.platform.win32.*;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinUser.*;

public interface User32Extended extends User32 {
    User32Extended INSTANCE = Native.load("user32", User32Extended.class);

    boolean GetCursorPos(POINT lpPoint);

    // FIX: PostMessage() should return a boolean and use `int` for WPARAM/LPARAM
    boolean PostMessage(HWND hWnd, int msg, int wParam, int lParam);

    int WM_SYSCOMMAND = 0x112;
    int SC_MAXIMIZE = 0xF030;
}

public User32Extended getUser32(){
    return User32Extended.INSTANCE;
}
#End If

Sub ShowSnapLayouts(MyForm As Form)
    Dim user32 As JavaObject = Me.as(JavaObject).RunMethod("getUser32",Null)
    user32.RunMethod("PostMessage", Array(hwnd, 0x112, 0xF030, 0))
end sub

Getting...
java.lang.reflect.InvocationTargetException

Seems something wrong with Postmessage may be not right way use of values... but can't find it...
 

Daestrum

Expert
Licensed User
Longtime User
Very nice. Runs happily on Java 22
 
Last edited:

stevel05

Expert
Licensed User
Longtime User
Seems something wrong with Postmessage may be not right way use of values... but can't find it...
Yes, Looking at the Windows document it appears that there is no PostMessage method. PostMessageA seems to work, but the message sets the window full screen. Need to try and find the right message. Also the Parameter types need to be matched, so I put in a wrapper method to do that.

B4X:
#if java
import com.sun.jna.*;
import com.sun.jna.platform.win32.*;
import com.sun.jna.platform.win32.WinDef.*;
import com.sun.jna.platform.win32.WinUser.*;

public interface User32Extended extends User32 {
    User32Extended INSTANCE = Native.load("user32", User32Extended.class);

    boolean GetCursorPos(POINT lpPoint);
    HMENU GetSystemMenu(HWND hWnd, boolean bRevert);
    boolean TrackPopupMenu(HMENU hMenu, int uFlags, int x, int y, int nReserved, HWND hWnd, RECT prcRect);

    int TPM_RETURNCMD = 0x0100;  // Track menu as system command
    void PostMessageA(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
   
   
}

public void PostMessageWrapper(HWND hwnd, long tmsg, long twParam, long tlParam){

    UINT msg = new UINT(tmsg);
    WPARAM wParam = new WPARAM(twParam);
    LPARAM lParam = new LPARAM(tlParam);
    getUser32().PostMessageA(hwnd,msg,wParam,lParam);
}

public User32Extended getUser32(){
    return User32Extended.INSTANCE;
}



#End If

Sub ShowSnapLayouts(MyForm As Form)
    Dim hwnd As JavaObject = GetHwnd(MyForm)
'    Dim user32 As JavaObject = Me.as(JavaObject).RunMethod("getUser32",Null)
    Dim msg As Long = 0x112
    Dim wParam As Long = 0xF030
    Dim lParam As Long = 0

    Me.as(JavaObject).RunMethod("PostMessageWrapper", Array(hwnd, msg, wParam, lParam))
End Sub
 

stevel05

Expert
Licensed User
Longtime User
Need to try and find the right message.
I asked Perplexity, apparently there is no such message. I asked what 0x112 related to and it gave me this answer:
The hexadecimal value 0x112 corresponds to the Windows message WM_INITDIALOG5. This message is sent to a dialog box procedure immediately before a dialog box is displayed. It initializes the dialog box and allows the dialog procedure to customize the dialog box before it is shown to the user5.

It's worth noting that this message is part of a larger set of system-defined messages used in Windows programming, particularly with functions like PostMessage() and SendMessage() in the User32 library56.
 

Daestrum

Expert
Licensed User
Longtime User
Only problem I have is that packager cant find com.sun.jna.platform when trying to package the app.
 

stevel05

Expert
Licensed User
Longtime User
Only problem I have is that packager cant find com.sun.jna.platform when trying to package the app.
No I did try it too but I assume it's because the library requires java 21 (seems to work on 19) but the packager says it will only currently package up to Java 16.
 

Daestrum

Expert
Licensed User
Longtime User
Odd as all my PyBridge apps package ok using same java 22 JDk
 
Top