I am building a Java Swing application that needs to support both an embedded browser and ActiveX. The easy way to do this seemed to be to use JDICplus, which just embeds IE in your application. That would have covered both requirements.
I also need to overlay some buttons on top of this browser view so the user can navigate between views. To do that, I have a JLayeredPane to which I add views, and at a higher layer, buttons. This works in my pure Java views. However, on my Internet view, the Internet draws on top of the buttons. In other words, it doesn't seem to respect the JLayeredPane. I'm guessing this is because it is a native component and not a Java component.
To be sure, I put the Internet pane into a JInternalFrame, and the buttons in the other, and put both of the internal frames into a JDesktopPane. When I drag the button frame on top of the Internet frame, the Internet frame jumps to the foreground and covers the other frame. It's as if the embedded IE steals the focus and puts itself in the forefront of my other windows.
My question is this: is there any way to draw Java components on top of these Windows/IE components reliably? Or, am I not going to get anywhere mixing Java with IE? Are there other options to meeting my requirement of an embedded browser and ActiveX support (which technically, could be a different view--in other words, I could have an Internet view and another view that just supports ActiveX). I'm open to suggestions. I have looked at other free browser components for Java, and as everyone will tell you, it's discouraging.
Check out Sun's article on mixing heavy and light components - since JDICPlus basically embeds IE into your app, it's a heavyweight component.
You may be able to place buttons over the browser window by using other heavyweight components (i.e. AWT Button), or do something like place the button into a JPopupMenu placed over the browser with setDefaultLightWeightPopupEnabled(false) set on it to make it heavyweight.
Edited
I wrote an example using JPopupMenu to display a JButton over a heavyweight component - JPopupMenu works, but it does have built in behavior to close the menu when the popup or components in the popup lose focus. I added a MouseMotionListener to the heavyweight component to show the popups when the mouse entered a bounding box near where the buttons should be. Not sure if this works for you as the buttons aren't always shown.
Including a code example and screenshot -
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.geom.Rectangle2D;
public class LightHeavy extends JFrame {
private Component heavyweightComponent;
private JPopupMenu backButton, forwardButton;
public LightHeavy() {
super("LightHeavy");
heavyweightComponent = buildHeavyweightComponent();
heavyweightComponent.setBackground(Color.ORANGE);
heavyweightComponent.setSize(640, 480);
getContentPane().add(heavyweightComponent, BorderLayout.CENTER);
ImageIcon backArrow = new ImageIcon("left_arrow_128.png");
backButton = buildPopup(backArrow);
ImageIcon forwardArrow = new ImageIcon("right_arrow_128.png");
forwardButton = buildPopup(forwardArrow);
heavyweightComponent.addMouseMotionListener(new MouseInputAdapter() {
public void mouseMoved(MouseEvent e) {
Rectangle backHotSpot = new Rectangle(0, 0, 200, 200);
Rectangle forwardHotSpot = new Rectangle(heavyweightComponent.getWidth() - 200, 0, 200, 200);
if (backHotSpot.contains(e.getPoint())) {
backButton.show(heavyweightComponent, 0, 0);
} else if (forwardHotSpot.contains(e.getPoint())) {
forwardButton.show(heavyweightComponent,
heavyweightComponent.getWidth() - forwardButton.getWidth(), 0);
}
}
});
}
private Component buildHeavyweightComponent() {
return new Canvas() {
public void paint(Graphics og) {
super.paint(og);
Graphics2D g = (Graphics2D)og;
String big = "Heavyweight Component";
g.setFont(getFont().deriveFont(20F));
Rectangle2D bigBounds = g.getFontMetrics().getStringBounds(big, g);
g.drawString(big,
(this.getWidth() - (int)bigBounds.getWidth()) / 2,
(this.getHeight() - (int)bigBounds.getHeight()) / 2);
String little = "(assume this is JDICplus)";
g.setFont(getFont().deriveFont(10F));
Rectangle2D littleBounds = g.getFontMetrics().getStringBounds(little, g);
g.drawString(little,
(this.getWidth() - (int)littleBounds.getWidth()) / 2,
(this.getHeight() + (int)littleBounds.getHeight()) / 2);
}
};
}
private JPopupMenu buildPopup(Icon icon) {
JButton button = new JButton(icon);
JPopupMenu popup = new JPopupMenu();
popup.add(button);
popup.setBorderPainted(false);
popup.setLightWeightPopupEnabled(false);
return popup;
}
public static void main(String[] args) {
JFrame f = new LightHeavy();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
Here's a screenshot with the JButton on the left showing - note that you also won't be able to do any cool transparency effects, as you're dealing with heavyweight components.
Related
I found a strange anomaly in Java Swing.
The first JButton added to the UI chronologically always fires when the uses presses the space bar, assuming he hasn't clicked another button before doing that. This behavior even occurs if getRootPane().setDefaultButton(JButton) and JButton.requestFocus() are called.
When requesting focus on a JButton there seem to be at least 2 different kinds of "focus".
One of the "focusses" or highlightings is a dashed rectangle around the text on the button, while the other one is a thicker outline around the specified button.
The button with the dashed outlined text fires whenever the space bar is pressed.
The button with the thick border fires whenever the enter key is pressed.
I prepared a compilable minimal example illustrating this behaviour. There is no key mapping/binding involved at all.
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.WindowConstants;
public class ButtonFocusAnomalyExample extends JFrame {
public ButtonFocusAnomalyExample() {
super();
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
int frameWidth = 300;
int frameHeight = 300;
setSize(frameWidth, frameHeight);
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int x = (d.width - getSize().width) / 2;
int y = (d.height - getSize().height) / 2;
setLocation(x, y);
setTitle("Any Frame");
setResizable(false);
Container cp = getContentPane();
cp.setLayout(null);
setVisible(true);
new DialogMinimal(this, true); // Runs the Dialog
}
public static void main(String[] args) {
new ButtonFocusAnomalyExample();
}
static class DialogMinimal extends JDialog {
private final JTextField output = new JTextField();
public DialogMinimal(final JFrame owner, final boolean modal) {
super(owner, modal);
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
int frameWidth = 252;
int frameHeight = 126;
setSize(frameWidth, frameHeight);
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
int x = (d.width - getSize().width) / 2;
int y = (d.height - getSize().height) / 2;
setLocation(x, y);
setTitle("Minimal Button Focus Example");
Container cp = getContentPane();
cp.setLayout(null);
JButton bYes = new JButton();
bYes.setBounds(0, 0, 100, 33);
bYes.setText("Yes (Space)");
bYes.addActionListener(this::bYes_ActionPerformed);
JPanel buttonPanel = new JPanel(null, true);
buttonPanel.add(bYes);
JButton bNo = new JButton();
bNo.setBounds(108, 0, 120, 33);
bNo.setText("No (Enter/Return)");
getRootPane().setDefaultButton(bNo); // Set "No" as default button
bNo.requestFocus(); // Get focus on "No" button
bNo.addActionListener(this::bNo_ActionPerformed);
buttonPanel.add(bNo);
buttonPanel.setBounds(8, 8, 400, 92);
buttonPanel.setOpaque(false);
cp.add(buttonPanel);
output.setBounds(8, 50, 220, 32);
cp.add(output);
setResizable(false);
setVisible(true);
}
public void bYes_ActionPerformed(final ActionEvent evt) {
output.setText("Yes"); // Still fires on every space bar press
}
public void bNo_ActionPerformed(final ActionEvent evt) {
output.setText("No"); // Only fires on every return/enter press
}
}
}
This is what it looks like:
The executable code can also be found here.
My questions now are:
What are these different focusses?
How can someone change the focus that shows as a dashed outline around the text of the button so that the space bar and the enter key would fire the event of the "No" button?
The Dialog Focus resource (already referenced in comments and in the accepted solution) shows an easier approach as well.
The simple solution has its drawbacks as they are clearly pointed out in the article, but for the scenario above, where the dialog is completely built from user code (as opposite to using static JOptionPane.showXXX), it will do fine.
The trick is to call pack() on the dialog before it is made visible and it's modality will block any further code execution (and focus requests)
Component c = myDialog.getContentPane();
...
c.add(myYesButton); // 1. Add all components to the dialog
myDialog.pack(); // Call pack() on the dialog
myYesButton.requestFocusInWindow(); // Request focus after pack() was called
myDialog.setVisible(true); // Show the dialog
Regarding question 1: "What is the difference between focus on a button represented by a dashed outline and one with a thick continous outline?
Answer: There are no 2 kinds of "focus". Both methods do what their respective names say:
JButton.requestFocus() (better yet JButton.requestFocusInWindow()) requests focus on a button, while getRootPane().setDefaultButton(JButton) sets a selected button, which the LAF handles seperately.
Regarding question 2: "Why does my specific implementation behave like this and how can I achieve the behaviour I want?"
Answer: The modality of the Dialog is the problem. You cannot request focus after setVisible(true) has been called on a modal window.
Possible solutions would therefore be to either:
Set modality to false when creating the Dialog, e.g. with new DialogMinimal(this, false); and get focus by calling bNo.requestFocusInWindow() instead of getRootPane().setDefaultButton(bNo); and/or bNo.requestFocus();, but this is no solution if the Dialog has to be modal.
or
Implement RequestFocusListener found in Dialog Focus as suggested by user camickr.
public DialogMinimal(final JFrame owner, final boolean modal) {
Button bNo = new JButton();
[...]
// bNo.requestFocusInWindow(); // obsolete now
getRootPane().setDefaultButton(bNo); // To fire on enter key
bNo.addAncestorListener(new RequestFocusListener()); // To fire on space bar
[...]
}
I want do design a simple login format and in order to do so I want two JTextFields for Username/Password and a Login Button. The Login button is display as expected but when I add the JTextField, nothing shows in my JFrame. Would be nice if someone could help a beginner out...
Here's my code (I know it's ugly but this is just a "code sketch"):
package bucketlistpackage;
import java.awt.Container;
import java.awt.GridLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;
public class GameFrame extends JFrame {
public GameFrame(String title) {
super(title); //sets title of frame
startFrame(); //sets details of main frame
final Container logincont = getContentPane(); //creates content pane
JFrame loginframe = new JFrame();
usernameField(loginframe);
loginButton(loginframe);
logincont.add(loginframe);
}
private void usernameField(JFrame loginframe) {
JTextField usernameF = new JTextField("Username", 1);
usernameF.setBounds(50, 50, 50, 20);
loginframe.add(usernameF);
usernameF.setVisible(true);
}
private void startFrame() {
this.setSize(1000, 1000);
this.setVisible(true);
}
private void loginButton(Container cont) {
JButton loginB = new loginButton();
loginB.setSize(300, 150);
loginB.setText("Login");
cont.add(loginB);
}
}
The problem lies on how you are adding component to one another in your case.
You are adding a JFrame to a Container, when in all case it should be the other way around.
The other problem is that you are not using Layouts to manage the components positions on the JFrame.
Another problem as well is that you are not refreshing the windows after adding all the stuff on it.
A bit of a resume on how Java works with native UIs:
Java creates a new thread for the UI. So if you open the debugger you will see AWT threads as well as the main threads and others. This means that you have to manage this in a correct way, because after the application starts SWING and the functions you determine for reactions will lay the ground on how it will behave. Your main thread will die, but the visual threads will keep active.
If you are just starting to program I would encourage you to practice a bit more native java language before moving to SWING or AWT. This libraries can be really painful and tricky to use.
The other thing is SWING library follows a hierarchy for the components:
JFrame > JPanels > Components
In your code you have already worked with all of them but in a disorganized way. JFrame is the main application window, where the graphics will be displayed (can also be a Canvas or whatever class you want to use for that matter). JPanels are organizers, you can use different layouts to organize whatever is inside it. And finally the Components are well... everything! A component can be a JTextField, but it can also be a JPanel, or JButton.
The idea with SWING is to create multiple Panels and organize the end components inside them, using the help of the different layouts to see whats the best approach to make them attractive in many different window sizes.
Finally, if you are using Eclipse, there is a plugin called WindowBuilder which might help you. I don't recommend you using it if you are very new to Java since it will make you depend a lot on it instead of learning how to actually code with SWING.
Hope this helps!!!
Btw, to fix the code above I would do something like this:
public GameFrame(String title) {
super(title); //sets title of frame
startFrame(); //sets details of main frame
final Container logincont = getContentPane(); //creates content pane
logincont.setLayout(new BorderLayout());
usernameField(logincont, BorderLayout.NORTH);
loginButton(logincont, BorderLayout.CENTER);
this.revalidate();
this.repaint();
}
private void usernameField(Container loginframe, String direction) {
JTextField usernameF = new JTextField("Username");
// usernameF.setBounds(50, 50, 50, 20);
loginframe.add(usernameF, direction);
usernameF.setVisible(true);
}
private void startFrame() {
this.setSize(1000, 1000);
this.setVisible(true);
}
private void loginButton(Container cont, String direction) {
JButton loginB = new JButton();
loginB.setSize(300, 150);
loginB.setText("Login");
cont.add(loginB, direction);
}
I was under the impression that a touch screen emulated mouse events, however how would I handle multiple touch events?
I have tried adding a JPanel with a MouseMotionListener and outputting the values to the console. I however, am only getting values for the first touch on the screen.
I then tried splitting the screen down the middle, with a JPanel on either side, and a separate MouseMotionListener on each side, each one outputting to the console.
In this configuration, I still only see the values for the first finger on the screen.
Here is what I have so far:
JPanel jPaneL = new JPanel();
jPaneL.setName("Left");
jPaneL.addMouseMotionListener(mouseMotionL);
jPaneL.setBounds(0, 0, 960, 1280);
jPaneL.setBackground(Color.BLACK);
JPanel jPaneR = new JPanel();
jPaneR.setName("Right");
jPaneR.addMouseMotionListener(mouseMotionR);
jPaneR.setBounds(960, 0, 960, 1080);
jPaneR.setBackground(Color.RED);
jFrame.add(jPaneL);
jFrame.add(jPaneR);
And my MouseMotionListener, L and R are the exact same.
private static MouseMotionListener mouseMotionX = new MouseMotionListener() {
#Override
public void mouseDragged(MouseEvent e) {
String s = ((JPanel) e.getSource()).getName();
System.out.println(s);
}
#Override
public void mouseMoved(MouseEvent e) {
String s = ((JPanel) e.getSource()).getName();
System.out.println(s);
}
};
I have found libraries that support multi-touch, but I am looking to do this without using third-party libraries. How can I go about doing this using just native mouse listeners?
First I want to say that I would really recommend using JavaFX for such an application.
You can also integrate your JavaFX project in existing swing applications using JFXPanel.
However I have heard about MT4j which is a framework for multitouch in Java application which also supports Swing. I have not worked with it. So I can not tell you how good it works.
I have been using this code to implement a Popup JDialog of sorts, like what you see when your anti-virus is scanning your system or updates itself:
import java.awt.*;
import javax.swing.JDialog;
import javax.swing.JLabel;
public class PopupDialog extends JDialog {
public PopupDialog() throws HeadlessException {
createUI();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
PopupDialog popupDialog = new PopupDialog();
popupDialog.setVisible(true);
}
});
}
private void createUI() {
setTitle("Popup Dialog");
setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
addComponentsToFrame();
//This call will give screens viable area, which takes into account the taskbar, which could be at the bottom, top,left or right of the screen.
Rectangle maxBounds = GraphicsEnvironment.getLocalGraphicsEnvironment().getMaximumWindowBounds();
//get screen size
int sWidth = maxBounds.width, sHeight = maxBounds.height;
setSize(275, 225);//set frame size
Dimension appDim = getSize();
//get app size
int aWidth = appDim.width, aHeight = appDim.height;
//set location like a tooltip would be except its a custom dialog like an AV
setLocation(sWidth - aWidth, (sHeight - aHeight));
}
private void addComponentsToFrame() {
JLabel label = new JLabel("Popup Dialog");
getContentPane().add(label, BorderLayout.CENTER);
}
}
But my question is: is there any class or package in java that will do this for me? and if not how would I go about allowing the JDialog to slide up from the taskbar (or offscreen)? or somehow become visible in a slow manner like a ToolTip Popup would from the system tray. Thanks.
EDIT The reason i want to use a JDialog or Frame is because i want to be able to fully skin the Popup window, using setUndecorated(true); and adding custom exit icons, backgrounds etc
You mean like the examples here: http://docs.oracle.com/javase/tutorial/uiswing/misc/systemtray.html ?
First, you can make your frame be always on top, this will ensure it is always visible. Next, you could position the frame off the bottom of the screen and slide it up programmatically. This should be fairly smooth even on an older XP machine. There is no standard Java API to do this but you can do it yourself pretty easily. Another option instead of sliding is to make the window fully transparent and fade it in. This API was added in recent (last 2 years) Java 6 updates, so it should be available everywhere.
I'm doing a project where i need some custom swing components. So far I have made a new button with a series of images (the Java Metal look doesn't fit with my UI at all). Ive implemented MouseListener on this new component and this is where my problem arises. My widget changes image on hover, click etc except my MouseListener picks up mouse entry into a the entire GridLayout container instead of into the image. So I have an image of about 200*100 and the surrounding container is about 400*200 and the mouseEntered method is fired when it enters that GridLayout section (even blank space parts of it) instead of over the image. How can I make it so that it is only fired when I hover over the image? Ive tried setting size and bounds and other attributes to no avail.
EDIT: Here's a demonstration of my issue. As you can see (sort of, colors are very similar) the bottom right button is highlighted just by entering its section of the GridlLayout. I only want it highlighted when I'm over the image actual, not the GridLayout section.
I Won't add the MouseListener methods because they just involve switching the displayed image.
public customWidget()
{
this.setLayout(new FlowLayout());
try {
imageDef=ImageIO.read(new File("/home/x101/Desktop/buttonDef.png"));
imageClick=ImageIO.read(new File("/home/x101/Desktop/buttonClick.png"));
imageHover=ImageIO.read(new File("/home/x101/Desktop/buttonHover.png"));
current=imageDef;
} catch (IOException e)
{
e.printStackTrace();
}
this.addMouseListener(this);
}
protected void paintComponent(Graphics g)
{
super.paintComponents(g);
g.drawImage(current, 0, 0, current.getWidth(), current.getHeight(), null);
}
EDIT: added code section
As an alternative, consider the The Button API, which includes the method setRolloverIcon() "to make the button display the specified icon when the cursor passes over it."
Addendum: For example,
import java.net.MalformedURLException;
import java.net.URL;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ButtonIconTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
private static void createAndShowGui() {
String base = "http://download.oracle.com/"
+ "javase/tutorial/uiswing/examples/components/"
+ "RadioButtonDemoProject/src/components/images/";
ImageIcon dog = null;
ImageIcon pig = null;
try {
dog = new ImageIcon(new URL(base + "Dog.gif"));
pig = new ImageIcon(new URL(base + "Pig.gif"));
} catch (MalformedURLException ex) {
ex.printStackTrace(System.err);
return;
}
JFrame frame = new JFrame("Rollover Test");
JPanel panel = new JPanel();
panel.add(new JLabel(dog));
panel.add(new JLabel(pig));
JButton button = new JButton(dog);
button.setRolloverIcon(pig);
panel.add(button);
frame.add(panel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
I assume your image contains ONLY 4 'customWidget' objects (in a 2x2 grid).
Your code is working as I would expect. Your MouseListener methods are responding to MouseEvents for 'customWidget' (not the image drawn in 'customWidget'), which is sized to take up 1/4 of the image, so they will respond when it enters the enlarged area. The error is actually in your Test program, because you are allowing the custom button widget to be larger than the image.
If you want a Test program that provides an image similar to yours, you should create a larger grid (say 4x4), and then only place your buttons in every other grid node. Place an empty component into the gaps.
Although I won't answer to your particular question, I hope this helps:
If the components just look wrong maybe you should reuse Swing components and just write a custom Look&Feel or theme.
It would certainly help ensuring the look of the application is consistent and at least you are using the right tool for the task you want to accomplish.
As a sidenote, be aware that Java comes with multiple Look&feels, including Look&Feels made to mimic the native OS theme.
See: http://download.oracle.com/javase/tutorial/uiswing/lookandfeel/plaf.html