0

I implement autocomplete textfield based on this answer here:

https://stackoverflow.com/a/40369435/9047625

I use ContextMenu which height is long enough so ~15 items can be visible at a time, when there are more items available arrows show at the bottom and on the top of the contextmenu.

The problem i have is that when i scroll down on the items and i input some other words my context menu remains scrolled down, and i have to scroll it up manually to show the items even if there is only 1 item.

I tried on different ways to access the scroll bar of ContextMenu, so i can scroll it to top every time i input a new character in the field, but i couldn't figure a way to do so..

Is there any way to scroll up the contextmenu or to set the focus to the first element (top) whenever i input something in the field for auto completion ?

I hope i managed to explain my problem correctly and i'm thankful for the replies in advance..

1 Answers1

2

ContextMenu keeps an up and down arrows as a special type of MenuItem called ArrowMenuItem. The structure goes like this:

ContextMenu > ContextMenuSkin > ContextMenuContent > ArrowMenuItem

ArrowMenuItem is a non-static package-private class. ContextMenuContent has two instances of this class: upArrow and downArrow and these two instances are shown only when the items can't fit in the ContextMenu. ContextMenuContent uses a Timeline to scroll the ContextMenu, so when an ENTERED type MouseEvent is fired on any of those arrow items, the Timeline starts scrolling the content up or down based on hovered ArrowMenuItem. The Timeline stops when the mouse exits that region. ContextMenuContent has a method scroll that is all you need, but unfortunately, this method is not public.

Possible solutions:

  1. Extend ContextMenuSkin, ContextMenuContent to expose the method scroll. In this way, you can call lookup ContextMenuContent from the skin and use that method to scroll all the way up or down.

  2. Use menu-up-arrow and menu-down-arrow style classes to lookup the arrow nodes. Once you get the arrow node, you can stimulate a mouse ENTERED event to make the ContextMenu scroll up or down. Note that in this way the user has to wait until the scrolling finishes since the Timeline has a fixed scrolling rate. Then you need to consume this event after the scrolling is over. Sample code:

ContextMenuSkin skin = (ContextMenuSkin) contextMenu.getSkin();
Node up = skin.getNode().lookup(".menu-up-arrow");
Node down = skin.getNode().lookup(".menu-down-arrow");
MouseEvent enteredEvent = new MouseEvent(MouseEvent.MOUSE_ENTERED, ...); // the remaining parameters
if (shouldScrollUp) {
    up.fireEvent(enteredEvent);
} else {
    down.fireEvent(enteredEvent);
}
// consume the event after scroll is over
  1. Using Reflection:
private static void scrollContextMenuUp(ContextMenu contextMenu) {
    try {
        ContextMenuSkin skin = (ContextMenuSkin) contextMenu.getSkin();
        ContextMenuContent content = (ContextMenuContent) skin.getNode();
        Method method = content.getClass().getDeclaredMethod("scroll", double.class);
        method.setAccessible(true);
        method.invoke(content, 12.0); // change this double value to scroll more
    } catch (Exception e) {
        System.err.println("Unable to scroll due to: " + e.getMessage());
    }
}
Miss Chanandler Bong
  • 4,081
  • 10
  • 26
  • 36
  • I already extended ContextMenu from my custom context menu so I can set its height. Im not exactly sure what do u mean by extending ContextMenu Skin, ContextMenuContent since I can't extend multiple classes from my class? – Nikola Obradovic Jan 11 '20 at 06:56
  • Sorry for posting double, I'm on my phone, I really would love to make first solution work, and if I can't figure it out I will go with second, I just wanted to ask u how do I know when the scroll is actually over and I can consume the event? And thank you very much I've been struggling with this really for a long time, I'm really thankful for ur time to answer this – Nikola Obradovic Jan 11 '20 at 06:58
  • and for 1st solution it seems like scroll method has default access modifier so i cannot access it from different package.. :/ – Nikola Obradovic Jan 11 '20 at 12:43
  • You can override `createDefaultSkin()` to return your own `ContextMenuSkin`. What version of JDK are you using? – Miss Chanandler Bong Jan 11 '20 at 14:08
  • I'm using jdk1.8.0_221. I see i can override createDefaultSkin() from my class that extends ContextMenu(which i made so i can set the height of the contextmenu). But i don't understand how can i access the scroll method.. thanks again for ur help i really appreciate it – Nikola Obradovic Jan 11 '20 at 14:20
  • I didn't notice that `ContextMenuSkin` has a private final `ContextMenuContent` which you can't change by extending. If you have no option, you can access `scroll` method by using reflection. – Miss Chanandler Bong Jan 11 '20 at 14:47
  • I edited the answer to include the code that uses reflection – Miss Chanandler Bong Jan 11 '20 at 15:14
  • Thanks a bounch! It worked :) I'm deeply grateful to your time and patience! – Nikola Obradovic Jan 11 '20 at 15:23