Normally, I would just create the JLabel using a string as the first parameter and JLabel.CENTER as the second parameter; adding the label to the panel using BorderLayout.CENTER would then cause the text in the label to be aligned in the center of the panel.
However, I'm using the 'RichJLabel' class in order to get a drop shadow on my text. To do this, it overrides Component.paintComponent in such a way that the alignment information is lost, and the label's text is drawn in the top left of the panel no matter what I do.
From what I understand, the workaround for this is to encase the label inside of another panel; that way, I can align the panel itself inside the parent panel, but I'm uncertain of how exactly to do this.
My complete goal is to:
Figure out what font size a given string needs in order to fill up its parent JPanel
Add a drop shadow to that text
Center the text within its JPanel
Here's what I've got so far:
import java.awt.*;
import javax.swing.*;
import javax.swing.border.BevelBorder;
public class RichJLabel extends JLabel {
private int tracking;
public RichJLabel(String text, int tracking) {
super(text, JLabel.CENTER);
this.tracking = tracking;
}
private int left_x, left_y, right_x, right_y;
private Color left_color, right_color;
public void setLeftShadow(int x, int y, Color color) {
left_x = x;
left_y = y;
left_color = color;
}
public void setRightShadow(int x, int y, Color color) {
right_x = x;
right_y = y;
right_color = color;
}
public Dimension getPreferredSize() {
String text = getText();
FontMetrics fm = this.getFontMetrics(getFont());
int w = fm.stringWidth(text);
w += (text.length()-1)*tracking;
w += left_x + right_x;
int h = fm.getHeight();
h += left_y + right_y;
return new Dimension(w,h);
}
public void paintComponent(Graphics g) {
((Graphics2D)g).setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
char[] chars = getText().toCharArray();
FontMetrics fm = this.getFontMetrics(getFont());
int h = fm.getAscent();
int x = 0;
for(int i=0; i<chars.length; i++) {
char ch = chars[i];
int w = fm.charWidth(ch) + tracking;
g.setColor(left_color);
g.drawString(""+chars[i],x-left_x,h-left_y);
g.setColor(right_color);
g.drawString(""+chars[i],x+right_x,h+right_y);
g.setColor(getForeground());
g.drawString(""+chars[i],x,h);
x+=w;
}
((Graphics2D)g).setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
} // end paintComponent()
public static void main(String[] args) {
JPanel panel1 = new JPanel( new BorderLayout() );
panel1.setBackground( Color.BLUE );
panel1.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED ));
JPanel interiorPanel = new JPanel( new BorderLayout() );
panel1.add(interiorPanel, BorderLayout.CENTER);
RichJLabel label = new RichJLabel("100", 0);
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
label.setVisible( true );
label.setForeground( Color.YELLOW );
interiorPanel.add(label, BorderLayout.CENTER);
label.setFont(new Font("Arial", Font.BOLD, 140));
label.setFont(label.getFont().deriveFont(140f));
//resize code
Font labelFont = label.getFont();
String labelText = label.getText();
int stringWidth = label.getFontMetrics(labelFont).stringWidth(labelText);
int componentWidth = interiorPanel.getWidth();
// Find out how much the font can grow in width.
double widthRatio = (double)componentWidth / (double)stringWidth;
int newFontSize = (int)(labelFont.getSize() * widthRatio);
int componentHeight = interiorPanel.getHeight();
// Pick a new font size so it will not be larger than the height of label.
int fontSizeToUse = Math.min(newFontSize, componentHeight);
// Set the label's font size to the newly determined size.
label.setFont(new Font(labelFont.getName(), Font.BOLD, fontSizeToUse));
label.setLeftShadow(-3,-3,Color.BLACK);
// drop shadow w/ highlight
label.setRightShadow(2,3,Color.black);
label.setForeground(Color.gray);
JFrame frame = new JFrame("Label SSCCEE");
frame.getContentPane().add(panel1);
frame.pack();
frame.setVisible(true);
}
}
What it does right now is this:
The code was checking for the size of the container too soon. Before it is displayed, it has a width/height of 0.
Altered code
import java.awt.*;
import javax.swing.*;
import javax.swing.border.BevelBorder;
public class RichJLabel extends JLabel {
private int tracking;
public RichJLabel(String text, int tracking) {
super(text, JLabel.CENTER);
this.tracking = tracking;
}
private int left_x, left_y, right_x, right_y;
private Color left_color, right_color;
public void setLeftShadow(int x, int y, Color color) {
left_x = x;
left_y = y;
left_color = color;
}
public void setRightShadow(int x, int y, Color color) {
right_x = x;
right_y = y;
right_color = color;
}
public Dimension getPreferredSize() {
String text = getText();
FontMetrics fm = this.getFontMetrics(getFont());
int w = fm.stringWidth(text);
w += (text.length()-1)*tracking;
w += left_x + right_x;
int h = fm.getHeight();
h += left_y + right_y;
return new Dimension(w,h);
}
public void paintComponent(Graphics g) {
((Graphics2D)g).setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
char[] chars = getText().toCharArray();
FontMetrics fm = this.getFontMetrics(getFont());
int h = fm.getAscent();
int x = 0;
for(int i=0; i<chars.length; i++) {
char ch = chars[i];
int w = fm.charWidth(ch) + tracking;
g.setColor(left_color);
g.drawString(""+chars[i],x-left_x,h-left_y);
g.setColor(right_color);
g.drawString(""+chars[i],x+right_x,h+right_y);
g.setColor(getForeground());
g.drawString(""+chars[i],x,h);
x+=w;
}
((Graphics2D)g).setRenderingHint(
RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_DEFAULT);
} // end paintComponent()
public static void main(String[] args) {
JPanel panel1 = new JPanel( new BorderLayout() );
panel1.setBackground( Color.BLUE );
panel1.setBorder( BorderFactory.createBevelBorder( BevelBorder.LOWERED ));
JPanel interiorPanel = new JPanel( new BorderLayout() );
panel1.add(interiorPanel, BorderLayout.CENTER);
RichJLabel label = new RichJLabel("100", 0);
label.setLeftShadow(-3,-3,Color.BLACK);
// drop shadow w/ highlight
label.setRightShadow(2,3,Color.black);
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
label.setVisible( true );
label.setForeground( Color.YELLOW );
interiorPanel.add(label, BorderLayout.CENTER);
label.setFont(new Font("Arial", Font.BOLD, 140));
label.setFont(label.getFont().deriveFont(140f));
//resize code
Font labelFont = label.getFont();
JFrame frame = new JFrame("Label SSCCEE");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.setContentPane(panel1);
frame.pack();
frame.setVisible(true);
int componentWidth = interiorPanel.getWidth();
String labelText = label.getText();
int stringWidth = label.getFontMetrics(labelFont).stringWidth(labelText);
// Find out how much the font can grow in width.
double widthRatio = (double)componentWidth / (double)stringWidth;
int newFontSize = (int)(labelFont.getSize() * widthRatio);
int componentHeight = interiorPanel.getHeight();
// Pick a new font size so it will not be larger than the height of label.
int fontSizeToUse = Math.min(newFontSize, componentHeight);
System.out.println("fontSizeToUse: " + fontSizeToUse);
if (fontSizeToUse<1) {
System.err.println("Font size less than 1!");
System.exit(1);
}
// Set the label's font size to the newly determined size.
label.setFont(new Font(labelFont.getName(), Font.BOLD, fontSizeToUse));
label.setForeground(Color.gray);
}
}
If it is this RichJLabel, then take a look at paintComponent. I would sugges making your own shadowed label, maybe consisting out of two shadow labels and one foreground label. I did not expect to see a 3 times drawing of every character. Problematic: inefficient and cannot do scripting languages like arabic or combining diacritic characters (one char letter, another accent).
At some points hacking around isn't worth it. If you had seen the sources, who knows.
I am not sure that I have the right code. It certainly might be of a very early version.
RichJLabel has a getPreferredSize() suitable for layouting. JPanel.setBounds(int, int, int, int) is the single method by which resizing is done. Hence you can use absolute layouting in a JPanel (null layout).
public class EPanel2 extends JPanel {
private RichJLabel label = new RichJLabel("", 0);
public EPanel2(String text) {
super(null);
setBackground(Color.GREEN.brighter().brighter());
label.setFont(new Font("Univers", Font.BOLD, 48));
label.setText(text);
label.setHorizontalAlignment(JLabel.CENTER);
label.setVerticalAlignment(JLabel.CENTER);
label.setForeground(Color.BLUE);
label.setLeftShadow(2, 2, Color.RED);
label.setRightShadow(2, 2, Color.ORANGE);
label.setVisible(true);
add(label);
}
#Override
public void setBounds(int x, int y, int width, int height) {
super.setBounds(x, y, width, height);
Dimension labelSize = label.getPreferredSize();
int labelX = (width - labelSize.width) / 2;
int labelY = (height - labelSize.height) / 2;
label.setBounds(labelX, labelY, labelSize.width, labelSize.height);
}
}
Related
I'm using JFreeChart and I want to customise the ToolTip by creating my own Class which extends ChartPanel and override createToolTip().
static private class PrivateChartPanel extends ChartPanel{
//constructors
#Override
public JToolTip createToolTip() {
JToolTip jtt = super.createToolTip();
jtt.setBackground(Color.WHITE);
jtt.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1, true));
return jtt;
}
}
The problem is at Border. It is not rounded on all corners.
Why it is not rounded on all corners and how I could done it?
P.S.: I created a new simple project
import java.awt.Color;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class HelloWorld {
public static void main(String[] args) {
JFrame a = new JFrame();
a.setBounds(100, 100, 100, 100);
a.setLayout(null);
JPanel b = new JPanel();
b.setBounds(5, 5, 50, 50);
b.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1, true));
a.add(b);
a.setVisible(true);
}
}
and Border of JPanel is with same problem. I'm using Java 10
The effect of rounded corners depends on the size of these rounded corners. In case of LineBorder, it is determined by the thickness property. This is how the relevant implementation code looks like:
int offs = this.thickness;
int size = offs + offs;
if (this.roundedCorners) {
float arc = .2f * offs;
outer = new RoundRectangle2D.Float(x, y, width, height, offs, offs);
inner = new RoundRectangle2D.Float(x + offs, y + offs, width - size, height - size, arc, arc);
}
else {
outer = new Rectangle2D.Float(x, y, width, height);
inner = new Rectangle2D.Float(x + offs, y + offs, width - size, height - size);
}
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(outer, false);
path.append(inner, false);
g2d.fill(path);
So it differentiates between inner and outer corner, which is not much meaningful for a line size of one. But even worse, the outer corner size is just offs, which is identical to thickness (one in your case) and the size of the inner rounded corner is determined by arc, which is .2f * offs. For your thickness of one, the resulting inner corner size is 0.2. So it seems to be a pure coincidence (rounding issue of these two different corners) that we see an effect in the upper left corner, as even the bigger outer corner size of one is not enough to create a visible rounded effect.
Here is how it looks like with a thickness of 20, which results in an outer corner size of 20 and a whopping inner corner size of 4:
It don’t know which actual use case the Swing developers had in mind when they added the rounded corner support in this class. I can’t imagine any scenario where this strategy is useful.
Implementing a meaningful Border is not that hard. One possible implementation looks like:
public class RoundedLineBorder extends AbstractBorder {
int lineSize, cornerSize;
Paint fill;
Stroke stroke;
private Object aaHint;
public RoundedLineBorder(Paint fill, int lineSize, int cornerSize) {
this.fill = fill;
this.lineSize = lineSize;
this.cornerSize = cornerSize;
stroke = new BasicStroke(lineSize);
}
public RoundedLineBorder(Paint fill, int lineSize, int cornerSize, boolean antiAlias) {
this.fill = fill;
this.lineSize = lineSize;
this.cornerSize = cornerSize;
stroke = new BasicStroke(lineSize);
aaHint = antiAlias? RenderingHints.VALUE_ANTIALIAS_ON: RenderingHints.VALUE_ANTIALIAS_OFF;
}
#Override
public Insets getBorderInsets(Component c, Insets insets) {
int size = Math.max(lineSize, cornerSize);
if(insets == null) insets = new Insets(size, size, size, size);
else insets.left = insets.top = insets.right = insets.bottom = size;
return insets;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
Graphics2D g2d = (Graphics2D)g;
Paint oldPaint = g2d.getPaint();
Stroke oldStroke = g2d.getStroke();
Object oldAA = g2d.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
try {
g2d.setPaint(fill!=null? fill: c.getForeground());
g2d.setStroke(stroke);
if(aaHint != null) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, aaHint);
int off = lineSize >> 1;
g2d.drawRoundRect(x+off, y+off, width-lineSize, height-lineSize, cornerSize, cornerSize);
}
finally {
g2d.setPaint(oldPaint);
g2d.setStroke(oldStroke);
if(aaHint != null) g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, oldAA);
}
}
}
Now, when I change the line
b.setBorder(BorderFactory.createLineBorder(Color.BLACK, 1, true));
in your example to
b.setBorder(new RoundedLineBorder(Color.BLACK, 1, 10, true));
I get
I'm trying to make a level editor for my platformer game, I want my levels to be 100 by 100 squares.
So far the editor works, but I can't scroll through the JPanel. I've been playing around and I've made a small test class to fiddle with which I'll post. If you run it, all it does it show the grid. However if I swap out two variables (I'll comment where) it can show an image and scroll according to the size of that image.
I want that scrolling ability only for the JPanel, so that I can scroll through my 100 x 100 square level.
import java.awt.BorderLayout;
public class ScrollPaneJ extends JFrame {
// setting the panels
private JPanel contentPane;
private JScrollPane scrollPane;
// dimensions/ variables of the grid
int size = 16;
int startX = 112;
int startY = 48;
int width = 30;
int height = 30;
// this is the grid
String[][] grid = new String[width][height];
// this is from the full editor class
String currentImage = new String("platform");
ImageIcon currentBackIcon = new ImageIcon("Resources/backdirttile.jpg");
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
// adding the scrollpane
ScrollPaneJ frame = new ScrollPaneJ();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the frame.
*/
public ScrollPaneJ() {
setTitle("Scrolling Pane Application");
setSize(new Dimension(300, 200));
setBackground(Color.gray);
setDefaultCloseOperation(EXIT_ON_CLOSE);
// defining the top and bottom panels, bottom is what I think I'm
// drawing on, top is where the scrollpanel goes, I copied this code
// from the internet and I'm not too sure how it works
JPanel topPanel = new JPanel();
JPanel bottomPanel = new JPanel(new GridLayout());
bottomPanel.setLayout(new BorderLayout());
getContentPane().add(bottomPanel);
topPanel.setLayout(new BorderLayout());
getContentPane().add(topPanel);
// this is the label I was talking about
Icon image = new ImageIcon("src/MenuDesign.jpg");
JLabel label = new JLabel(image);
// Create a tabbed pane
// if you set it to say label instead of bottomPanel, you can scroll
// through the size of the label
scrollPane = new JScrollPane(bottomPanel);
scrollPane.setBounds(40, 40, 100, 100);
// set it label here as well.
scrollPane.getViewport().add(bottomPanel);
// I was hoping this would force the scrollbar in but it does nothing
scrollPane
.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
scrollPane
.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
scrollPane.setBounds(50, 30, 300, 50);
JPanel contentPane = new JPanel(null);
contentPane.setPreferredSize(new Dimension(500, 400));
contentPane.add(scrollPane);
topPanel.add(scrollPane, BorderLayout.CENTER);
init();
}
public void init() {
// this sets the grid to empty
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
grid[x][y] = "";
}
}
}
#Override
public void paint(Graphics g) {
// this paints the grid
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
g2d.drawRect(x * size + startX, y * size + startY, size, size);
if (grid[x][y].equals("")) {
g2d.drawImage(currentBackIcon.getImage(),
x * size + startX, y * size + startY, null);
}
g2d.setColor(Color.black);
g2d.drawRect((x * size) + 1 + startX, (y * size) + 1 + startY,
size, size);
}
}
}
public void drawTile() {
// this isn't enabled which is why you can't paint the grid, however it
// would change the tile of the square you're mouse is on, to the
// current tile, it works and isn't really important for what i need
// help with
PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
int mouseX = (int) b.getX();
int mouseY = (int) b.getY();
int gMX = ((mouseX - 48) / 16) - 4;
int gMY = ((mouseY - 48) / 16) - 3;
grid[gMX][gMY] = currentImage;
repaint();
}
}
scrollPane.getViewport().add(bottomPanel); should be more like scrollPane.getViewportView(bottomPanel);
You shouldn't be painting directly to the frame, as child components can be painted without the notification to the parents, meaning that what ever you've painted could be partially wiped out. Instead, this kind of painting should be done within a custom component which acts as the JScrollPane's, JViewport's view.
A JScrollPane needs two things, first, the size that the component would like to be (the preferredSize) and the size of the viewport view. If the component doesn't implement the Scrollable interface, then the component's preferredSize is used to determine that as well. This is why a JLabel will work.
A JScrollPane has a JViewport as it's primary child component. The JViewport should only have a single component, typically assigned either via JScrollPane#setViewportView or JViewport#setView methods
See How to Use Scroll Panes for more details
Create a custom component that extends JPanel and override it's getPreferredSize method to return the size of the component you want. Override it's paintComponent method and perform you custom painting their.
Overlaying custom painting ontop of other components is more difficult
You can also add JScrollPane in your panel like this
JPanel p = new JPanel();
add(new JScrollPane(p));
In short, I want to set the text of a JLabel to be that of a JTextField in a JPanel (pnlUser) and then drag the JLabel across the screen from JPanel onto another JTextField in another JPanel (pnlGrid).
Here are the details.
I have written a "Solitaire Scrabble" program. The user can either position the text cursor in a grid cell (a JTextField in pnlGrid) and type a letter that is in the list of "User letters" (a JTextField in pnlUser) OR the user can simulate dragging a letter from "User letters" and dropping it into the destination grid cell in pnlGrid.
I say "simulate" because the selected letter is not actually dragged across the screen. I use the mouse pointer HAND_CURSOR to make the drag/drop as real as possible, but I haven't figured out how to make the HAND_CURSOR "grab" the letter and physically drag the letter across the board to its destination.
As it is, the letter is highlighted but left in the "User letters" area while the HAND_CURSOR moves along the grid during the drag operation. When it gets to the destination cell in pnlGrid and the mouse button is released, the letter is erased from "User letters" and suddenly appears in the grid cell.
So the letter is more or less "teleported" (beam me up, Scotty) from "User letters" to a grid cell. This is too abstract. I want the user letter to be at the tip of the HAND_CURSOR's pointing finger and be dragged along the grid into the grid cell where it will be dropped, as shown in the 3 pictures below.
I've successfully made it happen in a small test program (source below) using JLayeredPane, but I can't make it happen in the game. But I knew nothing about JLayeredPane until two days ago so I don't really know what I'm doing. (I adapted an Oracle tutorial program that demos JLayeredPane.)
I just read about the "glass pane" and thought it would maybe be easier to implement until I downloaded the source for that demo, which is quite long, so since it's totally new and will be even harder to adapt.
So I thought before I spend more hours in frustration I should ask:
Is a JLayeredPane or a setGlassPane approach appropriate? Is there an easier or better way to drag a JLabel from one JPanel onto another another JPanel?
(The approach in the program is to determine which "User letter" is being pointed at, store that letter in a JLabel, and then make sure that during mouseDragged the HAND_CURSOR fingertip is right at the bottom center of the letter.)
package mousemoveletter;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import static java.awt.Color.*;
import java.awt.event.*;
import static javax.swing.SwingUtilities.invokeLater;
public class LayeredPaneDemo extends JPanel
{
private static final int USER7 = 7;
static Cursor HAND = new Cursor(Cursor.HAND_CURSOR);
static Cursor ARROW = new Cursor(Cursor.DEFAULT_CURSOR);
private static JLayeredPane layeredPane;
private static JLabel lblToMove;
private static JPanel pnlUser;
private static JPanel pnlGrid;
private static final JTextField[] txtUser = new JTextField[USER7];
public LayeredPaneDemo() // constructor
{
pnlGrid = new JPanel();
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize(new Dimension(240, 240));
pnlGrid.setSize(140, 140);
pnlGrid.setBorder(new EtchedBorder(RED, GREEN));
pnlGrid.setBackground(YELLOW);
lblToMove = new JLabel("XXX");
lblToMove.setSize(new Dimension(40,40));
layeredPane.add(pnlGrid, 0,0);
layeredPane.add(lblToMove, new Integer(0), -1);
add(layeredPane);
}
private static void createAndShowGUI() {
JFrame frame = new JFrame("LayeredPaneDemo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JComponent newContentPane = new LayeredPaneDemo();
newContentPane.setOpaque(true); //content panes must be opaque
frame.setContentPane(newContentPane);
makeUser();
frame.add(pnlUser);
frame.pack();
frame.setVisible(true);
}
public static void main(String[] args) {
invokeLater(new Runnable()
{
public void run() {
createAndShowGUI();
}
});
}
private static void makeUser(){
pnlUser = new JPanel(new GridLayout(1,USER7));
pnlUser.setPreferredSize(new Dimension(225, 50));
pnlUser.setBackground(Color.green);
pnlUser.setBorder(BorderFactory.createLineBorder(Color.BLUE));
for(int k = 0; k < USER7; k++)
{
txtUser[k] = new JTextField("" + (char)(Math.random()*26+65));
txtUser[k].setName("" + k);
txtUser[k].setEditable(false);
txtUser[k].addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent e)
{
lblToMove.setCursor(HAND);
int w = Integer.parseInt(e.getComponent().getName());
lblToMove.setText(txtUser[w].getText());
layeredPane.setLayer(lblToMove, 0, 0);
lblToMove.setLocation(e.getX() + (e.getComponent().getWidth())*w,
e.getY() + layeredPane.getHeight() - e.getComponent().getHeight()/2);
};
});
txtUser[k].addMouseListener(new MouseAdapter()
{
public void mouseReleased(MouseEvent e)
{
lblToMove.setCursor(ARROW);
}
});
pnlUser.add(txtUser[k]);
}
}
}
Thanks to #trashgod, I figured it out by following his links to this example and variation; I adapted the drag/drop of the chessboard found there to my own particular needs for "Scrabble".
The code below is not final code for my Solitaire Scrabble program, but proof-of-concept, possibly usable by others wishing to drag a cell from a 1xN grid onto a MxM grid.
package components;
import java.awt.*;
import static java.awt.BorderLayout.NORTH;
import static java.awt.BorderLayout.SOUTH;
import java.awt.event.*;
import static java.lang.Integer.parseInt;
import javax.swing.*;
import static javax.swing.WindowConstants.DISPOSE_ON_CLOSE;
import javax.swing.event.MouseInputAdapter;
public class ChessBoard //implements MouseListener, MouseMotionListener
{
static Point parentLocation;
int homeRow, homeCol; // where to restore moved user letter if dropped on occupied cell
static int N = 11; // NxN 'chessboard' squares
static int S = 44; // square dimensions: SxS
static int W ; // chessboard dimensions: WxW
static int USER7 = 7;
static Font dragFont;
static JFrame frame;
JLayeredPane layeredPane;
static JPanel gamePanel, // encompasses both pnlGrid and pnlUser
pnlGrid,
pnlUser;
JLabel userDragLetter = new JLabel(); // main item to drag around or restore if needed
int xAdjustment, yAdjustment; // how to locate drops accurately
String userLetters[] ;
public ChessBoard() // constructor
{
W = S*N;
dragFont = new Font("Courier", Font.PLAIN, S);
userLetters = new String[USER7];
for (int i = 0; i < USER7; i++)
userLetters[i] = "" + (char)(65 + Math.random()*26);
Dimension gridSize = new Dimension(W, W);
Dimension userSize = new Dimension(W, S);
Dimension gameSize = new Dimension(W, (W + S));
frame = new JFrame();
frame.setSize(new Dimension(gameSize)); // DO NOT USE PREFERRED
layeredPane = new JLayeredPane();
layeredPane.setPreferredSize( gameSize ); // NO PREFERRED => NO GRID!
gamePanel = new JPanel();
// **EDIT** LOSE THIS LINE gamePanel.setLayout(new BorderLayout());
gamePanel.setPreferredSize(gameSize);
pnlGrid = new JPanel();
pnlGrid.setLayout(new GridLayout(N, N));
pnlGrid.setPreferredSize( gridSize );
pnlGrid.setBounds(0, 0, gridSize.width, gridSize.height);
pnlUser = new JPanel();
pnlUser.setLayout(new GridLayout(1, N));
pnlUser.setPreferredSize(userSize);
pnlUser.setBounds(0, gridSize.height, userSize.width, userSize.height);
layeredPane.add(pnlGrid, JLayeredPane.DEFAULT_LAYER); // panels to drag over
layeredPane.add(pnlUser, JLayeredPane.DEFAULT_LAYER); // " "
for (int i = 0; i < N; i++){
for (int j = 0; j < N; j++){
JPanel square = new JPanel();
square.setBackground( (i + j) % 2 == 0 ? Color.red : Color.white );
pnlGrid.add( square );
}
}
for (int i = 0; i < N; i++) {
JPanel square = new JPanel(new BorderLayout());
square.setBackground(Color.YELLOW);
pnlUser.add(square);
}
for (int i = 0; i < USER7; i++)
addPiece(i, 0, userLetters[i]);
gamePanel.addMouseListener(new MouseInputAdapter()
{
public void mousePressed (MouseEvent e){mousePressedActionPerformed (e);}
public void mouseReleased(MouseEvent e){mouseReleasedActionPerformed(e);}
});
gamePanel.addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseDragged(MouseEvent me){mouseDraggedActionPerformed(me);}
});
// **EDIT: LOSE THE NEXT TWO LINES AND REPLACE BY THE LINE AFTER THEM**
// gamePanel.add(layeredPane, NORTH);
// gamePanel.add(pnlUser, SOUTH);
gamePanel.add(layeredPane);
}
private void addPiece(int col, int row, String glyph) {
JLabel piece = new JLabel(glyph, JLabel.CENTER);
piece.setFont(dragFont);
JPanel panel = (JPanel) pnlUser.getComponent(col + row * N);
piece.setName("piece " + glyph + " # " + row + " " + col);
panel.add(piece);
}
void mousePressedActionPerformed(MouseEvent e)
{
userDragLetter = null; // signal that we're not dragging if no piece is in the square
gamePanel.setCursor(new Cursor(Cursor.HAND_CURSOR));
Component c = pnlGrid.findComponentAt(e.getX(), e.getY());
if(c != null)
return; // Illegal to click pnlGrid
c = pnlUser.findComponentAt(e.getX(), e.getY() - pnlGrid.getHeight());
if(c == null | c instanceof JPanel)
return; // letter already played; can't drag empty cell
parentLocation = c.getParent().getLocation();
xAdjustment = parentLocation.x - e.getX();
yAdjustment = parentLocation.y - e.getY() + gamePanel.getHeight() - pnlUser.getHeight();
userDragLetter = (JLabel)c;
userDragLetter.setPreferredSize(new Dimension(S, S)); // prevent 2 letters in a square
userDragLetter.setLocation(e.getX() + xAdjustment, e.getY() + yAdjustment);
layeredPane.add(userDragLetter, JLayeredPane.DRAG_LAYER);
homeRow = parseInt(userDragLetter.getName().substring(10,11)); // save restore location
homeCol = parseInt(userDragLetter.getName().substring(12,13));
}
void mouseDraggedActionPerformed(MouseEvent me)
{
if (userDragLetter == null)
return; // nothing to drag
int x = me.getX() + xAdjustment; // make sure grid cell will be chosen in-bounds
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
x = Math.min(x, xMax);
x = Math.max(x, 0);
int y = me.getY() + yAdjustment;
int yMax = layeredPane.getHeight() - userDragLetter.getHeight();
y = Math.min(y, yMax);
y = Math.max(y, 0);
if(y >= pnlGrid.getHeight())
return; // can't drag to location off grid
userDragLetter.setLocation(x, y);
}
void mouseReleasedActionPerformed(MouseEvent e)
{
//**EDIT: CHANGED NEXT LINE**
gamePanel.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (userDragLetter == null)
return; // nothing to drag; nothing to release
// Make sure the chess piece is no longer painted on the layered pane
userDragLetter.setVisible(false);
layeredPane.remove(userDragLetter);
userDragLetter.setVisible(true);
int xMax = layeredPane.getWidth() - userDragLetter.getWidth();
int x = Math.min(e.getX(), xMax);
x = Math.max(x, 0);
int yMax = layeredPane.getHeight()- userDragLetter.getHeight();
int y = Math.min(e.getY(), yMax);
y = Math.max(y, 0);
Component c = pnlGrid.findComponentAt(x, y); // find deepest nested child component
if(c == null) // then grid cell is unoccupied so ...
c = pnlUser.findComponentAt(x, y); // see if there's a letter there ...
if(c == null | (c instanceof JLabel)){ // and if illegal or there is one, put it back...
userDragLetter.setLocation(parentLocation.x + xAdjustment,
parentLocation.y + yAdjustment + gamePanel.getHeight());
userDragLetter.setVisible(true);
addPiece(homeCol, homeRow,userDragLetter.getName().substring(6,7));
layeredPane.remove(userDragLetter);
return;
}
else // but if NO letter ...
{
Container parent = (Container)c;
parent.add( userDragLetter ); // put one in the grid cell
parent.validate();
}
userDragLetter.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
}
public static void main(String[] args)
{
new ChessBoard();
frame.add(gamePanel);
frame.setDefaultCloseOperation( DISPOSE_ON_CLOSE );
// frame.setResizable( false );
frame.pack();
frame.setLocationRelativeTo( null );
frame.setVisible(true);
}
}
At the moment, all i see is a thin black line extending from the top left corner of the JFrame screen. I am assuming it is the bottom edge of my card and the rest is blocked from view
When i added the Card straight to the JFrame i could see all of it, so i am confused why i can only see this line (measuring the width of the card) when i add the card to the JPanel in the frame.
Code for JFrame:
public class WarFrame extends JFrame
{
public WarFrame()
{
setSize(600, 800);
setTitle("War");
setLocationRelativeTo(null);
setDefaultCloseOperation(EXIT_ON_CLOSE);
JPanel panel = new JPanel();
panel.setBackground(Color.GREEN);
add(panel);
panel.add(new Card(Rank.ACE));
}
public static void main(String[] args)
{
WarFrame game = new WarFrame();
game.setVisible(true);
}
}
Code for Card:
public class Card extends JComponent
{
private final Rank rank;
private boolean faceUp;
private int x;
private int y;
private final int width;
private final int height;
private final int arcWidth;
private final int arcHeight;
public Card(Rank r)
{
rank = r;
faceUp = false;
x = 0;
y = 0;
width = 75;
height = 100;
arcWidth = 10;
arcHeight = 10;
}
public Card(Rank r, int x, int y)
{
rank = r;
faceUp = false;
this.x = x;
this.y = y;
width = 75;
height = 100;
arcWidth = 10;
arcHeight = 10;
}
#Override
protected void paintComponent(Graphics g)
{
Graphics2D pen = (Graphics2D) g;
//this is the black boarder
pen.fillRoundRect(x, y, width, height, arcWidth, arcHeight);
//white card body
pen.setColor(Color.WHITE);
pen.fillRoundRect(x + 5, y + 5, width - 10, height - 10, arcWidth, arcHeight);
if (faceUp)
{
//draw the card's symbol
pen.setFont(pen.getFont().deriveFont(50f));
pen.setColor(Color.RED);
if (rank == Rank.TEN)
{
//10 has 2 digits, so needs to be shifted a bit
pen.drawString(rank.getSymbol(), x + 5, y + 65);
}
else
{
pen.drawString(rank.getSymbol(), x + 20, y + 65);
}
}
else
{
//draw a blue rectangle as back of card pic
pen.setColor(Color.BLUE);
pen.fillRoundRect(x + 10, y + 10, width - 20, height - 20, arcWidth, arcHeight);
}
}
I also noticed something interesting about adding the Card straight to the JFrame. The entire card shows up if painted from 0, 0
frame.add(new Card(Rank.ACE, 0, 0));
but if i add it where x > 0,
frame.add(new Card(Rank.ACE, 2, 10));
then the card starts getting cut off on the right side. Somehow when y > 0 the card is painted correctly at a lower part of the screen.
So, any suggestions why A. adding the card to a panel only makes a little line visible and
B. when added straight to the frame, why is the card getting cut off only when x > 0?
By default a JPanel uses a FlowLayout which respects the preferred size of any component added to it. When you do custom painting the default preferred size of a JComponent is (0, 0).
You need to override the getPreferredSize() of your Card class to return the proper Dimension for the Card.
Trying to figure out the best way to do this (And without crossing any specifics DO NOTs that I don't know about).
I'm working on visually displaying a graph (Various nodes, with edges connecting them) with circles and lines to represent such. Each node will be added during runtime and I can't hardcode this. From what I understand, all painting needs to be done in the paint(Graphics g) method - which isn't that helpful, since I can't be change the parameters and it seems this is only called during the initial creation?
Right now I was thinking about having it call various other methods, passing the Graphics object, and depending on other variables - I'll decide whether that's what I even want to call (Since the paint() method is the only one I can call).
Am I going about this completely wrong? Never bothered with this before.
To give you a better idea of what I want to end up with: I want to be able to pass the coordinates of the shape I want to add for the node, and then add it to whatever I have on the graph so far. And then same with the edges, I want to be able to pass the beginning and end point of the line to repaint on top of whatever is existing at that time.
Not exactly what I want right now - but you'll get the idea from what I patched together so far:
import java.awt.*;
import javax.swing.*;
public class MyCanvas extends Canvas
{
public MyCanvas()
{
}
public void paint(Graphics graphics)
{
// Keep this until I figured out if it's painted on load or not.
graphics.drawLine(10, 20, 350, 380);
}
public static void main(String[] args)
{
MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
frame.setSize(canvasSize, canvasSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(canvas);
frame.setVisible(true);
}
public void drawNode(int x, int y, Graphics g)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g.setColor(Color.white);
g.fillOval(xLoc, yLoc, 8, 8);
g.drawOval(xLoc, yLoc, 8, 8);
}
public void drawArc(int x, int y, int xx, int yy, Graphics g)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
Edit: (Response for Andrew)
import java.awt.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
public class MyCanvas extends JPanel
{
public MyCanvas() {
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
}
public static void main(String[] args)
{
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
JFrame frame = new JFrame();
JLabel label = new JLabel();
BufferedImage bImage = new BufferedImage(canvasSize, canvasSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bImage.createGraphics();
g2d.drawLine(50, 50, 300, 300);
ImageIcon iIcon = new ImageIcon(bImage);
label.setIcon(iIcon);
frame.add(label);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
g2d = drawNode(1,1,g2d);
label.repaint();
}
public static Graphics2D drawNode(int x, int y,Graphics2D g2d)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g2d.setColor(Color.white);
g2d.fillOval(xLoc, yLoc, 8, 8);
g2d.drawOval(xLoc, yLoc, 8, 8);
return g2d;
}
public static void drawArc(int x, int y, int xx, int yy)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
// g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
There are various strategies you might pursue for this.
If the objects are never removed from the drawing once done, use a BufferedImage, put it in a (ImageIcon in a) JLabel. When it comes time to update:
Get the graphics instance of the image and draw the new element.
Dispose of the graphics object.
Call repaint() on the label.
Keep a list of the drawn elements. In the paint method, paint them all. When a new element is added, call repaint() on the rendering component.
Here is an example of the 1st technique:
import java.awt.image.BufferedImage;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.util.Random;
public class MyCanvas
{
JLabel view;
BufferedImage surface;
Random random = new Random();
public MyCanvas()
{
surface = new BufferedImage(600,400,BufferedImage.TYPE_INT_RGB);
view = new JLabel(new ImageIcon(surface));
Graphics g = surface.getGraphics();
g.setColor(Color.ORANGE);
g.fillRect(0,0,600,400);
g.setColor(Color.BLACK);
// Keep this until I figured out if it's painted on load or not.
g.drawLine(10, 20, 350, 380);
g.dispose();
ActionListener listener = new ActionListener() {
public void actionPerformed(ActionEvent ae) {
addNewElement();
}
};
Timer timer = new Timer(200, listener);
timer.start();
}
public void addNewElement() {
boolean drawArc = random.nextBoolean();
int x = random.nextInt(60);
int y = random.nextInt(40);
Graphics g = surface.getGraphics();
if (drawArc) {
g.setColor(Color.BLUE);
int xx = random.nextInt(60);
int yy = random.nextInt(40);
drawArc(x,y,xx,yy,g);
} else {
drawNode(x,y,g);
}
g.dispose();
view.repaint();
}
public static void main(String[] args)
{
MyCanvas canvas = new MyCanvas();
JFrame frame = new JFrame();
int vertexes = 0;
// Change this next part later to be dynamic.
vertexes = 10;
int canvasSize = vertexes * vertexes;
frame.setSize(canvasSize, canvasSize);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setContentPane(canvas.view);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public void drawNode(int x, int y, Graphics g)
{
// Treat each location as a 10x10 block. If position 1,1 then go to (5,5) - If position 3,5 then go to (25, 45) eg: (x*10)-5, (y*10)-5
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
g.setColor(Color.white);
g.fillOval(xLoc, yLoc, 8, 8);
g.drawOval(xLoc, yLoc, 8, 8);
}
public void drawArc(int x, int y, int xx, int yy, Graphics g)
{
int xLoc = (x*10) - 5;
int yLoc = (y*10) - 5;
int xxLoc = (xx*10) - 5;
int yyLoc = (yy*10) - 5;
g.drawLine(xLoc, yLoc, xxLoc, yyLoc);
}
}
Further tip
You might notice that the lines look quite 'jagged' & ugly. Both the BufferedImage or a JComponent has access to the more useful Graphics2D object (for the JComponent it is necessary to cast it in paintComponent()). A Graphics2D instance accepts rendering hints that can be used to smooth (dither) the elements drawn.