How could I update content of several visible (at one time) components separately(independently) ?
For example, I would like to show some kind of progress indicator with connected information, and it should only be updated/painted without painting all other components on form?
Or if I have more then one components in progress and must update only their content.
You can (and will have to, here) schedule your updates. You SHOULD NOT be running the long running calculation in the GUI thread (which appears unlikely, if you have a progress bar). But you still need to let the GUI know it needs to update... Something like so:
import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
// I didn't seem to see anything like this with a quick look-through.
// anybody else know differently?
public class ComponentUpdater implements ActionListener {
private static List<Component> componenets = new ArrayList<Component>();
public void addComponent(Component component) {
componenets.add(component);
}
#Override
public void actionPerformed(ActionEvent arg0) {
for(Component component : componenets) {
component.repaint();
}
}
}
And to use it, you need a timer:
UpdatingComponent componentToUpdate = new UpdatingComponent(dataSourceToExamine);
panel.add(componentToUpdate);
ComponentUpdater updater = new ComponentUpdater();
updater.addComponent(componentToUpdate);
Timer schedule = new Timer(500, updater);
timer.setRepeats(true);
timer.start();
This will cause every component added to the updater to have repaint() caller ever 500 milliseconds, forever.
There are of course much more elegant ways to do this (like being able to specify update location), but this is a simple one to get you started.
Whenever you call the repaint function (or one of your methods such as setText calls it for you) the component will repaint itself and all other components inside itself. In order to just repaint one thing, just call the repaint() method of that particular component. This will save memory and be much more predictable.
So in an example with a JProgressBar
JFrame frame = new JFrame("Title");
JPanel panel = new JPanel();
JProgressBar pBar = new JProgressBar(SwingConstants.HORIZONTAL, 0, 100);
panel.add(pBar);
frame.add(panel);
pBar.repaint(); // Will only repaint the progress bar
You can also repaint only a specific section of your program. So assuming the progress bar is located at (100, 100) and is 100 wide and 20 tall:
frame.repaint(new Rectangle(100, 100, 100, 20));
Related
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 am trying to display images on the screen using Graphics but the screen doesn't load
The output screen appears but only show The black screen and not the images
The code gets compiled properly so why am i not getting the output
package game;
import java.awt.*;
import javax.swing.JFrame;
public class Screen {
private GraphicsDevice vc;
public Screen(){
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
vc=env.getDefaultScreenDevice();
}
public void setFullScreen(DisplayMode dm, JFrame window){
window.setUndecorated(true);
window.setResizable(false);
vc.setFullScreenWindow(window);
if(dm !=null && vc.isDisplayChangeSupported()){
try{
vc.setDisplayMode(dm);
}catch(Exception ex){}
}
}
public Window getFullSCreenWindow(){
return vc.getFullScreenWindow();
}
public void resotreScreen(){
Window w= vc.getFullScreenWindow();
if(w!=null){
w.dispose();
}
vc.setFullScreenWindow(null );
}
}
package game;
import java.awt.*;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
class Images extends JFrame{
public static void main(String[] args){
DisplayMode dm = new DisplayMode(800,600,16,DisplayMode.REFRESH_RATE_UNKNOWN);
Images i = new Images();
i.run(dm);
}
private Screen s;
private Image bg;
private Image pic;
private boolean loaded;
public void run(DisplayMode dm){
setBackground(Color.BLUE);
setForeground(Color.WHITE);
setFont(new Font("Arial",Font.PLAIN,24));
loaded =false;
s = new Screen();
try{
s.setFullScreen(dm, this);
loadpics();
try{
Thread.sleep(10000);
}catch(Exception ex){}
}finally{
s.resotreScreen();
}
}
public void loadpics(){
bg = new ImageIcon("C:\\Users\\Dhruv\\Downloads\\Ronaldo.jpg").getImage();
pic =new ImageIcon("C:\\Users\\Dhruv\\Downloads\\Messi.jpg").getImage();
loaded= true;
repaint();
}
public void paint(Graphics g){
if(g instanceof Graphics2D){
Graphics2D g2 =(Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
}
if(loaded){
g.drawImage(bg,0,0,null);
g.drawImage(pic,170,180,null);
}
}
}
Let's start with background information...
A JFrame is a container for a JRootPane, which contains the contentPane, JMenuBar and JGlassPane
When you override paint of top level container like JFrame, you are only painting the bottom most component, the JRootPane and it's contents are then painted over the top, making it kind of pointless.
See How to Use Root Panes for more details
Painting is also a complex operation, failing to call super.paint will cause no end of issues, make sure you always call the super paint method before painting unless you really understand how it works and are prepared to do it's job manually.
In Swing you are instead encouraged to extend from a JComponent based class (JPanel been the preferred) and override its paintComponent method and perform your custom paint there
This component can either be added to the window or set as the contentPane or added to some other container depending on your needs.
See Painting in AWT and Swing and Performing Custom Painting for more details.
ImageIcon uses background thread to load it's images, so even though it returns, the image might not be realised (or fully loaded). When you use g.drawImage(bg,0,0,null);, passing null as the ImageObserver, it prevents the container from knowing when the image changes and allowing it to automatically repaint itself.
What's cool is, all Component based classes implement ImageObserver, so pretty much anything which can paint can act as an ImageObserver.
Convention would encourage to pass this as the ImageObserver.
Generally, a better solution is to use ImageIO, which when it loads images, won't return until the image is fully realised.
Have a look at Reading/Loading an Image for more details.
Thread.sleep(10000); is a dangerous thing to use in Swing. It has the potential to stop the UI from been updated or respond to other input and events, making your program appear as if it's hung, because it has. But in your case, it means you're violating the single thread rules of Swing.
Swing is a single threaded environment, you should never perform any action which might block the Event Dispatching Thread and you should never update the UI from outside the context of the EDT.
There are solutions available to help you, Swing Timer for generating periodical events which are dispatched within the EDT and SwingWorker for performing long running operations which have support for updating the UI.
See The Event Dispatch Thread for more details.
My recommendation is to not worry about the full screen support, focus on getting the images painting using a normal window and the add the full screen support. This allows you to solve problems within an isolated set of functionality, making it much easier to solve.
I use .show() before a "blocking" code like a while loop. But even though the .show gets called, the UI doesn't actually show the called panel.
Here is the code that shows the issue:
(WARNING: This code contains a while true loop.)
import javax.swing.JFrame;
import java.awt.CardLayout;
import java.awt.event.ActionEvent;
import javax.swing.JPanel;
import javax.swing.JLabel;
import javax.swing.JButton;
public class CardTest extends JFrame{
public CardTest() {
CardLayout cl = new CardLayout(0,0);
getContentPane().setLayout(cl);
JPanel panelA = new JPanel();
getContentPane().add(panelA, "PanelA");
JLabel lblPanelA = new JLabel("Panel A");
panelA.add(lblPanelA);
JButton btnSwitchToPanel = new JButton("Switch to Panel B");
panelA.add(btnSwitchToPanel);
JPanel panelB = new JPanel();
getContentPane().add(panelB, "PanelB");
JLabel lblPanelB = new JLabel("Panel B");
panelB.add(lblPanelB);
btnSwitchToPanel.addActionListener(new java.awt.event.ActionListener() {
public void actionPerformed(ActionEvent event) {
cl.show(getContentPane(), "PanelB");
getContentPane().revalidate();
// Here is the problem. Even though cl.show is called first,
// it still doesn't show, before the while loop has terminated.
int i = 0;
while(i < 1000000){
i++;
System.out.println(i);
}
}
});
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args){
new CardTest();
}
}
If you are wondering, I need this for a downloader, where the while true loop (to download a file) is called after pressing a button in the first panel. The second panel contains the progress bar. But the progress panel never gets displayed even though the .show function is called before the download code.
UPDATE
I do know that putting the loop into a new thread, solves the draw problem, but it also introduces other problems, because I rely on sequential execution of functions after the loop(download file(loop), Unzipp file, move those files...).
The best solution would be to find a way to allow the .show() call to actually take the time to switch panes before continuing with the loop.
I use .show() before a "blocking" code like a while loop. But even though the .show gets called, the UI doesn't actually show the called panel.
Yes, because you are "blocking" the Event Dispatch Thread (EDT) which is responsible for repainting the GUI. So the GUI can't repaint itself until the code finishes executing.
You need to create a separate Thread to executing the long running task so you don't block the EDT. One way to do this is to use a SwingWorker. The SwingWorker will create the Thread for you and will notify you when the task is complete so you can update the GUI.
Read the section from the Swing tutorial on Concurrency for more information and a working example.
This happens because you are doing work on the EventDispatchingThread. This Thread is also responsible for actually drawing the GUI.
You have no other choice than doing your work in another thread.
E.g.: (Quick + Dirty)
new Thread(){
#Override
public void run() {
while (...) {...}
}
}.start();
This is because redrawing the UI is done in the same thread as event processing and doesn't happen until after the event processing has completed (i.e., all event handling methods have returned).
Best thing to do is move that "blocking" code into a runnable and execute it in a worker thread.
I've looked at several other posts, and I have yet to find a clear answer. I don't entirely understand the paint method, which is probably my problem, but nowhere can I find a clear explanation. Can someone help me get this one working? The issue is that the paint method is not running. Everything else seems to work fine, but I do not see the oval I tell the program to render in the frame.
import java.awt.Color;
import java.awt.Component;
import java.awt.Frame;
import java.awt.Graphics;
#SuppressWarnings("serial")
public class TestObject extends Component {
MouseResponder mouseListener = new MouseResponder(); // Creates a new mouse listener.
WindowResponder windowListener = new WindowResponder(); // Creates a new window listener.
Frame mainFrame = new Frame(); // Makes a new frame.
public TestObject() {
mainFrame.setSize(400,500); // Makes the new frame 400 by 500 in size.
mainFrame.setLocationRelativeTo(null); // Sets the location of the window to center it.
mainFrame.setTitle("A Test program!"); // Sets frame label.
mainFrame.setBackground(new Color(199,199,199)); // Sets the background color of the window.
mainFrame.addWindowListener(windowListener); // Adds the window listener so close works.
mainFrame.addMouseListener(mouseListener); // Adds the mouse listener to the frame.
mainFrame.setVisible(true); // Makes the new frame visible.
System.out.println("[TestObject] Window" + // Prints a console message when main window is launched.
" configured and launched.");
}
public void paint(Graphics pane) {
System.out.println("[TestObject] Painting.");
pane.setColor(Color.BLACK);
pane.drawOval(10,10,10,10);
}
}
Other Info:
MouseResponder and WindowResponder are separate functioning classes.
The TestObject class seen above is called by a main class which
creates a new TestObject. The frame displays successfully as I
specify.
Thank you for any help!
-Docithe
You are late for your homework buddy !
Take a new java file.
Create a class
Make it extend JFrame
override the paint method
put a println in it
Take a second file
put a main in it
instanciate your first class and call show
move the window around, println should print out, meaning your code in paint is executing.
That's the way to do it in OOP, and for sure in java. Read more.
paint() is responsible for rendering a component when it is visible.
At least in your code snippet, you did not add the test-component to the frame - thus, it is not displayed and not painted.
public TestObject() {
//...
mainFrame.add( this );
//...
}
This still might not work because your Test Component is 0x0 pixel.
So you also
#Override
getPreferredSize(){
return new Dimension( 20, 20 );
}
you are mixing two things here, creating a frame and creating a component. If I understand you correctly, you want to create a frame and within that frame have a custom component draw an oval.
The component is just this:
public class TestObject extends Component {
public void paint(Graphics pane) {
System.out.println("[TestObject] Painting.");
pane.setColor(Color.BLACK);
pane.drawOval(10,10,10,10);
}
}
and your main program looks more like this:
public static void main(String[] args)
{
MouseResponder mouseListener = new MouseResponder();
WindowResponder windowListener = new WindowResponder();
Frame mainFrame = new Frame();
mainFrame.setSize(400,500);
mainFrame.setLocationRelativeTo(null);
mainFrame.setTitle("A Test program!");
mainFrame.setBackground(new Color(199,199,199));
mainFrame.addWindowListener(windowListener);
mainFrame.addMouseListener(mouseListener);
mainFrame.add(new TestObject());
mainFrame.setVisible(true);
}
I am not saying this code will run, but it splits the two things in what they should be, a main program creating the frame, and a component painting an oval. I agree with Snicolas on the reading more...
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