SVG in Java not displaying correctly - java

Ok, I have a Java program, that displays some tiles that are SVGs in a FlowLayout. It does this by being a class ScrabbleRack, and extending JPanel, then adding JSVGCanvas tiles to this panel.
Afterwards I created a frame and added the panel, this. (packed it and displayed it). On appearing, the panel does not display properly. It just displays the first tile and then in the space where the rest of the tiles should be displayed, there is whitearea.
But if I resize the frame by any amount, the image will render correctly.
public class ScrabbleRackGUI extends JPanel{
ScrabbleRack rack=new ScrabbleRack();
JSVGCanvas rackContentsImages[]=new JSVGCanvas[8];
public ScrabbleRackGUI() {
setLayout(new FlowLayout());
createComponents();
}
public void createComponents() {
//INITIALISE SOURCE IMAGES
initImages();
for (int i=0;i<rackContentsImages.length;i++){
this.add(rackContentsImages[i]);
}
}
private void initImages(){
File tempImages[]=new File[8];
for(int i=0;i<8;i++){
tempImages[i]= new File("./src/res/rackBackground.svg");
rackContentsImages[i]=new JSVGCanvas();
try {
rackContentsImages[i].setURI(tempImages[i].toURL().toString());
} catch (MalformedURLException ex) {
Logger.getLogger(ScrabbleBoardGUI.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
public static void main(String args[])
{
JFrame frame = new JFrame("ScrabbleTest");
ScrabbleRackGUI rack= new ScrabbleRackGUI(1);
frame.add(rack);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setSize(214,70);
frame.setVisible(true);
}
}
Any ideas on how I can get this panel to display properly, first time.
Or some hack that will resize it at the end of the program.
I used batik to render the SVGs in Java, for those who want to reproduce this problem.

You problem may be that the construction of your GUI is not being done on the EDT.
Your main should look something like:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
MyWindow window = new MyWindow();
MyWindow.setVisible(true);
}
});
}
and the rest of your code in your current main should be in the MyWindow constructor.
More detailed information can be found at http://leepoint.net/JavaBasics/gui/gui-commentary/guicom-main-thread.html (among other places)

This might be related to Batik issue 35922 reported here: https://issues.apache.org/bugzilla/show_bug.cgi?id=35922
If I understand that bug report correctly, you can workaround the problem by adding the JSVGCanvas instances (and the ScrabbleRackGUI instance) and calling pack() first, and then set the URIs on each JSVGCanvas.

First of all, you wrote:
ScrabbleRackGUI rack= new ScrabbleRackGUI(1);
and you don't have constructor that takes int.
Secondly, you're setting FlowLayout to JPanel component, and JPanel by default has FlowLayout as layout. better call super(); to get all the benefits of JPanel.
Try to run your application inside of Event Dispatching Thread (EDT), as others mentioned already.
SwingUtilities.invokeLater(new Runnable() {
// your code here
}
Also you should set your URI like this:
setURI(f.toURI().toURL().toString());
because f.toURL() is deprecated.
I hope it helps.

Related

JPanel wont appear until being resized [duplicate]

I subclass JPanel to overwrite paintComponent(Graphics), I want to draw an image onto jpanel in a jframe.
But my image hasn't shown up until I make a change to jframe's size.
This is my code:
public class ImagePanel extends JPanel{
public void setImage(BufferedImage bi)
{
image = bi;
revalidate();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(image != null)
{
g.drawImage(image, 0, 0, this);
}
}
}
Verify that you invoke setVisible() after adding components and calling pack(), as discussed in this related example. You may also need to adopt an appropriate layout. Invoking repaint(), as suggested here, may fix the symptom but not the underlying cause.
Take a look at the docs for JPanel.add(), which it inherits from java.awt.Container:
Appends the specified component to the end of this container. This is a convenience method for addImpl(java.awt.Component, java.lang.Object, int).
This method changes layout-related information, and therefore, invalidates the component hierarchy. If the container has already been displayed, the hierarchy must be validated thereafter in order to display the added component.
Emphasis added.
Therefore, if you modify a Container after it's already been displayed, you must call validate() in order for it to show up. Just invoking repaint() is not enough. You may have noticed that calling setVisible(true) also works; this is because it calls validate() internally.
If you want to "refresh" the JPanel then you should call repaint(), which will call your paintComponent(). This should fix your problem:
public void setImage(BufferedImage bi)
{
image = bi;
EventQueue.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
}
Its good practice to update and change the GUI using the EDT. Heres more info on the EDT if you're interested:
How does the event dispatch thread work?
repaint doesn't need to be called from the EDT. If you're changing the GUI, such as setting text to a JLabel, it should be inside of the EDT. Heres more information on what can be called outside of the EDT (courtesy of nIcE cOw):
Safe to use Component.repaint() outside EDT?
I had the same problem and fixed it by call setVisible(true); the JFrame I was using.
Example : if your JFrame does not update after using :
jframe.setContentPane(new MyContentPane());
fix it with :
jframe.setContentPane(new MyContentPane());
jframe.setVisible(true);
I know that it sounds silly to do this even though your JFrame is already visible, but that's the only way I've found so far to fix this problem (the solution proposed above didn't work for me).
Here is a complete example. Run it and then uncomment the "f.setVisible(true);" instructions in classes Panel1 and Panel2 and you'll see the difference. Don't forget the imports (Ctrl + Shift + O for automatic imports).
Main class :
public class Main {
private static JFrame f;
public static void main(String[] args) {
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(new Panel1(f));
f.pack();
f.setVisible(true);
}
}
Panel1 class :
public class Panel1 extends JPanel{
private JFrame f;
public Panel1(JFrame frame) {
f = frame;
this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
JButton b = new JButton("Panel 1");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
f.setContentPane(new Panel2(f));
// Uncomment the instruction below to fix GUI "update-on-resize-only" problem
//f.setVisible(true);
}
});
add(b);
}
}
Panel2 class :
public class Panel2 extends JPanel{
private JFrame f;
public Panel2(JFrame frame) {
f = frame;
this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
JButton b = new JButton("Panel 2");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
f.setContentPane(new Panel1(f));
// Uncomment the instruction below to fix GUI "update-on-resize-only" problem
//f.setVisible(true);
}
});
add(b);
}
}
Hope that helps.
Regards.
I also had same problem but I found a solution. Just create a jframe object on top and call jframe methods at the bottom like jf.pack(), jf.setVisible(), jf.setSize(), jf.setDefaultCloseOpetion() should be at the bottom of the all UIs added in that frame you will find it work great.

What exactly a constructor does and why this code is not working in constructor?

I read about the constructer that a constructor is something that is used to set up the components of the container. So i wrote the following program which works well but only problem is that the label is not at the specified location i.e. 125,300. When I wrote the line label.setLocation(125,300) in the public run() then the code works well. Why it is not working when I write the line in the constructor? Basically it should work as the constructor is used to set up the components of the JFrame.
If not then What exactly is a constructor?
The Code:
public class RealGame extends JFrame implements Runnable, KeyListener{
JLabel label = new JLabel("I am a JLabel");
RealGame(){
setVisible(true);
requestFocus();
setContentPane(new JPanel());
getContentPane().setSize(640, 480);
setSize(640, 480);
label.setSize(50,50);
label.setOpaque(true);
label.setVisible(true);
label.setFont(new Font("Arial", Font.PLAIN, 18));
getContentPane().add(label);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new RealGame());
}
#Override
public void run() {
label.setLocation(125,300); //THIS IS IMPORTANT
JOptionPane.showMessageDialog(null, "Hello");
System.out.println("Done");
System.exit(0);
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
And also is the structure of the program correct? i.e. in many programs I saw that people use
SwingUtilities.invokeLater(new Runnable(){
public run(){
}
});
But instead I instantiated the main class. Will it give any problems in future?
but only problem is that the label is not at the specified location i.e. 125,300.
What is special about (125, 300). Don't use magic numbers in your program.
Swing uses layout managers to position components in a panel. The layout manager will determine the size and location of your component.
Read the section from the Swingtutorial on Layout Managers for more information and examples.
in many programs I saw that people use
Yes all GUI components should be updated on the Event Dispatch Thread(EDT). All the tutorial examples show you how to do this. You can also read the section in the tutorial on Concurrency in Swing for reasons why this is important.
When I wrote the line label.setLocation(125,300) in the public run() then the code works well
The code is added to the end of the EDT, which means it executes AFTER the layout manager. However this is only temporary. Try resizing the frame and the label will go back to the position determined by the layout manager. Don't try to set the location manually!

Java, swing opening new JFrame from another class

I have this piece of code:
public class GUI extends JFrame {
private PlaneUI planeui;
public GUI(PlaneUI planeui) {
this.planeui = planeui;
}
//We have put the code that creates the GUI inside a method
public GUI() {
start();
planeui.display();
} ...
This is just a test and I need the method "planeui.display" to work when the program starts, together with the method "start();" which already works.
public final class PlaneUI extends JFrame {
public void display() {
//Creates a new JPanel object
JPanel panelStart = new JPanel();
getContentPane().add(panelStart);
//Changing the default layout from Flowlayout to absolute
panelStart.setLayout(null);
setTitle("Reservationer"); //Sets the window title
setSize(236, 256); //Sets the default size of the window
setLocationRelativeTo(null); //Start location of the window (centered)
setDefaultCloseOperation(EXIT_ON_CLOSE); //Exits the window
}
}
I have imported the needed libraries and I feel like the problem lies in an object that isn't created correctly since I get a nullpointerexception. I tried running this planeUI class in the main method and it worked correctly. I just can't get it to work this way..
In function PlaneUI.display() add one last line setVisible(true) because your adding everything but not displaying anything
you have to add this into your display() method:
setVisible(true);
Otherwise, all you are doing is setting all the aspects of the JFrame and adding the JPanel to it. You have to make it visible afterwards.

JPanel doesn't update until resize Jframe

I subclass JPanel to overwrite paintComponent(Graphics), I want to draw an image onto jpanel in a jframe.
But my image hasn't shown up until I make a change to jframe's size.
This is my code:
public class ImagePanel extends JPanel{
public void setImage(BufferedImage bi)
{
image = bi;
revalidate();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(image != null)
{
g.drawImage(image, 0, 0, this);
}
}
}
Verify that you invoke setVisible() after adding components and calling pack(), as discussed in this related example. You may also need to adopt an appropriate layout. Invoking repaint(), as suggested here, may fix the symptom but not the underlying cause.
Take a look at the docs for JPanel.add(), which it inherits from java.awt.Container:
Appends the specified component to the end of this container. This is a convenience method for addImpl(java.awt.Component, java.lang.Object, int).
This method changes layout-related information, and therefore, invalidates the component hierarchy. If the container has already been displayed, the hierarchy must be validated thereafter in order to display the added component.
Emphasis added.
Therefore, if you modify a Container after it's already been displayed, you must call validate() in order for it to show up. Just invoking repaint() is not enough. You may have noticed that calling setVisible(true) also works; this is because it calls validate() internally.
If you want to "refresh" the JPanel then you should call repaint(), which will call your paintComponent(). This should fix your problem:
public void setImage(BufferedImage bi)
{
image = bi;
EventQueue.invokeLater(new Runnable()
{
public void run()
{
repaint();
}
});
}
Its good practice to update and change the GUI using the EDT. Heres more info on the EDT if you're interested:
How does the event dispatch thread work?
repaint doesn't need to be called from the EDT. If you're changing the GUI, such as setting text to a JLabel, it should be inside of the EDT. Heres more information on what can be called outside of the EDT (courtesy of nIcE cOw):
Safe to use Component.repaint() outside EDT?
I had the same problem and fixed it by call setVisible(true); the JFrame I was using.
Example : if your JFrame does not update after using :
jframe.setContentPane(new MyContentPane());
fix it with :
jframe.setContentPane(new MyContentPane());
jframe.setVisible(true);
I know that it sounds silly to do this even though your JFrame is already visible, but that's the only way I've found so far to fix this problem (the solution proposed above didn't work for me).
Here is a complete example. Run it and then uncomment the "f.setVisible(true);" instructions in classes Panel1 and Panel2 and you'll see the difference. Don't forget the imports (Ctrl + Shift + O for automatic imports).
Main class :
public class Main {
private static JFrame f;
public static void main(String[] args) {
f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setContentPane(new Panel1(f));
f.pack();
f.setVisible(true);
}
}
Panel1 class :
public class Panel1 extends JPanel{
private JFrame f;
public Panel1(JFrame frame) {
f = frame;
this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
JButton b = new JButton("Panel 1");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
f.setContentPane(new Panel2(f));
// Uncomment the instruction below to fix GUI "update-on-resize-only" problem
//f.setVisible(true);
}
});
add(b);
}
}
Panel2 class :
public class Panel2 extends JPanel{
private JFrame f;
public Panel2(JFrame frame) {
f = frame;
this.setLayout(new BoxLayout(this, BoxLayout.LINE_AXIS));
JButton b = new JButton("Panel 2");
b.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
f.setContentPane(new Panel1(f));
// Uncomment the instruction below to fix GUI "update-on-resize-only" problem
//f.setVisible(true);
}
});
add(b);
}
}
Hope that helps.
Regards.
I also had same problem but I found a solution. Just create a jframe object on top and call jframe methods at the bottom like jf.pack(), jf.setVisible(), jf.setSize(), jf.setDefaultCloseOpetion() should be at the bottom of the all UIs added in that frame you will find it work great.

Desktop application - how do I dynamically create and destroy forms

I'm creating a small crypto app for the desktop using java.
I'm using JFrames (import javax.swing.JFrame) with Oracle
JDeveloper 11g under Linux.
I want to have a "welcome" form/frame where users can choose
their encryption method, and then on choosing the method,
I want to dynamically create the appropriate form for the
chosen encryption method and also destroy/free/dispose() of
the welcome form. When the user has finished their encrypting,
they should close the frame/form (either by clicking on the
x at the top right - or using the Exit button or by any
method) and the welcome frame should be dynamically recreated
and appear.
I've tried various things - btnEncode_actionPerformed(ActionEvent e)
then this.dispose() - and I've fiddled with this_windowClosed(WindowEvent e)
and dispose(), but nothing seems to work.
Even a workaround using setVisibl(true/false) would be acceptable at
this stage - this has been wrecking my head all day. It's very
easy to do in Delphi!
TIA and rgs,
Paul...
something like this usually does the trick: (Note I haven't tested this)
public class WelcomeMsg extends JFrame
.
.
.
public void btnContinue_actionPerformed(ActionEvent e)
{
this.dispose();
SwingUtilities.invokeLater(new Runnable(){ new JFrameAppropriateWindow(args) });
}
where btnContinue is the Continue button on your welcome form and JFrameAppropriateWindow is the next frame you would like to show depending on the user's choice. Args are any arguments you need to pass.
When you are ready, you can simply dispose the current frame and relaunch an instance of WelcomeMsg
I put together this simple example for creating and displaying a panel depending on a user choice.
public class Window extends JFrame {
public Window() {
this.setLayout(new BorderLayout());
JComboBox encryptionCombobox = new JComboBox();
encryptionCombobox.addItem("foo");
encryptionCombobox.addItem("bar");
//...
encryptionCombobox.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// find choices and the correct panel
JPanel formPanel = new JPanel();
formPanel.setOpaque(true);
formPanel.setBackground(Color.RED);
//...
Window.this.add(formPanel, BorderLayout.CENTER);
Window.this.validate();
Window.this.repaint();
}
});
add(encryptionCombobox, BorderLayout.NORTH);
}
public static void main(String[] args) {
new Window().setVisible(true);
}
}
When I come to think about it, you should probably use a CardLayout instead, which allows you to switch between different panels (cards).

Categories

Resources