This is a simplified version of my code, if this is not enough to diagnose the problem, comment and I'll post more code:
//All Necessary Imports
JFrame window;
Container container;
JPanel mainTextPanel;
JScrollPane scrollPane;
JTextArea mainText;
Font normalFont = new Font("Times New Roman", Font.PLAIN,30);
ChoiceHandler choiceHandler = new ChoiceHandler();
//Unrelated Variables
public class setUp()
{
public setUp()
{
window = new JFrame();
window.setSize(825,600);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.white);
window.setLayout(null);
container = window.getContentPane();
mainTextPanel = new JPanel();
mainTextPanel.setBounds(100,100,600,250);
mainTextPanel.setBackground(Color.white);
container.add(mainTextPanel);
//more code goes here
}
}
public class mainText extends setUp
{
public mainText()
{
mainText = new JTextArea("Tons of text here");
mainText.setBounds(100,100,600,250);
mainText.setBackground(Color.white);
mainText.setForeground(Color.black);
mainText.setFont(normalFont);
mainText.setLineWrap(true);
scrollPane = new
JScrollPane(mainText,JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
mainTextPanel.add(scrollPane);
mainTextPanel.revalidate();
mainTextPanel.repaint();
mainTextPanel.setVisible(true);
//more code goes here
}
}
My problem is that when I run the code, everything works except that the ScrollPane apparently cannot detect the fact that the text is out of bounds. The scrollbar is there, the only problem is that you cannot scroll with it, it looks like a scroll bar from when the text does not exceed the limit.
When I removed the ALWAYS modifier in creating the scrollbar, the scrollbar disappears, again demonstrating that the scrollbar simply does not detect the out-of-bounds text.
I'd just like to point out to you why null layouts suck ... code
This is how your code appears on my system. To be honest, there is scroll bar there, but your attempt to do away with the layout management API has caused the JScrollPane to exceed the physical bounds of the parent container.
You also don't seem to understand how the coordinate system works in Swing, in relationship to the parent component's context, but if you work with a layout manager instead, you wouldn't need to.
So, after updating your code a bit to use a layout managers
You know have a solution which can be scrolled...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
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();
}
mainText mt = new mainText();
mt.window.pack();
mt.window.setLocationRelativeTo(null);
mt.window.setVisible(true);
}
});
}
public class setUp {
JFrame window;
Container container;
JPanel mainTextPanel;
JScrollPane scrollPane;
JTextArea mainText;
Font normalFont = new Font("Times New Roman", Font.PLAIN, 30);
public setUp() {
window = new JFrame();
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
window.getContentPane().setBackground(Color.white);
container = window.getContentPane();
mainTextPanel = new JPanel(new BorderLayout());
mainTextPanel.setBackground(Color.white);
container.add(mainTextPanel);
//more code goes here
}
}
public class mainText extends setUp {
public mainText() {
mainText = new JTextArea("Tons of text here");
mainText.setBackground(Color.white);
mainText.setForeground(Color.black);
mainText.setFont(normalFont);
mainText.setLineWrap(true);
scrollPane = new JScrollPane(mainText, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
mainTextPanel.add(scrollPane);
mainTextPanel.revalidate();
mainTextPanel.repaint();
//more code goes here
}
}
}
But where is the horizontal scroll bar?
mainText.setLineWrap(true); would have removed the need for it
But the JTextArea is to small!
So? Supply some appropriate sizing hints which the component can use to make better determinations about how large it might like to be.
Something like mainText = new JTextArea("Tons of text here", 1, 10); produces
So, the long and short answer is, use the layout management API, Swing has been designed around it's use and it will save you a lot of head scratching and stupid edge cases
But on my system the scroll bars don't appear!?
😓 Go back to the start, do not collect $200 and read it again. The reason why it would appear differently is because of the lack of layout support
But I don't want to use/understand layout managers!
Tough. If you want to avoid these kind of "weird" issues, then the layout management API is the right direction to head in. Take the time to experiment with the different layout managers and try different combinations, you're not stuck to using just one, almost all complex UIs will make use a at least two if not more layout managers in a single compound UI
Related
Working with the JTreeWithScrollbar example, but scaled it back significantly to focus on the issue.
The original code would have the vertical scrollbars appear as needed.
Here, there is plenty of space and no scrollbars are needed.
If the panel is moved enough, the scrollbar will appear.
Once the following line of code was added, the scrollbars stopped appearing.
tree.setUI(new MyTreeUI());
Notice no scrollbar.
If the above line of code is commented out, the vertical scrollbar appears.
Checking the documentation for BasicTreeUI and there isn't anything related to showing/hiding scrollbars.
2 Questions
1 - When utilizing the BasicTreeUI object, what is required to ensure the scrollbars still function?
2 - Why is it the Horizontal scrollbar never appears even if the line of code is commented out?
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTree;
import javax.swing.plaf.basic.BasicTreeUI;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.Dimension;
public class JTreeWithScrollbar extends JPanel {
private JEditorPane htmlPane;
private JTree tree;
public JTreeWithScrollbar()
{
//Create the nodes.
DefaultMutableTreeNode top = new DefaultMutableTreeNode("The Java Series");
DefaultMutableTreeNode book1Node = new DefaultMutableTreeNode("Book 1");
DefaultMutableTreeNode book2Node = new DefaultMutableTreeNode("Book 2");
top.add(book1Node);
top.add(book2Node);
tree = new JTree(top);
tree.setUI(new MyTreeUI()); ///Comment out this line of code and the vertical scrollbar appears.
JScrollPane treeView = new JScrollPane(tree);
JScrollPane htmlView = new JScrollPane(htmlPane);
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
splitPane.setTopComponent(treeView);
splitPane.setBottomComponent(htmlView);
Dimension minimumSize = new Dimension(100, 50);
htmlView.setMinimumSize(minimumSize);
splitPane.setDividerLocation(100);
splitPane.setPreferredSize(new Dimension(500, 300));
add(splitPane);
}
public static void main(String[] args)
{
//Create and set up the window.
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel jp = new JPanel();
jp.add(new JTreeWithScrollbar());
frame.add(jp);
//Display the window.
frame.pack();
frame.setVisible(true);
}
private static class MyTreeUI extends BasicTreeUI
{
public MyTreeUI()
{
super();
}
#Override
protected void updateCachedPreferredSize() {
treeState.invalidateSizes();
tree.treeDidChange();
}
}
}
When utilizing the BasicTreeUI object, what is required to ensure the scrollbars still function?
As shown in the minimal example below, BasicTreeUI correctly shows each scroll bar when needed; resize the frame to see the effect.
Why does the horizontal scrollbar never appear even if the line of code is commented out?
After pack() the frame has been resized to adopt the preferred size of it content. Making the frame slightly smaller illustrates the effect. Your example adds the tree to a JPanel having a default FlowLayout which ignores preferred sizes; the example below adds the tree to the center of the frame's default BorderLayout which responds to preferred sizes.
I am assuming the updateCachedPreferredSize() must be doing other stuff behind the scenes…
Exactly. Each invocation of updateCachedPreferredSize() updates the component's preferred size to reflect any change in state (resize, expand, etc.); when the preferred size exceeds the viewport size, the scroll bars appear. As you observed, invoking super.updateCachedPreferredSize() allows normal operation, and any further customization must preserve that functionality.
In addition,
Expand rows as need like this.
Construct and manipulate Swing GUI objects only on the event dispatch thread.
Don't use setSize() when you really mean to override getPreferredSize() or illustrates a resize effect; more here.
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.plaf.basic.BasicTreeUI;
public class JTreeWithScrollbar {
public static void main(String[] args) {
EventQueue.invokeLater(() -> {
//Create and set up the window.
JFrame frame = new JFrame("TreeDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTree tree = new JTree(); //default model
for (int i = 0; i < tree.getRowCount(); i++) {
tree.expandRow(i);
}
tree.setUI(new MyTreeUI());
frame.add(new JScrollPane(tree));
//Display the window.
frame.pack();
frame.setSize(frame.getWidth() - 10, frame.getHeight() - 100);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
private static class MyTreeUI extends BasicTreeUI {
}
}
When running below simple app and changing font anti-aliasing (WIN+R "sysdm.cpl" -> System Properties -> Advanced -> Performance Settings -> Smooth edges of screen fonts) one can see that the JTextArea gets the wrong font (and stays with it also if toggle anti-aliasing again):
import java.awt.Font;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
public class Main
{
public static void main(String[] args)
{
JFrame frame = new JFrame();
JPanel mainPanel = new JPanel();
JLabel label = new JLabel("labelMessage:");
mainPanel.add(label);
final JTextArea textArea = new JTextArea("textAreaMessage")
{
// #Override
// public void setFont(Font f)
// {
// super.setFont(label.getFont());
// }
};
textArea.setEditable(false);
textArea.setOpaque(false);
textArea.setFont(label.getFont());
mainPanel.add(textArea);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setVisible(true);
}
}
Only, when uncommenting the override of setFont it works like one would expect.
When setting there a breakpoint one can see that when the font anti-aliasing is changed there is a AATextListener fired:
Where at some point the defaults are installed:
editor.setFont(UIManager.getFont(prefix /*TextArea*/ + ".font"));
So it is clear why it is happening but I do not know how to solve it in a good fashion.
Possible solutions:
Override the methods like shown above in the code
Use e.g. -Dswing.useSystemFontSettings=false (or -Dswing.aatext=true or awt.useSystemAAFontSettings) BUT then the font looks ugly so not acceptable without further enhancements
Set via UIManager the defaults
Remove/disable the listener? Seems to be hard coded and not be doable easy?
or 3. could be a solution but would require much work in a big legacy app.
NOTE: This is just a simple example. In our app the problem is much bigger as not only set fonts are lost, but almost everything, like borders of buttons, the custom ComponentUIs etc. And the described way how to reproduce it was just to have it easy reproducible, means we encounter this issue also spontaneously when e.g. the app runs for some hours. We then see that some windows event is firing the listener and destroying the look and feel of our app (often the PC was not used at all for almost an hour when this suddenly happens).
Maybe I will also ask the OpenJDK community the next days, as well. If so, I will link the ticket ID here.
Windows version: Windows Enterprise 10 Version 20H2 (Build 19042.1348)
Java version: zulu11.50.19-ca-jdk11.0.12-win_x64
Demo which listens for change like proposed by #VGR. When font anti aliasing is changed the UI will be re-rendered.
import java.awt.Toolkit;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
public class Main
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> {
final JFrame frame = new JFrame();
final JPanel mainPanel = new JPanel();
Toolkit tk = Toolkit.getDefaultToolkit();
tk.addPropertyChangeListener("awt.font.desktophints"/*SunToolkit.DESKTOPFONTHINTS*/, new PropertyChangeListener()
{
#Override
public void propertyChange(PropertyChangeEvent evt)
{
SwingUtilities.invokeLater(() -> render(mainPanel));
}
});
render(mainPanel);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setVisible(true);
});
}
private static void render(JPanel parent)
{
parent.removeAll();
final JPanel panel = new JPanel();
final JLabel label = new JLabel("labelMessage:");
panel.add(label);
final JTextArea textArea = new JTextArea("textAreaMessage");
textArea.setEditable(false);
textArea.setOpaque(false);
textArea.setFont(label.getFont());
panel.add(textArea);
parent.add(panel);
parent.revalidate();
parent.repaint();
}
}
It's an old question but I'd like to explain what's going on here.
textArea.setFont(label.getFont());
You use the font of the label for textArea. Since you didn't change the font, the font is a UIResource, therefore the font gets reset to the default font whenever UI components are updated because of a desktop property change or any other update, like Look-and-Feel change.
When such an event occurs, the UI of all the components is updated using updateUI() method of JComponent.
Creating a new instance of Font removes the magic of UIResource:
Font labelFont = label.getFont();
textArea.setFont(labelFont.deriveFont(labelFont.getStyle()));
The font of the textArea is not reset by updateUI. Yet the font will not change even when you want it to change, for example when the Look-and-Feel of your app is updated to another LaF.
A better solution is to override updateUI method and set the font for textArea there:
final JTextArea textArea = new JTextArea("textAreaMessage") {
#Override
public void updateUI() {
super.updateUI();
setFont(UIManager.getFont("Label.font"));
}
};
This approach handles both the initial setup as well as any updates. This can be a new reusable component which is used to display selectable text, if you also set the background and foreground colors and make the text non-editable by default. (If you set the background and foreground colors, which I recommend doing, you won't need textArea.setOpaque(false);, it makes paint and repaint faster.)
The runnable example:
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
public class UIFontUpdate {
private final JFrame frame;
public static void main(String[] args) {
SwingUtilities.invokeLater(UIFontUpdate::new);
}
private UIFontUpdate() {
frame = new JFrame("UI Font Update");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel mainPanel = new JPanel();
JLabel label = new JLabel("labelMessage:");
mainPanel.add(label);
final JTextArea textArea = new JTextArea("textAreaMessage") {
#Override
public void updateUI() {
super.updateUI();
setFont(UIManager.getFont("Label.font"));
}
};
textArea.setEditable(false);
textArea.setOpaque(false);
mainPanel.add(textArea);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new Thread(this::callUpdateUI).start();
}
public void callUpdateUI() {
try {
Thread.sleep(2000);
} catch (InterruptedException ignored) { }
System.out.println("UI updating...");
SwingUtilities.invokeLater(() ->
SwingUtilities.updateComponentTreeUI(frame));
}
}
I'm simulating update to properties without actually changing system settings. I'm using callUpdateUI which is run on another thread, and after 2 seconds it updates the UI.
To test that the approach works when changing the Look-and-Feel, change the code callUpdateUI to:
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException
| IllegalAccessException
| UnsupportedLookAndFeelException ignored) {
}
SwingUtilities.updateComponentTreeUI(frame);
});
I am new to Jframes and I wanted to design a window with a textbox and two buttons. I am able to get this working except for the scrollbar part.
I have written the below piece of code to enable scrollbar to the textarea.
private JTextArea outputPane;
outputPane = new JTextArea();
outputPane.setColumns(20);
outputPane.setRows(5);
outputPane.setFont(new Font("Monospaced", Font.PLAIN, 18));
outputPane.setBounds(12, 13, 408, 189);
contentPane.add(outputPane);
JScrollPane scrollPane = new JScrollPane(outputPane);
jScrollPane1.setBounds(399, 13, 21, 189);
scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS);
Now the problem is I am getting a disabled scrollbar on the window but I cannot see my Text Area.
Please help me out with the problem. I even tried using WindowsBuilder but I cannot figure it out.
Detailed explanation with the corrected code will be appreciated as I am still in the learning stage.
Thanks in advance.
Start by having a look at Laying Out Components Within a Container and How to Use Scroll Panes and How to Use Text Areas probably wouldn't hurt
Now the problem is I am getting a disabled scrollbar on the window but I cannot see my Text Area.
The likely problem is, you are seeing your JTextArea, the "disabled" scroll bar is simply because you're using scrollPane.setVerticalScrollBarPolicy(ScrollPaneConstants.VERTICAL_SCROLLBAR_ALWAYS, which will always display the scroll bar, even when there is nothing to be scrolled, so it probably looks empty.
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
// Swing is not thread safe, so need to get started in the
// Event Dispatching Thread before we do anything
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// I simply hate the default look and feel
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
// Always better to create an instance of a window
// to display you content then to extend from one
// directly...
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
// Our main UI, I do it this way so I'm not locked into a single
// use case and can decide how I want to use the view
public class TestPane extends JPanel {
public TestPane() {
// The default layout is a FlowLayout, so we want to change
// this will allow the main component to occupy the whole
// available space
setLayout(new BorderLayout());
// Providing "sizing" hints, 10 rows, 20 columns, this is
// platform independent, so it will size accordingly
JTextArea ta = new JTextArea(10, 20);
JScrollPane sp = new JScrollPane(ta);
add(sp);
}
}
}
I'm developing Java Swing application. My application has two Java classes. Inside class1.java, I include JFrame, JButton and JPanel (panel1). When I click the button I want to hide panel1 and should be shown panel2 of class2.java. I tried this method in button actionPerformed method of class1.java. But it was not working.
class2 pnl = new class2();
this.remove(panel1);
this.add(pnl);
this.validate();
this.repaint();
Analysis
You simply want the JComponents to be displayed on the JFrame. We can achieve this by using a single JPanel, but adding and removing the JComponents from it, during the JButton's action listener.
Without looking at your actual code, it is better to make a manageable way to reach code and instantiated Objects. The code listed below, creates a nice and manageable way to do so.
Achieving This
The entire class is listed below with comments for explanations.
package swing;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class MultiPaneledFrame {
JFrame frame = new JFrame();
JPanel window = new JPanel();
// As you can see, we create an array containing all your JComponents.
// We have two of these, to simulate multiple JPanel's.
List<JComponent> window1Contents = new ArrayList<JComponent>();
List<JComponent> window2Contents = new ArrayList<JComponent>();
// NOTE: The above Lists can instead be stuck in their own class like asked for,
// and instantiated on Class invocation.
JButton goto2 = new JButton("Goto Panel 2");
JButton goto1 = new JButton("Goto Panel 1");
int panelToShow = 0; // 0 - First "panel".
// 1 - Second "panel".
// Main method of class. Change 'Multi_Paneled_Frame' to the name of your Class.
public MultiPaneledFrame() {
// Execute anything else you want here, before we start the frame.
window1Contents.add(goto2);
window2Contents.add(goto1);
// Here is where I personally am setting the coordinates of the JButton's on the JPanel.
goto2.setPreferredSize(new Dimension(200, 100));
goto1.setPreferredSize(new Dimension(200, 100));
//goto2.setBounds(5, 5, 150, 30); < Used for 'null' layout.
//goto1.setBounds(5, 5, 150, 30); < Used for 'null' layout.
goto2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addComponents(panelToShow = 1);
}
});
goto1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
addComponents(panelToShow = 0);
}
});
initialiseFrame();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
new MultiPaneledFrame();
}
});
}
private void initialiseFrame() {
frame.setSize(600, 400); // Change it accordingly.
// Optional
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setResizable(false);
// Needed
frame.setVisible(true);
frame.add(window);
window.setLayout(new BorderLayout()); // Assuming your using a BorderLayout.
//window.setLayout(null); < Uses 'null' layout.
addComponents(panelToShow);
// I always like to make sure that everything is on the frame nicely.
frame.repaint();
frame.validate();
}
private void addComponents(int panelNo) {
if (panelNo == 0) {
for (JComponent component : window1Contents) {
window.removeAll(); // We're removing everything that it contains and replacing it...
window.revalidate();
window.add(component, BorderLayout.CENTER);
//window.add(component); < Uses 'null' layout.
// Since we are using the first panel, we are adding
// everything from the first list of components to the window...
}
} else {
for (JComponent component : window2Contents) {
window.removeAll(); // We're removing everything that it contains and replacing it...
window.revalidate();
window.add(component, BorderLayout.CENTER);
//window.add(component); < Uses 'null' layout.
// Since we are using the second panel, we are adding
// everything from the second list of components to the window...
}
}
// Refreshes the frame.
frame.repaint();
frame.validate();
}
}
Conclusion
Although there are countless ways to achieve something like this, the way I have given, is semi-efficient, and very flexible. Feel free to edit the code, or drop a question if you have any concerns, and I will be happy to respond.
PS: This code was tested and works on a Macbook Air running OS X 10.11 and Java Version 8 Update 65.
CardLayout should be your solution. In this tutorial they show how to switch from panel to another one by selecting a value in ComboBox.
A little bit of explanation for the CarLayout:
The CardLayout lets you place different panel on top of each other but shows only one at the time. With your code, you select the one you want to display.
Initialisation:
this.setLayout(new CardLayout());
class1 pnl1 = new class1();
class2 pnl2 = new class2();
this.add(pnl1, "PANEL1");
this.add(pnl2, "PANEL2");
On your button actionPerformed:
CardLayout cl = (CardLayout)(this.getLayout());
cl.show(this, "PANEL2");
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