How to make a submenu within a MenuButton in JavaFX? - javafx

I'm trying to add a submenu to my MenuButton and it doesn't seem to be able to accept a Menu as a child. Is it possible to do this, or do I need to use some other kind of menu? My menu is set up like this:
class DotMenuButton: MenuButton() {
item("item 1").action {
//action 1
}
item("item 2").action {
//action 2
}
//here is where I would like a submenu
menu("sub menu") {
item("sub menu item 1").action {
//sub menu action 1
}
}
}
I am using TornadoFX but if there is a way to do this in plain JavaFX I can adapt it. Any suggestions would be appreciated.
Edit: As of tornadofx 1.7.19-SNAPSHOT the above code now works. :-)

I have committed support for submenus in MenuButton, so your code above will now work with the the latest snapshot release.

Related

QML MenuBar hovering items with mouse pressed

Using Qt Quick Controls 2, you can create a "traditional" menu bar like this:
ApplicationWindow {
id: window
width: 320
height: 260
visible: true
menuBar: MenuBar {
Menu {
title: qsTr("&File")
Action { text: qsTr("&New...") }
Action { text: qsTr("&Open...") }
Action { text: qsTr("&Save") }
Action { text: qsTr("Save &As...") }
MenuSeparator { }
Action { text: qsTr("&Quit") }
}
Menu {
title: qsTr("&Edit")
Action { text: qsTr("Cu&t") }
Action { text: qsTr("&Copy") }
Action { text: qsTr("&Paste") }
}
Menu {
title: qsTr("&Help")
Action { text: qsTr("&About") }
}
}
}
This works ok, but when the user presses on a menu and then drag the mouse while pressed, on the menus are not hovered. In order to hover over the menus, the mouse cannot be in a pressed state (using Qt Widgets https://doc.qt.io/qt-5/qtwidgets-mainwindows-menus-example.html this is not needed).
Is there a way to make the MenuBar, hover over items while the mouse is pressed?
When you did through this doc
https://doc.qt.io/qt-5/qml-qtquick-controls2-action.html
You then go to this doc and have high hopes when reading about "hoverEnabled"
https://doc.qt.io/qt-5/qml-qtquick-controls2-toolbutton-members.html
You really need these two signals entered() exited() from a MouseArea.
https://doc.qt.io/qt-5/qml-qtquick-mousearea.html
The short hopeful hack is to see if the documentation is "just wrong" and somewhere deep in the class structure they declared a MouseArea for a button and you really do get entered() and exited().
How else would you be able to implement hoverEnabled()? The "widget" has to know the mouse entered and did not yet exit. They may well be consuming that, but you should be able to dig through the source and find child entity that you can connect to the signal of.
There is not an easy way.
The underlying issue is that MenuItem is implemented as a Button.
When you press a button and release on another button, none of them register a click.
However, on traditional menus, if you press an item and release on another one, the item that registers the release is triggered.
The public API offered by QtQuick Controls 2 does not seem to offer a way to easily change this. So to get what you want Isee the following solutions:
Use Qt.labs.platform to build your menus, these will be native menus so they should have the correct behavior. However it still in a preview state and i have not tested them.
Reimplement MenuItem. Since it is part of Qt Quick Controls 2 it is easy to reimplement your own MenuItem, see Qt documentation. However, you will have to use MouseArea to catch user inputs and force the behavior you want.
EDIT
The 2nd solution won't work. In Qt once an Item (or a QWidget) accepts a press event, it grabs the mouse until it is released. So reimplementing MenuItem and adding MouseArea to them won't help.
Knowing that it seems that the solution would to reproduce what QMenu is doing: You need to have a single Item responsible for handling mouse events. So you should not let each MenuItem handles mouse events individually. Instead you should handle mouse events at the Menu or MenuBar level, process the events and manually change the MenuItems.
At this point I do not know if it is easier to customize Menu and MenuItem or to jus write your own Menu from scratch.

How to clear QML Menu correctly

I have a dynamicaly created Menu. The code below is just to understand the hierarchy
Menu {
id: mainMenu
MenuItem {
text: "item"
}
Menu {
title: "submenu"
MenuItem {
text: "submenuitem"
}
}
}
Now I need to remove all mainMenu's content. As I can see in the documentation, Menu have methods removeItem, takeItem and takeMenu. Using takeMenu and count property I can access menu's children and remove them recursively. But what if I don't know the order and type of menu items? Item has not property count. I need some universal solution that can remove the item itself, and if it is a menu then remove all it's children.
I don't know if it's the cleanest solution, but it works well to clear your example.
while(mainMenu.items.length > 0)
mainMenu.removeItem(mainMenu.items[0]);
You don't need to go recursively because when you remove an Item, all its childItems are removed with it.

Smooth scrolling in JavaFX TableView

I am writing a small chat application in Kotlin with TornadoFX that works so far.
I am currently trying to make it more visually appealing when receiving new messages.
The messages are in a TableView (sender - message) but scrolling to new messages isn't smooth like I would like.
The snippet where I need help is relatively short:
addEventHandler(ScrollToEvent.ANY) {
it.consume()
timeline {
val keyValue = KeyValue(/* property to change */, /* target value */, Interpolator.EASE_OUT)
keyframe(0.25.seconds) {
this.plusAssign(keyValue)
}
}
}
In general I need help figuring out which property to change and what the target should be in this line:
KeyValue(/* property to change */, /* target value */, Interpolator.EASE_OUT)
Ok, I found the solution.
One needs to lookup the ScrollBar the TableView provides, once enough rows are present (and when scrolling actually does anything).
From TornadoFX JavaFX Sync Scroll across tableviews, I adapted the lookup and came up with this, working, code:
addEventHandler(ScrollToEvent.ANY) {
it.consume()
timeline {
val scrollBar = lookupAll(".scroll-bar").first() as ScrollBar
val keyValue = KeyValue(scrollBar.valueProperty(), scrollBar.max, Interpolator.EASE_OUT)
keyframe(0.5.seconds) {
this.plusAssign(keyValue)
}
}
}

Disable default animation in TabPane

I would like to disable the default animation when I create new Tab.
Tab tabA = new Tab();
tabA.setText("Tab A");
tabPane.getTabs().add(tabA);
Is this possible?
Now, when Java 8 is finally out, one has the possibility to disable the animation, using CSS:
tabPane.setStyle("-fx-open-tab-animation: NONE; -fx-close-tab-animation: NONE;");
The default setting is "GROW".
Not easily. The animation logic is part of the TabPaneSkin:
From TabPane.java:
#Override protected Skin<?> createDefaultSkin() {
return new TabPaneSkin(this);
}
But as far as I know, there is no way to make small adjustments to the default skin (which is hidden in the com.sun.javafx.scene.control.skin package) but you'd have to implement a whole new skin.

Trying to bind a class to a toggled div for CSS targeting

I have an accordion and unfortunately there is no active or current class assigned to the qafp-faq div which serves as a container for each item.
My main objective is to add icons indicating toggle state.
I added:
$( ".qafp-faq-anchor" ).click(function() {
$( this).find( ".fa-caret-right" ).toggleClass( "open", 1000 );
return false;
});
Which works great for allowing me to target the title if I open a div via the title, but not when the accordion behavior hides an open div when clicking another div.
You can see the problem here: http://jsfiddle.net/Qzwvr/2/
The solution I'm really after is how I can add a class to the qafp-faq div whenevr it is toggled.
I've definitely been learning a lot about jQuery and hope I can figure this out. Thank you.
The easiest way to do this would be to change it from toggleClass into a removeClass and addClass
$(".qafp-faq-anchor").click(function () {
// Remove open class if this is open
if($(this).find('.fa-caret-right').hasClass('open')) {
$(this).find('.fa-caret-right').removeClass('open');
}
else {
// Removes open class no matter where it is
$('.open').removeClass( "open", 1000 );
// Adds open class to clicked element
$(this).find(".fa-caret-right").addClass("open", 1000);
return false;
}
});
Updated jsFiddle
If you have multiple accordions you could make the selector for removing the open class more specific if you want multiple open at the same time

Resources