Scrollbar not appearing on custom Panel - java

I have made a class RefreshablePanel that extends JPanel
public class RefreshablePanel extends JPanel {
static String description="";
static int x=10;
static int y=11;
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (String line : description.split("\n"))
g.drawString(line, x, y += g.getFontMetrics().getHeight());
}
void updateDescription(String dataToAppend){
description = description.concat("\n").concat(dataToAppend);
System.out.println("The description is "+description);
}
}
and then I am adding it into my GUI_class like this
JScrollPane scrollPane_2 = new JScrollPane();
scrollPane_2.setBounds(32, 603, 889, 90);
frmToolToMigrate.getContentPane().add(scrollPane_2);
descriptionPanel = new RefreshablePanel();
scrollPane_2.setViewportView(descriptionPanel);
descriptionPanel.setBackground(Color.WHITE);
descriptionPanel.setLayout(null);
I have added the scrollbar in the class where I am making an instance of RefreshablePanel but scrollbar is not appearing.
I have tried adding
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 1010);
}
to refreshable Panel but then the string is disappearing like this
and nothing is appearing when I scroll down

You're using null layouts, so nothing is going to work the way it should. Swing is designed at the core to utilise layout managers.
You're RefreshablePanel has no discernible size, meaning that when you add to the scroll pane, the scroll pane is likely to simply think it's size should 0x0. RefreshablePanel needs to provide some kind of size hint back to the scrollpane, preferably via the getPreferredSize method
You could use a JLabel (with text wrapped in html) or a non-editable JTextArea to achieve the same results
Updated
Take a quick check of your code. You are declaring the x and y values as static and you are incrementing the y value in your paintComponent method
static int x=10;
static int y=11;
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (String line : description.split("\n"))
g.drawString(line, x, y += g.getFontMetrics().getHeight());
}
This means two things.
If you have more than one instance of your RefreshablePanel they will ALL share the same x/y values AND update them
y is constantly being update to a new position, so if the panel is painted twice, on the second paint, the y position will start from the position it was last at when the first call exited.
Remember, you don't control the paint process. A paint cycle may be executed at any time that the system decides it needs to...
Make the x/y values local variables to the paintComponent method...
Updated
The scroll pane will try and match the preferred size of the component if the available space allows for it. This may mean that the scroll bars may not appear until you resize the window...but you're using a null layout so that won't work for you...
To affect the size of the scroll pane, you can use the Scrollable interface instead...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Scrollable01 {
public static void main(String[] args) {
new Scrollable01();
}
public Scrollable01() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
RefreshablePanel pane = new RefreshablePanel();
pane.updateDescription("1. You're using null layouts, so nothing is going to work the way it should. Swing is designed at the core to utilise layout managers.");
pane.updateDescription("2. You're RefreshablePanel has no discernible size, meaning that when you add to the scroll pane, the scroll pane is likely to simply think it's size should 0x0. RefreshablePanel needs to provide some kind of size hint back to the scrollpane, preferably via the getPreferredSize method");
pane.updateDescription("3. You could use a JLabel (with text wrapped in html) or a non-editable JTextArea to achieve the same results");
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new JScrollPane(pane));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RefreshablePanel extends JPanel implements Scrollable {
public String description = "";
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 1010);
}
#Override
protected void paintComponent(Graphics g) {
int x = 10;
int y = 11;
super.paintComponent(g);
for (String line : description.split("\n")) {
g.drawString(line, x, y += g.getFontMetrics().getHeight());
}
}
void updateDescription(String dataToAppend) {
description = description.concat("\n").concat(dataToAppend);
System.out.println("The description is " + description);
repaint();
}
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(400, 400);
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 64;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 64;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
}

Related

How to prevent the JPanel from being updated?

I'm creating a sort of paint application. The user can move a circle in a JPanel by pressing/dragging the mouse.
I have a JCheckBoxMenuItem in one of my JMenus:
JCheckBoxMenuItem checkitem = new JCheckBoxMenuItem("Draw mode",false);
When it is not activated, the circle can only be moved (by dragging/pressing) and the previous circle will be erased.
When it is activated, the circle can only be moved, but the previous circle will not be erased when dragging/pressing the mouse ( This works the same way as a paint program )
Shortened version of my code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class GUI extends JFrame implements MouseListener, MouseMotionListener, ActionListener, ItemListener
{
JPanel mainPan, colorPan;
Color color = Color.BLACK;
JCheckBoxMenuItem checkitem;
boolean clear = true;
public GUI(String header)
{
maker();
mainPan.addMouseListener(this);
mainPan.addMouseMotionListener(this);
add(mainPan , BorderLayout.CENTER);
add(colorPan, BorderLayout.PAGE_END);
}
public void maker()
{
colorPan = new JPanel();
colorPan.setLayout(new GridLayout(1, 0));
mainPan = new JPanel(){
#Override
public void paintComponent(Graphics g)
{
//g.setColor(Color.WHITE);
//g.fillRect(0,0,getWidth(),getHeight());
if(clear)
super.paintComponent(g); //Do the same thing as above(Clear JPanel)
g.setColor(color);
g.fillOval(x,y,50,50); //x and y are integer variables that I use in my full program
}
};
checkitem = new JCheckBoxMenuItem("Draw mode",false);
//After adding this to a JMenu,
checkitem.addItemListener(this);
}
public void itemStateChanged(ItemEvent e)
{
if(e.getStateChange() == ItemEvent.SELECTED)
{
clear = false;
}
else
{
clear = true;
}
}
}
The below screenshot shows the output of my full program:
colorPan is the JPanel full of JButtons of different colors. The top of it is mainPan.
Right now, the "Draw mode" doesn't work as expected. I had always thought that super.paintComponent(g); was the one that clears/resets the screen when repaint() is called. But I removed that and was quite surprised to see the program behave the same way.
Basically, my problem is here:
if(clear)
super.paintComponent(g);
I need to prevent everything from being cleared when repaint() is called. How do I achieve what I want?
It is not in this code where changes should be made. And it is not paint method which should be changed. Paint paints whenever is required either by your or by system. When window is resized or moved or partially covered - it uses paint to paint picture again.
What you should really do is to stop updating coordinates for your painted oval. It could be done in mouse listener or in coordinates setter or, better, in control part which manages these coordinates. Your checkbox should control ability to change your model. It should not control painting.
There is commonly used pattern Model-View-Controller - look at it. Maybe it could look like overkill for such small application but even Swing itself is built on this pattern so you already follow it. Issues rise when you try to break it. So - don't.
You can't "prevent the JPanel from being updated;" paintComponent() will be called asynchronously, as required by the system. Instead, condition attributes of your view class in a way that allows your implementation of paintComponent() to render everything whenever it is called.
In the example below, the foreground color is changed with each mouse click and paintComponent() uses the revised setting. In the more elaborate example cited here, ClearAction clears the List<Node> and List<Edge> that define the graph's model. Absent a call to super.paintComponent(g), otherwise required for an opaque component, a call to fillRect() in paintComponent() cleans up any leftover selection artifacts.
public void actionPerformed(ActionEvent e) {
nodes.clear();
edges.clear();
repaint();
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
/** #see https://stackoverflow.com/a/5312702/230513 */
public class MouseDragTest extends JPanel {
private static final String TITLE = "Drag me!";
private static final Random r = new Random();
private static final int W = 640;
private static final int H = 480;
private Point textPt = new Point(W / 2, H / 2);
private Point mousePt;
private Color color = Color.black;
public MouseDragTest() {
this.setFont(new Font("Serif", Font.ITALIC + Font.BOLD, 32));
this.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
mousePt = e.getPoint();
setColor(Color.getHSBColor(r.nextFloat(), 1, 1));
repaint();
}
});
this.addMouseMotionListener(new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
int dx = e.getX() - mousePt.x;
int dy = e.getY() - mousePt.y;
textPt.setLocation(textPt.x + dx, textPt.y + dy);
mousePt = e.getPoint();
repaint();
}
});
}
public void setColor(Color color) {
this.color = color;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(color);
int w2 = g.getFontMetrics().stringWidth(TITLE) / 2;
g.drawString(TITLE, textPt.x - w2, textPt.y);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame f = new JFrame(TITLE);
f.add(new MouseDragTest());
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
}

FlowLayout or BoxLayout? in JAVA

I try to do this:
but if I use GridLayout, the heigth is bery small
PanelPost.setLayout(new GridLayout(20, 1, 0, 12));
for(int y=0;y<15;y++){
JPanel p=new JPanel();
p.setBackground(Color.RED);
p.setLayout(null);
p.setSize(PanelPost.getWidth(),150);
PanelPost.add(p);
}
if I use FlowLayout, only displays some points
With BoxLayout
setLayout(new BoxLayout(pane, BoxLayout.Y_AXIS));
the panels are in the center and the width and height are not respected
and if i use setLayout(null) the scroll does not work then
What is the best way to do this? :C
Your problem starts here -> p.setLayout(null);. GridLayout will use the preferredSize of the components to determine the cell sizes it wants to use. This value is determine by the panel's layout manager...which you've now discarded...
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
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Rectangle;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Scrollable;
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();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new JScrollPane(new ListOfStuffPane()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class ListOfStuffPane extends JPanel implements Scrollable {
public ListOfStuffPane() {
setLayout(new GridLayout(20, 1, 0, 12));
for (int y = 0; y < 15; y++) {
JPanel p = new JPanel(new FlowLayout(FlowLayout.LEFT));
p.setBackground(Color.RED);
p.add(new JLabel("Boo"));
add(p);
}
}
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(100, 200);
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return true;
}
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
}
}
See Laying Out Components Within a Container for more details
You can use BoxLayout to solve this issue. Let me explain. First of all, let's consider your scenario; you want a scrollable list, right ? with components having fixed height, but varying width (since you had used the container's getWidth() as the width). So, for creating some thing "Scrollable", you need a ScrollPane in Java. JScrollPane is the preferred scrollable container. Now set this JScrollPane as the basecontainer. Now create a new JPanel object with the BoxLayout set as having Y_AXIS orientation. Now set this newly created JPanel object as the viewport of the JScrollPane container. So, by here, we had created a scrolling container.
Next step is what you mentioned in your code sample, ie adding 'post' elements. For that just as you did, inside the for loop create each JPanel objects. But instead of using setSize() method, you should use setPreferredSize(new Dimension(w,h)) method. This forces your components to render according to the dimension specified. For inserting vertical gaps between each post objects, you can use java.awt.Box class's createVerticalStrut(int height) method. I am giving the code below. Feel free to ask if you have any doubt. :)
import java.awt.*;
import javax.swing.*;
public class LinkedInLayout extends JFrame{
public LinkedInLayout(){
setTitle("Linked In Layout Resolution");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(0,0,500,520);
JPanel postPane = new JPanel(new FlowLayout(FlowLayout.LEADING));
JScrollPane container = new JScrollPane(postPane,ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
setContentPane(container);
postPane.setLayout(new BoxLayout(postPane,BoxLayout.Y_AXIS));
for(int y=0;y<15;y++){
postPane.add(Box.createVerticalStrut(35));
JPanel p=new JPanel();
p.setBackground(new Color(247,221,221));
p.setLayout(null);
p.setPreferredSize(new Dimension(postPane.getWidth(),75));
postPane.add(p);
}
setVisible(true);
}
public static void main(String[] args) {
new LinkedInLayout();
}
}

Stopping JButton highlighting on press

Any JButton that is pressed will seem to "highlight" itself when pressed like so:
I can't seem to find any way of disabling this.
There are a number of ways you might achieve this...
You Could...
Override paintComponent and implement you own paint logic. This is kind of dangerous and now means that for each state change you want to modify will either require a new JButton based class or some other serious of flags to implement. It's also possible that this could effect other look and feels...
You Could...
Create your own ButtonUI, which would normally be the preferred way, but it's not an insignificant amount of work and you'd need one for each platform you wanted to support
You Could...
Use the icon property of the button to "simulate" the button boundaries. This is preferred solution (over customising the the painting process) as it's easy to apply and doesn't require a specialised button to achieve. It also overcomes some of the issues of how buttons are painted across different platforms (as not all buttons use the background color property the same)
You Could...
Define your own ButtonModel which could ignore certain state changes (like pressed or rollover).
This is a preferred solution as it works with the current look and feel to achieve your results.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.GridLayout;
import javax.swing.DefaultButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class TestButton {
public static void main(String[] args) {
new TestButton();
}
public TestButton() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
public TestPane() {
JButton normal = createButton("Normal", Color.RED);
JButton fixed = createButton("Fixed", Color.BLUE);
fixed.setModel(new FixedStateButtonModel());
setLayout(new GridLayout(1, 0));
add(normal);
add(fixed);
}
protected JButton createButton(String text, Color background) {
JButton btn = new JButton(text);
btn.setFocusPainted(false);
btn.setBackground(background);
btn.setForeground(Color.WHITE);
return btn;
}
}
public class FixedStateButtonModel extends DefaultButtonModel {
#Override
public boolean isPressed() {
return false;
}
#Override
public boolean isRollover() {
return false;
}
#Override
public void setRollover(boolean b) {
//NOOP
}
}
}
You can extend JButton class and design your own appearance or just override the default bahaviour as in this exemplary code:
public class MyButton extends JButton {
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (isSelected()) {
setBorder(BorderFactory.createEmptyBorder());
} else {
setBorder(BorderFactory.createLoweredBevelBorder());
}
}
}
I always do it by calling setFocusPainted(boolean b):
btn.setFocusPainted(false);
Painting the background when pressed is a UI implementation, so you would need to change the UI.
A simpler approach would be to create Icons of a specific color to add to your buttons. Something like:
public class ColorIcon implements Icon
{
private Color color;
private int width;
private int height;
public ColorIcon(Color color, int width, int height)
{
this.color = color;
this.width = width;
this.height = height;
}
public int getIconWidth()
{
return width;
}
public int getIconHeight()
{
return height;
}
public void paintIcon(Component c, Graphics g, int x, int y)
{
g.setColor(color);
g.fillRect(x, y, width, height);
}
}
Then you can display the text on top of the Icon by using:
JButton button = new JButton("1");
button.setIcon( new ColorIcon(Color.RED, 32, 32) );
button.setHorizontalTextPosition(JButton.CENTER);
button.setVerticalTextPosition(JButton.CENTER);
button.setMargin( new Insets(0, 0, 0, 0) );

Java: Using graphics component within an ActionListener

I have two separate class and driver files, and in the class file I create the paint method:
public void paint(Graphics g){
g.drawLine(......
....
//along with all of my other draw commands
}
Further down in the code, I create a JButton and within this button's action listener I don't know how to use a Graphics object to create more graphics in the JFrame. Should I be adding something to my driver to make this happen, or is there a way to use these graphics within my action listener? Thank you, and any help is appreciated.
You need to draw everything within the paint method. The actionPerformed should only change the state of something already in the paint method, and then call repaint. For example
boolean drawHello = true;
boolean drawWorld = false;
protected void paintComponent(Graphics g) {
super.paintCompoent(g);
if (drawHello)
g.drawString("Hello", 50, 50);
if (drawWorld)
g.drawString("World", 10, 10);
}
Then in your actionPerformed, you can change the state of drawWorld to true and call repaint().
public void actionPerformed(ActionEvent e) {
drawWorld = true;
repaint();
}
So as you can see, everything should be drawn in the paintComponent method. You can just hide and paint renderings, and make them "visible" from a action command. You should already have predefined what could posibly be drawn. Then just change the state of it rendering
And as #MadPrgrammer pointed out, you should not be painting on top-level containers like JFrame. Instead paint on a custom JPanel or JComponent and override the paintComponent method, instead of JFrame and paint
Here's an example where I draw a new square every time the button is pressed. If look at the code, you will see that in the paintComponent method, I loop through a list of Squares and draw them, and in the actionPerformed all I do is add a new Square to the List and call repaint()
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
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.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class AddSquares {
private int R = 0;
private int G = 0;
private int B = 0;
private int xLoc = 0;
private int yLoc = 0;
List<Square> squares = new ArrayList<>();
private JButton addSquare = new JButton("Add Square");
private RectsPanel panel = new RectsPanel();
public AddSquares() {
addSquare.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
Color color = new Color(R, G, B);
squares.add(new Square(xLoc, yLoc, color));
panel.repaint();
R += 10;
G += 20;
B += 30;
xLoc += 20;
yLoc += 20;
}
});
JFrame frame = new JFrame("Draw Squares");
frame.add(panel, BorderLayout.CENTER);
frame.add(addSquare, BorderLayout.SOUTH);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
private class RectsPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Square square : squares) {
square.drawSquare(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(250, 250);
}
}
private class Square {
int x = 0;
int y = 0;
Color color;
public Square(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void drawSquare(Graphics g) {
g.setColor(color);
g.fillRect(x, y, 75 ,75);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
AddSquares addSquares = new AddSquares();
}
});
}
}
It's difficult to be 100%, but it would seem as you don't understand how custom painting is performed in Swing.
Start by taking a look at Performing Custom Painting and Painting in AWT and Swing.
Essentially, painting is arranged by the Repaint Manager, which decides what and when something should be painted. It then calls (through a chain of methods) the paint method of the components it thinks need to be updated, passing it a reference to a Graphics context that should be used to actually paint on.
Basically, when ever your paint method is called, you should create paint the current state of your painting.
You should avoid overriding paint and instead use paintComponent from classes the extend JComponent
Your question is a little on the vague side as to what you are actually wondering about but generally speaking:
We don't override paint in Swing, we override paintComponent.
If you are already aware of this, you may be overriding paint because you are doing it on a JFrame and you found that JFrame does not have a paintComponent method. You shouldn't override paint on a JFrame. Instead, create a JPanel or something to put inside the frame and override paintComponent on the panel.
Question about the ActionListener.
It sounds like you are wanting to do painting outside of paintComponent in which case probably the best way is to do painting to a separate Image. Then you paint the Image on to the panel in paintComponent. You can also put an Image in a JLabel as an ImageIcon. Here is a very simple drawing program using MouseListener that demonstrates this (taken from here):
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
class PaintAnyTime {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
final BufferedImage image = (
new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB)
);
final JFrame frame = new JFrame();
final JLabel label = new JLabel(new ImageIcon(image));
final MouseAdapter drawer = new MouseAdapter() {
Graphics2D g2D;
#Override
public void mousePressed(MouseEvent me) {
g2D = image.createGraphics();
g2D.setColor(Color.BLACK);
}
#Override
public void mouseDragged(MouseEvent me) {
g2D.fillRect(me.getX(), me.getY(), 3, 3);
label.repaint();
}
#Override
public void mouseReleased(MouseEvent me) {
g2D.dispose();
g2D = null;
}
};
PaintAnyTime() {
label.setPreferredSize(
new Dimension(image.getWidth(), image.getHeight())
);
label.addMouseListener(drawer);
label.addMouseMotionListener(drawer);
frame.add(label);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
#MadProgrammer has already linked to the articles that I was going to link to.

JButton subclass with custom shape shifts during repainting

I have just started working with Swing and am trying to draw a button with a custom shape, a triangle in this example. I called the JButton subclass 'ShiftingButton' in the following code because of its unusual behavior. When the mouse enters its region, it is repainted with an offset from its original position. Furthermore the shifted, offset version is drawn in addition to the original position so that both the original and shifted versions appear together. That is, when I run this code, the button is shown as a triangle along the left edge of the window. Then when I run the mouse over the button, a new triangle is drawn (in addition to the old one), shifted down and to the right by about 10 pixels. Resizing the window changes the offset of the phantom button from the original.
Experimenting with mouse clicks shows that only the original, correctly-positioned button is active. The region of the offset phantom button is not active.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Polygon;
public class ShiftingButton extends JButton implements ActionListener {
private Polygon shape;
public ShiftingButton () {
initialize();
addActionListener(this);
}
protected void initialize() {
shape = new Polygon();
setSize(120, 120);
shape.addPoint(0, 0);
shape.addPoint(0, 60);
shape.addPoint(90, 0);
setMinimumSize(getSize());
setMaximumSize(getSize());
setPreferredSize(getSize());
}
// Hit detection
public boolean contains(int x, int y) {
return shape.contains(x, y);
}
#Override
public void paintComponent (Graphics g) {
System.err.println("paintComponent()");
g.fillPolygon(shape);
}
protected void paintBorder(Graphics g) {
}
#Override
public void actionPerformed (ActionEvent ev) {
System.out.println("ShiftingButton ActionEvent!");
}
public static void main (String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
ShiftingButton button = new ShiftingButton();
panel.add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
You failed to call super.paintComponent(g) inside the overridden paintComponent(...) method. Moreover, while overriding a method of the Base class, always try to keep the access specifier of the methods, the same, as much as possible. In this case it's protected and not public :-) Now function should be like this :
#Override
protected void paintComponent (Graphics g) {
System.err.println("paintComponent()");
super.paintComponent(g);
g.fillPolygon(shape);
}
EDIT 1 :
Moreover, since you are using a custom shape to be drawn, hence you again failed to specify the ContentAreaFilled property for this JButton in question, hence inside your constructor, you should write setContentAreaFilled(false), for it to work nicely. Though if this doesn't works (for reasons specified in the Docs), then you have to use the plain old Opaque property and set it to false for this JButton using setOpaque(false) :-)
Here is your code with modified changes :
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.awt.Polygon;
public class ShiftingButton extends JButton implements ActionListener {
private Polygon shape;
public ShiftingButton () {
setContentAreaFilled(false);
initialize();
addActionListener(this);
}
protected void initialize() {
shape = new Polygon();
setSize(120, 120);
shape.addPoint(0, 0);
shape.addPoint(0, 60);
shape.addPoint(90, 0);
}
#Override
public Dimension getPreferredSize() {
return (new Dimension(120, 120));
}
// Hit detection
public boolean contains(int x, int y) {
return shape.contains(x, y);
}
#Override
protected void paintComponent(Graphics g) {
System.err.println("paintComponent()");
super.paintComponent(g);
g.fillPolygon(shape);
}
protected void paintBorder(Graphics g) {
}
#Override
public void actionPerformed (ActionEvent ev) {
System.out.println("ShiftingButton ActionEvent!");
}
public static void main (String[] args) {
JFrame frame = new JFrame();
JPanel panel = new JPanel();
ShiftingButton button = new ShiftingButton();
panel.add(button);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}

Categories