How to make JSplitPane auto expand on mouse hover? - java

I want to make a Java program with Swing layout where there is a menu on the left that is expanded when a mouse hovers over the menu area, but auto contracts after your mouse leaves.
I am trying to imitate the effect of something like mobile Youtube for Android, or Weebly's editor. Fro those who don't know, both layouts have menus on the left that expand when your mouse hovers over them. Then after your mouse leaves the area, the menu contracts again and is out of view.
I was able to create the JSplitPane containing my menu successfully, but I have no idea how to make it automatically expand when the user's mouse hovers over the JSplitPane component, or how to make it contract after the mouse leaves the area.
In case anyone is wondering why: This type of menu is easy for the user to use but because it hides away when unneeded, allows me to have more space for the main part of the program.
Any help would be appreciated thanks!

Well make use of jSplitPan.setDividerLocation(location);: Sets the location of the divider. location - an int specifying a UI-specific value (typically a pixel count).
jSplitPane1.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent evt) {
// use evt.getSource() if needed
jSplitPan1.setDividerLocation(location);
}
});
You will probably need to compute the divider location a little bit intelligently. hints: by computing the relevant preferred-size hints. The part relevant should be discovered by you.
Take a look into official tutorial page to know: How to use MouseMotionListeners including other event listeners.

There are two basic problems...
You need to detect when the mouse hovers over the divider and
When it exists the "menu"
The second is relatively easy, you can use a MouseListener and monitor the mouseExit event, setting the position of the split pane divider as the mouse leaves.
This is complicated though, as if the user exists the "menu" by crossing over the divider, this may trigger the "menu" to made visible again...
The first problem is more complicated, as the JSplitPane contains three components, the left and right components, but also a divider component. JSplitPane doesn't actually allow access to the divider, which is rather annoying and because it uses it's own mouse listener, it blocks mouse events going to the split itself.
Instead, you need to gain access to it via the UI delegate.
Once you've set up the mouse listener, you need to ensure that the menu will only be shown when the menu is hidden, for this, I simply checked the size of the "menu" component
For example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.plaf.basic.BasicSplitPaneDivider;
import javax.swing.plaf.basic.BasicSplitPaneUI;
public class TestSpltPane {
public static void main(String[] args) {
new TestSpltPane();
}
public TestSpltPane() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
final JPanel left = new JPanel();
left.setBackground(Color.RED);
left.setPreferredSize(new Dimension(100, 100));
JPanel right = new JPanel();
right.setBackground(Color.BLUE);
right.setPreferredSize(new Dimension(100, 100));
final JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, right);
sp.setDividerLocation(0);
BasicSplitPaneDivider divider = ((BasicSplitPaneUI) sp.getUI()).getDivider();
divider.addMouseListener(new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
if (left.getWidth() == 0) {
sp.setDividerLocation(100);
}
}
});
left.addMouseListener(new MouseAdapter() {
#Override
public void mouseExited(MouseEvent e) {
sp.setDividerLocation(0);
}
});
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(sp);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Related

How to make a JButton appear "as rolled over"

A JButton has a different appearance when rolled over. That appearance is different from the "selected" appearance.
I want to display my button "as if" it was rolled over, so that user understands that if he hits the Return key, that button will be triggered.
The problem is not the same as setting the default button, because I am in a situation where I really want to get the user to understand that although he wouldn't expect it, if he hits enter that button will be activated. More details below for those who want some. Setting button as default would make button the default one, but wouldn't be significantly signaling to the user.
In my case the strong enough signal is the appearance that the button has when it is rolled over.
How to do that ?
More details on the situation, for those who want some :
I have a list of buttons representing options, and a text box at the top, which acts as a filter on the buttons
when filter is such that only one option remains, hitting return directly clicks that option's button
in reality user would have had to select the button with tab or arrow, and then hit enter.
since that shortcut is not obvious I want to signal it to user
Based on your question, what you "really" want, is the JRootPane#setDefaultButton, which will highlight the button, in a OS specific manner and if the user presses the default "action" key (Enter in most cases) will call it's ActionListener
For example...
The "normal" button is just a plain old JButton, the Hacked sets the rollOver to enabled and Default has been set as the default button for the JRootPane
As you can see, you're suggest fix does nothing on MacOS, don't know what it might do on other platforms
I suggest you have a look at How to Use Root Panes for more details
import java.awt.EventQueue;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Action;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JRootPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private JButton asDefault = new JButton("Default");
public TestPane() {
JButton hack = new JButton("Hacked");
hack.getModel().setRollover(true);
hack.setRolloverEnabled(true);
asDefault.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.out.println("Defaulted");
}
});
add(new JButton("Normal"));
add(hack);
add(asDefault);
}
#Override
public void addNotify() {
super.addNotify();
JRootPane rootPane = SwingUtilities.getRootPane(this);
if (rootPane != null) {
rootPane.setDefaultButton(asDefault);
}
}
}
}
So using button.getModel().setRollover(true); doesn't work on all platforms and on those platforms it does work on, I suspect the user will simply need to move the mouse through it to return it to normal
button.getModel().setRollover(true);

Added components are not painted until parents are repainted from another source

When I modify a component in a component tree of any depth, the modifications usually show automatically, immediately, without need for me to take any action to that end.
Not so when the modification is an addition of a new child.
Furthermore, if I want to force the repaint using any of the methods appropriate (as far as I understand the API), this has no tangible effect as well.
Only when a new modification to the existing tree - including the added, but still invisible component - is made, does the added child appear.
Here is an example, that will render a black window with an "Add" button at the bottom. Clicking the button will have no effect. Resizing or minimising the window will cause as many white "XX" strings to appear, as the button has been pressed beforehand.
I would, of course, very much like to have the additions appear immediately one by one, whenever the button is pressed.
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Applikation
{
public static void main(String[] argumente)
{
Box dummy = new Box(BoxLayout.Y_AXIS);
JFrame window = new JFrame();
JPanel panel = new JPanel();
panel.setBackground(Color.black);
window.add(dummy);
dummy.add(panel);
JButton button = new JButton("Add");
button.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e)
{
JLabel white = new JLabel("XX");
white.setBackground(Color.white);
white.setForeground(Color.white);
panel.add(white); // only visible after resizing window or switching focus to another program and back
panel.invalidate(); // does nothing
panel.repaint(); // does nothing
panel.repaint(200); // does nothing
} });
dummy.add(button);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.setVisible(true);
}
}
What am I missing?
Note that this is essentially a duplicate of this question, but as can bee seen in my example code, none of its answers do apply: They empirically do not work.

How to implement a Java Swing application to Touch Screen

We have built a Point of Sale system and now we require to implement it to Touch screens? Do we need to change any code in turn to allow this to work.
And we are using the Keyboard to enter values - let's say quantity - Is there a java way of popping up a key board (like android) when I focus on a JTextField?
Here is a simple example on how to implement a pop-up keyboard:
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
#SuppressWarnings("serial")
public class MainFrame extends JFrame
{
private JTextField txt;
private PopUpKeyboard keyboard;
public MainFrame()
{
super("pop-up keyboard");
setDefaultCloseOperation(EXIT_ON_CLOSE);
txt = new JTextField(20);
keyboard = new PopUpKeyboard(txt);
txt.addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
Point p = txt.getLocationOnScreen();
p.y += 30;
keyboard.setLocation(p);
keyboard.setVisible(true);
}
});
setLayout(new FlowLayout());
add(txt);
pack();
setLocationByPlatform(true);
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new MainFrame().setVisible(true);
}
});
}
private class PopUpKeyboard extends JDialog implements ActionListener
{
private JTextField txt;
public PopUpKeyboard(JTextField txt)
{
this.txt = txt;
setLayout(new GridLayout(3, 3));
for(int i = 1; i <= 9; i++) createButton(Integer.toString(i));
pack();
}
private void createButton(String label)
{
JButton btn = new JButton(label);
btn.addActionListener(this);
btn.setFocusPainted(false);
btn.setPreferredSize(new Dimension(100, 100));
Font font = btn.getFont();
float size = font.getSize() + 15.0f;
btn.setFont(font.deriveFont(size));
add(btn);
}
#Override
public void actionPerformed(ActionEvent e)
{
String actionCommand = e.getActionCommand();
txt.setText(txt.getText() + actionCommand);
}
}
}
If you don't need multi-touch, the normal mouse drivers for use with most touch screen controllers will just have the touch-screen emulate a normal mouse where a finger touching the screen is emulated as a mouse click.
As for a virtual keyboard, there are crummy ones built into Windows and MacOSX but it would probably be best to build one into the application if you can.
If you need multi touch or have issues with specific touch screen controllers, there are a few options.
Your best bet in swing, at least on windows, seems to be this project: http://www.michaelmcguffin.com/code/JWinPointer/
JavaFX appears to have touch support, Intel has a tutorial: https://software.intel.com/en-us/articles/using-javafx-to-implement-multi-touch-with-java-on-windows-8-desktop. You might be able to get this working with swing somehow as there are methods to host Swing in JavaFX and JavaFX in Swing, you might look for other answers to accomplish interop between both.
There was project MT4J, but it seems to be defunct. It doesn't seem to work with Swing or JavaFX.
You should be able to provide your own virtual keyboard through the use of something like a JWindow and the KeyboardFocusManager
We implemented a custom Look-and-feel for our Swing application with touch support to make everything just look bigger (all buttons, checkboxes, ..., even JTree instances) so that it is easy to modify them using a finger.
Such a solution might save you the work to convert all your UI's to a touch-friendly UI.
You should change your user experience and interaction design, thus change the code. Check out Windows UX guidelines for touch - https://msdn.microsoft.com/en-us/library/windows/desktop/dn742468.aspx

Fire mouse event on underlying components

I'm looking for a way to pass mouse events to components covered by other components. To illustrate what I mean, here's a sample code. It contains two JLabels, one is twice smaller and entirely covered with a bigger label. If you mouse over the labels, only the bigger one fires mouseEntered event however.
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;
import javax.swing.border.LineBorder;
public class MouseEvtTest extends JFrame {
public MouseEvtTest() {
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setLayout(null);
setSize(250, 250);
MouseAdapter listener = new MouseAdapter() {
#Override
public void mouseEntered(MouseEvent e) {
System.out.printf("Mouse entered %s label%n", e.getComponent().getName());
}
};
LineBorder border = new LineBorder(Color.BLACK);
JLabel smallLabel = new JLabel();
smallLabel.setName("small");
smallLabel.setSize(100, 100);
smallLabel.setBorder(border);
smallLabel.addMouseListener(listener);
add(smallLabel);
JLabel bigLabel = new JLabel();
bigLabel.setName("big");
bigLabel.setBorder(border);
bigLabel.setSize(200, 200);
bigLabel.addMouseListener(listener);
add(bigLabel, 0); //Add to the front
}
public static void main(String[] args) {
new MouseEvtTest().setVisible(true);
}
}
What would be the best way to fire mouse entered event on the smaller label when cursor is moved to the coordinates above it? How would it work in case where there would be multiple components stacked on top of each other? What about the remaining mouse events, like mouseClicked, mousePressed, mouseReleased, etc.?
Take a look at Alexander Potochkin's blog entry on A Well-Behaved GlassPane
In your listener:
bigLabel.dispatchEvent(mouseEvent);
Of course, you will have to define bigLabel as final
Well to understand whats happening you need to understand how Z-Ordering works. As a quick overview, the component that was added last is painted first. So in your case you want to add the small component before the big component.
// add(bigLabel, 0); //Add to the front
add(bigLabel); // add to the end so it is painted first
The OverlayLayout might help explain this better and give you another option.

setBorder method for JLabel causing paint problem

I have a custom class that extends JLabel. For specific instances of that class, I want to add some spacing to the text on the left side. I need the spacing as I'm setting the background of this JLabel and I don't want the text to bump up right next to the edge of the colored background. I fished around quite a bit and implemented this (inside the paint function):
if (condition) {
bgColor = Color.red;
setBackground(bgColor);
setOpaque(true);
// This line merely adds some padding on the left
setBorder(BorderFactory.createEmptyBorder(0, 10, 0, 0));
}
else {
setOpaque(false);
}
This appears to work in that it adds the spacing I want, however it has an unfortunate side effect in that it appears to break the repainting of the whole rest of the application...it appears that only that particular component is repainting and not the rest of the application. I eventually tracked it down to the setBorder call specifically...setting ANY kind of border appears to cause the same broken behavior. We have two different versions of our application, one that runs in Java 1.5 and one that runs in Java 1.6, the Java 1.6 version appears to work correctly while the Java 1.5 version doesn't. It is not possible to upgrade the older version to Java 1.6...I need something that will work in Java 1.5. Also, I tried this (just to see what it looked like):
setHorizontalTextPosition(JLabel.CENTER);
And that also appears to break the repainting in exactly the same way. I looked through the source of our application and found other places where we set borders (including empty borders), but couldn't find any on JLabels (only panels, buttons, etc). Anybody see anything like this before? Know how to fix it? Or perhaps another way to obtain the spacing I require that may work around the bug? Thanks.
The problem is that you're calling that code inside the paint method. You should not do that because it will freeze the EDT with unwanted loops in the swing painting pipeline.
You should put that code on the constructor and change the component design state elsewhere on the app life cycle.
If you want to know a little bit more about Swing painting please read the "Swing painting pipeline" post on pushing-pixels.org.
Note that you can use BorderFactory.createCompoundBorder to combine any two borders. Then you can set spacing with the emptyBorder and any other to draw the outer border.
EDIT: Example added.
package com.stackoverflow.swing.paintpipeline;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
import javax.swing.border.Border;
public class JLabelSetBorderPaintProblem extends JLabel {
public JLabelSetBorderPaintProblem(String text) {
super(text);
}
/*
* #see javax.swing.JComponent paint(java.awt.Graphics)
*/
#Override
public void paint(Graphics g) {
super.paint(g);
// You can not call setBorder here.
// Please check javadoc.
}
/*
* #see javax.swing.JComponent paintBorder(java.awt.Graphics)
*/
#Override
protected void paintBorder(Graphics g) {
super.paintBorder(g);
// Here is where the Swing painting pipeline draws the current border
// for the JLabel instance.
// Please check javadoc.
}
// Start me here!
public static void main(String[] args) {
// SetBorder will dispatch an event to Event Dispatcher Thread to draw the
// new border around the component - you must call setBorder inside EDT.
// Swing rule 1.
SwingUtilities.invokeLater(new Runnable() {
#Override public void run() {
// Inside EDT
JFrame frame = new JFrame("JLabel setBorder example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Add the JLabel
final JLabelSetBorderPaintProblem label = new JLabelSetBorderPaintProblem("Just press or wait...");
frame.add(label);
// And change the border...
label.addMouseListener(new MouseAdapter() {
#Override public void mousePressed(MouseEvent e) {
label.setBorder(BORDERS.get(new Random().nextInt(BORDERS.size())));
}
});
// ...whenever you want
new Timer(5000, new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
label.setBorder(BORDERS.get(new Random().nextInt(BORDERS.size())));
}
}).start();
frame.pack();
frame.setVisible(true);
}
});
}
public static final List<Border> BORDERS;
static {
BORDERS = new ArrayList<Border>();
BORDERS.add(BorderFactory.createLineBorder(Color.BLACK));
BORDERS.add(BorderFactory.createLineBorder(Color.RED));
BORDERS.add(BorderFactory.createEtchedBorder());
BORDERS.add(BorderFactory.createTitledBorder("A border"));
}
}

Categories