Create custom UI elements in Java - java

I want to draw customized elements on a JFrame.
I've tried it by creating a class UI (extends JFrame) and a class Component (extends JPanel). The component draws something on itself and the UI just adds this component. So until now, I've written this code:
File UI.java
package UIComponent;
import javax.swing.JFrame;
public class UI extends JFrame {
public UI(){
this.setSize(1024,684);
this.setTitle("This is just a test program.");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new Component(20,20,20,20));
this.add(new Component(40,30,20,20));
}
}
File Component.java
package UIComponent;
import java.awt.Color;
import javax.swing.JPanel;
import java.awt.Graphics;
public class Component extends JPanel {
int x, y, w, h;
public Component(int x, int y, int w, int h){
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
#Override
public void paintComponent(Graphics g){
g.setColor(Color.red);
g.fillRect(this.x, this.y, this.w, this.h);
}
}
But the result is not that what I accept. It draws just one Rectangle.

The x/y/w/h values have no bearing on the actual size of the compnent which is likely to be 0x0, meaning that you'd be painting out side of the visible area of the component.
Start by overriding the getPreferredSize method and return a area which would allow you painting to be visible, something like....
public Dimension getPreferredSize() {
return new Dimension(x + w, y + h);
}
For example.
JFrame uses a BorderLayout by default, which means that it will only allow one component to be visible within any of its 5 available positions.
This means that your example will only show the last component added.
Depending on what you intend to achieve, you might consider using an OverlayLayout or some other layout manager.
Personally, unless you had a particular need, I would not worry about the x/y position of the painting, and simply paint from the 0x0 position of component, allowing the containers layout manager to deal with the actually positing.
I'd as reconsider some of your naming, as Component already exists in the API and may cause confusion, and components already have a concept of position and size...
Remember, the position of the component within its container has no effect on where the components drawing starts. That is, 0x0 is always the top left corner of the component.

Don't extend JFrame. You are not adding any new behaviour to the frame.
Don't call your class Component. There is already an AWT class by that name so you will probably cause Swing to stop working.
this.add(new Component(20,20,20,20));
this.add(new Component(40,30,20,20));
The default layout manager for a JFrame is a BorderLayout. By default when you add components to the frame without specifying a constraint they go to the CENTER. The CENTER can only contain a single component, so that is why you only see the last one added.
Instead try adding one component to the BorderLayout.NORTH and one to the SOUTH.
Also, the components won't paint properly because you need to override the getPreferredSize() method of you custom component so the layout manager can do its job:
#Override
public Dimension getPreferredSize()
{
return new Dimension(w, h);
}
Also, the paintComponent() method should invoke super.paintComonent().
Check out the Swing tutorial. You should be reading the sections on Custom Painting and Layout Managers for more information.
Also, the painting of your rectangles should be done at an x/y location of (0, 0) so that the entire painting will fit in the width/height of your component. If you want the rectangle to appear at a specific location then you should be using a null layout in which case you are responsible for setting the location and size of the component.
If you are trying to just paint shapes on a panel then you should probably be playing with the Shape class instead of creating custom components. See Playing With Shapes for more ideas.

Just adding a LayoutManager should give your the two rectangles you're looking for
public class UI extends JFrame {
public UI(){
this.setSize(1024,684);
this.setTitle("This is just a test program.");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new GridLayout(1, 2)); // I used a simple grid layout.
this.add(new Component(20,20,20,20));
this.add(new Component(40,30,20,20));
}
public static void main(String[] args){
SwingUtlities.invokeLater(new Runnable(){
public void run(){
new UI();
}
});
}
}

Related

Cant draw two items to Jframe

I cant Draw two items( there will be more) to a Jframe, im trying to make a landscape, but the item painted last overwrites anything before it.
Main:
import javax.swing.*;
import java.awt.*;
public class TheComponets extends JComponent {
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setSize(600, 600);
frame.setTitle("A house on the water!");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
House home = new House();
Sun sun = new Sun();
frame.setLayout(new GridLayout(2,3));
frame.add(home);
frame.add(sun);
}
}
House class:
import javax.swing.*;
import java.awt.*;
import java.applet.*;
// Program to draw a house
public class House extends JComponent
{
public void paintComponent(Graphics g)
{
// Draw the roof
g.setColor(Color.red);
int xs[] = {100,160,220};
int ys[] = {100,50,100};
Polygon poly=new Polygon(xs,ys,3);
g.fillPolygon(poly);
// Draw the body of house
g.setColor(Color.blue);
g.fillRect(100,100,120,120);
// draw the door
g.setColor(Color.orange);
g.fillRect(145,160,30,60);
}
}
Sun class:
import javax.swing.*;
import java.awt.*;
public class Sun extends JComponent {
public void paintComponent(Graphics g)
{
// draw sun
g.setColor(Color.yellow);
g.fillOval(500, 0, 50, 50);
}
}
I want the house and the sun to show up in the Jframe, but as of now it will only show the last frame.add() object. I have only been programming for two months and dont know much about Swing and awt. Please try to keep that in mind when answering.
The reason of this, is that a JFrame uses a BorderLayout by default. When you frame.add(component) without any constraints, the component will be added to BorderLayout.CENTER position. So, no matter how many components you will add without constraints, borderlayout will override the older since all of them are being added to CENTER.
The solution would be either to choose where you want your components to be added:
frame.add(home,BorderLayout.CENTER);
frame.add(sun,BorderLayout.LINE_START);
either to change the layout of your container (the JFrame in your case):
frame.setLayout(new FlowLayout());
frame.add(home);
frame.add(sun);
Worth to read: A Visual Guide to Layout Managers
Finally, do not #Override paint() method. #Override paintComponent() method instead.
When you use individual panels for custom painting then your painting will be done in 2D since components are positioned in 2D space based on the layout manager used.
im trying to make a landscape,
Then keep all you custom painting in a single class.
First you paint the background. Then you paint the sun, then the house etc.
You will now have full control over the order in which items are painted.
To Add multiple components on JFrame, need to set layout from Layout manager. it can be FlowLayout or Gridlayout or BorderLayout....
In your case,it add Last component only because JFrame's default layout is BoderLayout and can add component
frame.add(home, BorderLayout.LINE_START);
you have PAGE_START, PAGE_END, LINE_START, LINE_END, CENTER position to add component. so add second component to other position or change layout of frame using
frame.setLayout(new FlowLayout());

How to have a class create its own instance of Graphics

I have a class called CharMove,in it are the paint(Graphics g) method, and some custom methods. The class should create a square,then move that square randomly around the screen. However,when I create two instances of this class in my World Class,only one square appears. First the square doesn't move but the new coord.'s are displayed,then after 5 runs the square begins to move randomly. I think the program is getting caught on the Graphics method because only one square is being created,when the CharMove class should be creating another instance of Graphics.I have searched online but can't find a way to create different instances of Graphics.Thanks in advance.
CharMove Class
import java.awt.*;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import java.util.Random;
public class CharMove extends JPanel {
int x = 250;
int y = 250;
public void paint(Graphics g) {
Graphics pane = (Graphics2D) g;
pane.setColor(Color.blue);
pane.fillRect(x, y, 10, 10);
}
public void movement(JFrame frame) {
for (int i=0;i<5;i++) {
try {
TimeUnit.SECONDS.sleep(1);
this.x = Getx(this.x,frame);
this.y = Gety(this.y,frame);
frame.repaint();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public int Getx(int a, JFrame frame) {
Random rn = new Random();
int xnum = rn.nextInt(10)-5;
a += xnum;
System.out.println("x:" + a);
return a;
}
public int Gety(int b, JFrame frame){
Random rn = new Random();
int ynum = rn.nextInt(10)-5;
b += ynum;
System.out.println("y:" + b);
return b;
}
}
World Class
import java.awt.*;
import java.util.concurrent.TimeUnit;
import javax.swing.*;
import java.util.Random;
public class World {
public static void main(String[] args) {
JFrame game = new JFrame();
game.setTitle("Matrix");
game.setSize(500, 500);;
game.getContentPane().setBackground(Color.white);
game.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
game.setVisible(true);
CharMove char1 = new CharMove();
CharMove char2 = new CharMove();
game.add(char1);
game.add(char2);
char1.movement(game);
char2.movement(game);
}
}
In swing, all of your painting should be down in paintComponent(Graphics g) (rename your method)
To do animation, you should use a Swing Timer (w/ an ActionListener) to update the positions of your animated items. Once that's done, the timer should call repaint();
public void actionPerformed(ActionEvent ae) {
this.x = Getx(this.x,frame);
this.y = Gety(this.y,frame);
frame.repaint();
}
However,when I create two instances of this class in my World Class,only one square appears.
The default layout manager for a JFrame is a BorderLayout.
game.add(char1);
game.add(char2);
When you add components without specifying a constraint then both components are added to the CENTER. However, only one component can be added to the CENTER so only the last one added is displayed.
Try:
game.add(char1, BorderLayout.PAGE_START);
game.add(char2, BorderLayout.PAGE_END);
However when you do this the componens won't be displayed because they have a (0, 0) preferredSize. So you will also need to override the getPreferredSize() method of your CharMove class.
#Override
public Dimension getPreferredSize()
{
return new Dimension(300, 200);
}
Also, custom painting should be done in the paintComponent(...) method and you need to invoke super.paintComponent(...) at the start to clear the background.
The repaint() method in your movement() method should be on the panel, not the frame, since you are changing properties of the panel.
Each CharMove is essentially a JPanel which draws a single square of size 10 somewhere on itself when it is painted. You are adding two CharMove panels to the game JFrame (which in fact adds them to the default content pane, which has a subclassed BorderLayout). As you are not providing a layout constraints object, in fact both panels are being added to the BorderLayout.CENTER of the content pane, and the second is completely covering the first.
To correct this you should modify CharMove so that it paints all of the squares (eg by maintaining an array or some sort of collection of squares, and painting all of them in the paint method) and just add that one panel to the JFrame.
Apart from this issue, while you are animating the squares in the movement method you are blocking the Event Dispatch Thread, meaning that during the animation you won't be able to move any other windows or respond to any mouse clicks or other inputs. Take ControlAltDel's advice about using the Swing Timer for animation to correct this problem.

Strange repositioning using repaint();

I have a JSlider in a JPanel that return me a value of R-G-B .
I create it, in the Costructor of JPanel. I draw in same Panel (using paintComponent) a little circle, and I change his color using the Slider. I want that the color change in contemporany of slider shift.
So, i use the method repaint.. Next to Panel there is another Panel, with two button.. If I use method repaint in first panel , the buttons of second panel duplicated in the topLeft of First Panel. Why? Thank's you.
First Panel:
public class OptionsPanel extends JPanel {
static JSlider RBG = new JSlider(0,255);
OptionsPanel(){
this.setVisible(false);
this.setSize(350,1000);
this.setLayout(null);
this.setBackground(new Color(200,200,0));
Main.f1.add(this);
RBG.setVisible(true);
RBG.setSize(255,50);
RBG.setLocation(30,240);
this.add(RBG);
LotL lotl = new LotL();
Button save = new Button("Save");
save.setVisible(true);
save.setSize(100,40);
save.setLayout(null);
save.setLocation(60,300);
save.addActionListener(lotl);
save.setBackground(Color.yellow);
save.identificatore=3;
this.add(save);
}
boolean draw=false;
#Override
public void paintComponent(Graphics g){
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
repaint();
}
}
Second Panel:
public class FirstPanel extends JPanel{
FirstPanel(){
this.setVisible(true);
this.setSize(1000,1000);
this.setLayout(null);
this.setBackground(new Color(255,200,180));
Main.f1.add(this);
Button start = new Button("Start Game!");
Button options = new Button("Options");
LotL LotL = new LotL();
start.setVisible(true);
start.setSize(200,80);
start.setLayout(null);
start.setLocation(400,450);
start.addActionListener(LotL);
start.setBackground(Color.green);
start.identificatore=1;
this.add(start);
options.setVisible(true);
options.setSize(200,70);
options.setLayout(null);
options.setLocation(400,550);
options.addActionListener(LotL);
options.setBackground(Color.green);
options.identificatore=2;
this.add(options);
}
}
You've broken the paint chain...
#Override
public void paintComponent(Graphics g){
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
repaint();
}
Graphics is a shared resource, which gets passed to ALL the components that are painted during a given paint cycle.
One of the jobs of paintComponent is to prepare the Graphics context for painting, but filling with the components background color.
You MUST call super.paintComponent before performing any custom painting.
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(50,100,70,70);
g.setColor(new Color(RBG.getValue(),180,200));
g.fillOval(50,100,70,70);
}
Also, there is never any need for paintComponent to be public, no one should ever be calling directly and NEVER modify the state of a component from within any paint method which may trigger a repaint, you will get yourself into a infinite loop which will eventually consume your CPU and make you computer unusable.
Take a look at Painting in AWT and Swing and Performing Custom Painting for more details
You should also avoid using null layouts, pixel perfect layouts are an illusion within modern ui design. There are too many factors which affect the individual size of components, none of which you can control. Swing was designed to work with layout managers at the core, discarding these will lead to no end of issues and problems that you will spend more and more time trying to rectify

Drawing from another class wont take the right dimensions of the JPanel

Ok i have a JPanel such as this one :
public class GUI {
JFrame frame = new JFrame("Net");
JPanel panel = new JPanel();
public GUI()
{
frame.setSize(835,650);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setVisible(true);
frame.add(panel);
panel.setSize(600,600);
panel.setLocation(215,5);
panel.add(new DrawPlanes(300,300,200,Color.BLACK));}
There are some other panels in there tables etc. My main is this one :
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable(){
#Override
public void run(){
//new GUI();
new GUI().buildTable();
}
});
And there i another class this one :
public class DrawPlanes extends JPanel
{
private static int centreX, centreY, radius;
private Color colour;
public DrawPlanes()
{
centreX = 300;
centreY = 300;
radius = 200;
colour = Color.BLACK;
}
public DrawPlanes(int centreX,int centreY, int radius, Color colour)
{
this.centreX = centreX;
this.centreY = centreY;
this.radius = radius;
this.colour = colour;
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
System.out.println("ppp");
Graphics2D g2D = (Graphics2D) g;
g2D.setStroke(new BasicStroke(2F));
g.setColor(Color.BLACK);
g.drawOval(centreX - radius , centreY - radius, radius * 2 , radius * 2);
......
}
}
Ok so i ve setted the background colour of my panel red to see whats going on the whole panel its red but there is a small grey square where i believe the drawings are. I ve tried to change opaque as there might be an incompatibility issue but nothing changed at all.Any suggestions,is there anything that i m missing?
link of what is the result of
panel.add(new DrawPlanes(300,300,200,Color.BLACK))
http://dc626.4shared.com/img/FeYopZC1/s7/142d22f1be0/2013-12-08_142846.png?async&rand=0.9010817544924218
what does the DrawPlanes class draws when i checked it having the main and a panel etc in the DrawPlanes itself http://dc626.4shared.com/img/NPDkiQRJ/s7/142d22f23b0/1451491_586878858047235_191988.jpg?async&rand=0.27479583155781395 .When i apply a layout manager that grey square just moves to the center when i use getPreferredSize overriden or not the whole red panel appears grey.
frame.add(component) function eventually add your component to frame's content pane which has BorderLayout as default layout manager.
A JPanel uses FlowLayout as default layout which respect component's preferred size.
As your 'panel' and 'frame' is satisfying above two as a default, size hint with setSize(Dimension) or setBounds(Dimenstion) to component won't have effect.
You should provide size hint using setPreferredSize(Dimenstion)(to DrawPanel instance of your context) and if specifying minimum/maximum size is needed setMinimumSize(Dimenstion) and setMaximumSize(Dimenstion).
However it is considered as a better practice to override getXXXSize(Dimenstion): xxx represents Preferred/Minimmum/Maximum always which allows to adjust size of component with it's content.
Instead of calling setSize(Dimension) on a window it is preferable to call pack() at the end of addition of child components.
We should call setVisible(true) after finishing addition of all of the child components and following above point, after pack().
Please, start with the tutorial: Laying Out Components Within a Container
Edit:
Let us edit your GUI() constructor code and see what happens:
public GUI()
{
frame.setSize(835,650);
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
//frame.setVisible(true); // <<--- call it at the end of the code
frame.add(panel);
//panel.setSize(600,600); <<--- removing set size
panel.add(new DrawPlanes(300,300,200,Color.BLACK));
frame.setVisible(true);
}
And the DrawPnales will extend JComponent:
public class DrawPlanes extends JComponent{
public DrawPlanes(int centreX,int centreY, int radius, Color colour)
{
this.centreX = centreX;
this.centreY = centreY;
this.radius = radius;
this.colour = colour;
}
#Override
public Dimension getPreferredSize() {
return new Dimenstion(width, height);
// ^ provide your required size
}
}
If this still doesn't make any sense to you, then please try learning Swing layout managers a little bit further. Otherwise no matter how hard we hit our head on the table, possibly we won't be able to achieve any thing.
You're missing the fact that panels are laid out inside their container using a LayoutManager. The default layout manager of JPanel is a FlowLayout. The FlowLayout uses the preferred width and height of the components it lays out to decide where to place them in the container, and which size they should have. But your DrawPlanes panel doesn't override getPreferredSize(), so its preferred size is the default one.
Every time you use setSize() on a component or frame, you have a 99.9% probability of doing something wrong. Learn layout managers. If you design a custom component like your DrawPlanes component, which is not just a container for other components, but has a custom paintComponent() method, then override getPreferredSize(), getMaximumSize() and getMinimumSize() to tell the layout managers how they should display your component, and to make sure your custom component always has the appropriate size. You never set the size of a JButton, right? That's because the JButton itself decides, based on the text and icon it contains, which size it should have.

Swing: How can I prevent flickering and "vibrating" of a component, when restricting its movement?

I need to restrict movement of a component (JInternalFrame) inside a JPanel.
More exact: a component should only move along one axis when dragged by the user.
I tried to do it by adding a component listener and reset the position
of one axis everytime the component moves. But it "vibrates"
(moves rapidly during dragging).
I even wrote a custom layout manager, it didn't help a little!
I guess, the problem is: Both the layout manager and the listener
handle the movement-event after the component is actually moved, right?
Any suggestions?
Should (can I) intercept some event, and modify it, before it's delivered?
There is no "event" you need to intercept. Once Swing processes the mouse event, it tells the component to move - and that's where you can tweak the movement values.
move, setLocation and resize are all just wrappers to setBounds. Extend JInternalFrame, override the setBounds() method and ignore the new value of x (or y if you want horizontal movement) before calling super.setBounds().
import javax.swing.JInternalFrame;
public class VerticalOnlyFrame extends JInternalFrame {
public void setBounds(int x, int y, int width, int height) {
super.setBounds(getBounds().x, y, width, height);
}
}
That should solve your example, although looking at the JRE code setBounds() is actually a wrapper to the deprecated reshape() method so if you're feeling brave override reshape() instead.
you might want to subclass your InternalFrameUI. I see in BasicInteralFrameUI.BorderListener.mouseDragged(event)(line 885 in version 1.129) a call to getDesktopManager().dragFrame(frame, newX, newY);
You could extend BasicInternalFrameUI to return a custom DesktopManager (extension of DefaultDesktopManager), where in dragFrame() you test the identity of your frame and adjust the axis.
edit:
thinking about it, you are right - it is too complex. as the UI for the internal frame defers to the DesktopManager of the JDesktopPane, you can set it there. here is a poc:
public Demo() {
JFrame frame = new JFrame();
frame.setSize(300,300);
JDesktopPane df = new JDesktopPane();
DesktopManager dm = df.getDesktopManager();
df.setDesktopManager(new DefaultDesktopManager(){
public void dragFrame(JComponent f, int newX, int newY) {
super.dragFrame(f, newX, 5);
}
});
JInternalFrame jif = new JInternalFrame("test ");
jif.setLocation(5, 5);
jif.setSize(150,100);
jif.setVisible(true);
df.add(jif);
frame.setContentPane(df);
frame.setVisible(true);
}

Categories

Resources