Drawing on JSplitPane not showing - java

Just trying to draw some lines to the screen.
I've checked to ensure all the relevant code's being run
I've tried calling repaint (and ensuring that's being run)
Since this is a JSplitPane, the layout must be the JSplitPane layout
I'm setting the color to ensure it isn't drawing using the background color.
I've checked the height and width to ensure its size isn't 0 or something
I've tried drawing text as well; same result
I've changed the coordinates all over the place, tried both arbitrary and proportional values
Or at least I think. Swing is unintuitively quirky. I'd use AWT, but I need the specificity Swing offers. Anyway, the code. It's just a split pane, which is actually displaying - resizable and all - but the contents of the top pane (the only one I've attempted to put anything in) don't show.
package derange;
import java.awt.Dimension;
import java.awt.GridLayout;
import javax.swing.*;
public class Derange {
private static void createAndShowGUI() {
//Create and set up the window.
JFrame frame = new JFrame("Derange");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//Display the window.
frame.setExtendedState(frame.getExtendedState() | JFrame.MAXIMIZED_BOTH);
frame.pack();
frame.setVisible(true);
//Create a split pane with the two scroll panes in it.
PanelScore scorePane = new PanelScore();
JScrollPane instrumentPane = new JScrollPane();
JSplitPane splitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT,
scorePane, instrumentPane);
splitPane.setOneTouchExpandable(true);
splitPane.setDividerLocation((frame.getHeight() / 4) * 3 );// Three-quarters of the way down
splitPane.setDividerSize(20);
//Provide minimum sizes for the two components in the split pane
Dimension minimumSize = new Dimension(frame.getWidth(), frame.getHeight()/ 2);//width, height
scorePane.setMinimumSize(minimumSize); //Score takes up at least half the screen
instrumentPane.setMinimumSize(new Dimension(0,0));//no minimum size on the instrument panel; collapsible
frame.getContentPane().add(splitPane);
}
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}
.
package derange;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Insets;
import javax.swing.JScrollPane;
#SuppressWarnings("serial")//wtf is this needed for?
public class PanelScore extends JScrollPane{
public int strings = 6;
public void drawStaffTablature(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.black);
int xStart = 30;//insets.left;
int xEnd = getParent().getWidth() - 30;
int yCoord = this.getHeight() / 2;
System.out.println(this.isShowing());
//Space between tablature lines
int lineSpacing = 15;
//Space between staffs.
int staffSpacing = 60;`enter code here`
for(int x = 0; x < strings; x++){
g2d.drawLine(xStart, yCoord + (lineSpacing * x), xEnd, yCoord + (lineSpacing * x));
//System.out.println("String: " + (x + 1));
g.drawString("Test", xStart, yCoord); //change the co-odrinates
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
drawStaffTablature(g);
}
}

Short answer, don't extend from JScrollPane, a JScrollPane contains single component known as a JViewport, which covers the most of the scroll pane (the rest is taken up by the JScrollBars
Instead, try extending from something like JPanel.
I'd also advise you against using anything like int xEnd = getParent().getWidth() - 30; within your paint code, the Graphics context is translated to the components location, making the top/left corner 0x0 and clipped to the components current width and height

Related

Get knob/thumb postion JScrollPane

I need to get the exact middle or top point of a knob/thumb in JScrollPane, so the current focused component (the one with the yellow background) will follow the thumb. Is it possible?
ps. Sorry for missing the code example, but the implementation is quite large.
Maybe there is a way to grab the thumb/knob of the scroll pane but I don't know it. Here is a geometric way to find what you're after.
We'll add an adjustment listener, then get the value of the slider. From the value we can calculate the fraction of the scene the slider is moved along. Then we get the height of the scroll bar and we can use the fraction to find the position of the knob.
double relativePositionOnScreen = height*model.getValue()/(model.getMaximum() - model.getMinimum());
The position of the knob on screen will have to be shifted relative to the viewport, so we can just add the value to that.
Here is an example, with a scrollpane and inside of the scrollpane is a panel that has a box on it following the knob.
import javax.swing.*;
import java.awt.Dimension;
import java.awt.Component;
import java.awt.geom.*;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class ScrollByMe{
public static void main(String[] args){
Rectangle2D rect = new Rectangle2D.Double(0, 0, 0, 0);
JFrame frame = new JFrame("scroll by me");
JPanel panel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
((Graphics2D)g).draw(rect);
}
};
panel.setPreferredSize( new Dimension(200, 800) );
JScrollPane scroller = new JScrollPane( panel);
JScrollBar bar = scroller.getVerticalScrollBar();
bar.addAdjustmentListener( e ->{
BoundedRangeModel model = bar.getModel();
Dimension d = bar.getSize();
double height = d.getHeight();
double buttonHeight = model.getExtent() * height / (model.getMaximum() - model.getMinimum());
//maximum *value* range.
double relativePositionOnScreen = height*model.getValue()/(model.getMaximum() - model.getMinimum());
rect.setRect( 40, model.getValue() + relativePositionOnScreen, 40, buttonHeight);
panel.repaint();
});
frame.add(scroller);
frame.setSize(200, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}

Unable to create a button on top of a rectangle

Here is a piece of code :
import java.awt.Color;
import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class QuitButton extends JPanel implements ActionListener
{
static JButton button = new JButton("Panic");
Color[] colors = new Color[9];
boolean pressed = false;
public QuitButton()
{
button.addActionListener(this);
colors[0] = Color.RED;
colors[1] = Color.BLUE;
colors[2] = Color.GREEN;
colors[3] = Color.YELLOW;
colors[4] = Color.BLACK;
colors[5] = Color.PINK;
colors[6] = Color.MAGENTA;
colors[7] = Color.ORANGE;
colors[8] = Color.CYAN;
}
#Override
public void actionPerformed(ActionEvent e)
{
pressed = true;
}
public static void main(String args[])
{
JFrame frame = new JFrame("Do NOT Panic!!");
QuitButton qb = new QuitButton();
frame.add(qb);
frame.add(button);
frame.setLayout(new FlowLayout());
frame.setSize(400, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
//frame.pack();
button.requestFocus();
qb.gameLoop();
}
public void gameLoop()
{
while (true)
{
repaint();
try
{
Thread.sleep(200);
} catch (Exception e)
{
}
}
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
if (pressed == false)
{
super.paint(g2d);
g2d.setColor(Color.GRAY);
g2d.fillRect(0, 0, 400, 400);
} else
{
super.paint(g2d);
Random r = new Random();
int min = 0;
int max = 8;
int index = r.nextInt(max - min) + min;
g2d.setColor(colors[index]);
g2d.fillRect(0, 0, 400, 400);
}
}
The purpose of this program: The rectangle should be grey before but when I click the panic button colours should start changing.
Please don't get confused with the name of the class which is QuitButton.
But my rectangle is not occupying the entire window. Instead I am getting a teeny tiny rectangle like this : http://g.recordit.co/xJAMiQu6fM.gif
I think it is because of the layout I am using and I haven't specified anywhere that the button will be on top. Probably that's why they are coming side by side. I am new to GUI creation and thank you for your help.
You seem to be making some guesses on how to do this, which is not a good way to learn to use a library. Your first step should be to check the relevant tutorials on this, most of which will be found here: Swing Info Since this appears to be homework, I'm not going to give you a code solution but rather suggestions on how to improve:
Override paintComponent, not paint since the latter gives double buffering and is less risky (less painting of borders and child component problems)
In your paintComponent override, be sure to call the super's paintComponent method first to clear "dirty" pixels.
Use a Swing Timer, not a while loop for your game loop. This will prevent your while loop from freezing the Swing event thread, a problem that can freeze your program. Google the tutorial as it is quite helpful.
Do your randomization within the ActionListener's code (here likely the ActionListener for your Swing Timer), not within the painting code. The painting code should not change the state of the object but rather should only display the object's state.
FlowLayout will respect a component's preferredSize, and your component's preferred size is 0,0 or close to it. Change this. Best to override public Dimension getPreferredSize() and return a Dimension that matches your Rectangle's size.
Avoid using "magic" numbers, such as for your rectangle's size, and instead use constants or fields.
Call repaint() within your Timer's ActionListener so the JVM knows to paint the component.

How can I repaint only one layer of the JLayeredPane layers?

I am writing an application which has layers (I used JLayeredPane) containing two principal layers (JPanels). I override the paintComponent method of the Panel at the bottom (call it Map) so it paints a Map, and the the paintComponent method of the one at the top (call it selectionPanel) so it paints a selection of an element.
Here's a summary of the structure:
layers -
|-selectionPanel(on top)
|-Map (at bottom)
I want the Map to stay static, ie, not to do any repaint (except the initial one) since it does not change.
The trouble is, whenever I call selectionPanel.repaint(), Map gets repainted as well! This is a definitely not efficient.
I think this is due to the eager painting behavior of JLayeredPane. Is there a way to disable this feature in JLayeredPane?
In case you're interested to see the above effect, I've modified this example:
import java.awt.AlphaComposite;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLayeredPane;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/** #see https://stackoverflow.com/q/9625495/230513 */
public class LayerDemo extends JFrame {
private static final Dimension d = new Dimension(320, 240);
public LayerDemo() {
JLayeredPane layers = new JLayeredPane();
layers.setPreferredSize(d);
layers.add(new LayerPanel(1 * d.height / 8), 100);
layers.add(new LayerPanel(2 * d.height / 8), 101);
layers.add(new LayerPanel(3 * d.height / 8), 102);
this.add(layers, BorderLayout.CENTER);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.pack();
this.setLocationByPlatform(true);
}
private static class LayerPanel extends JPanel {
private static final Random r = new Random();
private int n;
private Color color = new Color(r.nextInt());
public LayerPanel(int n) {
this.n = n;
this.setOpaque(false);
this.setBounds(n, n, d.width / 2, d.height / 2);
this.addMouseListener(new MouseHandler(this));
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
color = new Color(r.nextInt());
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setColor(color);
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, ((float) 20) / 100));
g2d.fillRoundRect(0, 0, getWidth(), getHeight(), 16, 16);
g2d.setColor(Color.black);
g2d.drawString(String.valueOf(n), 5, getHeight() - 5);
}
}
private static class MouseHandler extends MouseAdapter {
LayerPanel panel;
MouseHandler(LayerPanel panel) {
this.panel = panel;
}
#Override
public void mouseClicked(MouseEvent e) {
panel.repaint();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
(new LayerDemo()).setVisible(true);
}
});
}
}
Is there a way to disable this feature in JLayeredPane?
If is not a JLayeredPane feature. It is a Swing painting feature.
this.setOpaque(false);
When you make a component non-opaque, then Swing needs to repaint the parent component to make sure the background is painted properly.
In your case it looks like you are using transparency so you would definitely need the background to be repainted.
whenever I call selectionPanel.repaint(), Map gets repainted as well
If you are only painting a certain area of the child panel then you can use:
selectionPanel.repaint(Rectangle)
to minimize the area that is repainted.
color = new Color(r.nextInt());
Don't change the color in the paintComponent() method. This should be done in the MouseListener so it only affects the panel you click on. So even though the other panels will be repainted, their colors will not randomly change.

How to add JPanels to JScrollPane?

I'm implementing a simple chat application, using Java. I want my chat application to have the "bubble" message style like modern message apps, so I've built 2 classes LeftArrowBubble and RightArrowBubble which extend JPanel to illustrate sender & receiver bubbles, like this:
This is the code for my LeftArrowBubble class (quite alike for RightArrowBubble):
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import javax.swing.JPanel;
/**
* #author harsh
*/
public class LeftArrowBubble extends JPanel {
private static final long serialVersionUID = -5389178141802153305L;
private int radius = 10;
private int arrowSize = 12;
private int strokeThickness = 3;
private int padding = strokeThickness / 2;
#Override
protected void paintComponent(final Graphics g) {
final Graphics2D g2d = (Graphics2D) g;
g2d.setColor(new Color(0.5f, 0.8f, 1f));
int x = padding + strokeThickness + arrowSize;
int width = getWidth() - arrowSize - (strokeThickness * 2);
int bottomLineY = getHeight() - strokeThickness;
g2d.fillRect(x, padding, width, bottomLineY);
g2d.setRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
g2d.setStroke(new BasicStroke(strokeThickness));
RoundRectangle2D.Double rect = new RoundRectangle2D.Double(x, padding, width, bottomLineY, radius, radius);
Polygon arrow = new Polygon();
arrow.addPoint(20, 8);
arrow.addPoint(0, 10);
arrow.addPoint(20, 12);
Area area = new Area(rect);
area.add(new Area(arrow));
g2d.draw(area);
}
}
Now I have a JFrame window with a JScrollPane on it, which looks like this:
What I want to do now is when I click on that CreateNewBubble button, a new Left(or Right)ArrowBubble JPanel will be created & displayed inside that JScrollPane (and this JScrollPane will be vertical scrollable if there're more bubbles inside of it). I've already tried this way:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
BubbleTest.LeftArrowBubble leftArrowBubble = new BubbleTest.LeftArrowBubble();
jScrollPane1.add(leftArrowBubble);
}
But it didn't work as I expected: nothing shows up in the JScrollPane after clicking the button!
I've been stuck at this problem for hours, really appreciate if you guys can help!
You can't use .add that way on a JScrollPane. A JScrollPane can only scroll a single component, which is set by either passing it to its constructor, or by calling .setViewportView.
Instead, create a separate container for the bubbles, such as a vertical Box, and set that as the single component scrolled by the scroll pane:
Box box = new Box(BoxLayout.Y_AXIS);
JScrollPane jScrollPane1 = new JScrollPane(box);
When you add a bubble, add it to the box (and call .revalidate() to lay it out):
box.add(leftArrowBubble);
box.revalidate();
Edit: Also, your bubbles will not, by default, have any size, unless you give them a size such as by calling setPreferredSize or by overriding getPreferredSize or by putting components inside them.
With JScrollPane you should always add components to the scroll pane's JViewPort. Look at the documentation here, it explains the concept behind the class rather well.
Short summary: A JScrollPane holds the scroll bars and a view port. The view port is a component that displays only a portion of its content - in this case the part that is visible on screen. The scroll bars tell the view port which portion to show.

Why is paintComponent never called?

I have the following code. Basically I have a frame which has a background image. I also have three panels within the frame: panels 1, 2 and 3. 2 & 3 work fine as I haven't subclassed them. However, panel 1 as soon as I subclassed it i.e. put the logic inside the paintComponent method of JPanel stopped working as that method is never called and foo is never printed. I'm not able to figure out why. Would appreciate your help. I've tried a few suggestions from other similar threads and they haven't helped.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Toolkit;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
public class Test {
public static void main(String[] args) throws IOException {
JFrame.setDefaultLookAndFeelDecorated(true);
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
int fooPanelX = 5;
int fooPanelY = 160;
int fooPanelWidth = 470;
int fooPanelHeight = 305;
int bar0PanelX = 5;
int bar0PanelY = 550;
int bar0PanelWidth = 230;
int bar0PanelHeight = 210;
int bar1PanelX = bar0PanelX * 2 + bar0PanelWidth + bar0PanelX;
int bar1PanelY = bar0PanelY;
int bar1PanelWidth = bar0PanelWidth;
int bar1PanelHeight = bar0PanelHeight;
JPanel panel1 = new Panel1(fooPanelX, fooPanelY, fooPanelWidth, fooPanelHeight);
JPanel panel2 = new JPanel();
panel2.setBackground(Color.WHITE);
panel2.setLocation(bar0PanelX, bar0PanelY);
panel2.setSize(bar0PanelWidth, bar0PanelHeight);
panel2.setOpaque(false);
panel2.setBorder(BorderFactory.createLineBorder(Color.WHITE));
panel2.setBounds(bar0PanelX, bar0PanelY, bar0PanelWidth, bar0PanelHeight);
JPanel panel3 = new JPanel();
panel3.setBackground(Color.WHITE);
panel3.setLocation(bar1PanelX, bar1PanelX);
panel3.setSize(bar1PanelWidth, bar1PanelHeight);
panel3.setOpaque(false);
panel3.setBorder(BorderFactory.createLineBorder(Color.WHITE));
panel3.setBounds(bar1PanelX, bar1PanelY, bar1PanelWidth, bar1PanelHeight);
JLabel imagePanel = new JLabel(new ImageIcon(ImageIO.read(new File("image.png"))));
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == 27) {
System.exit(0);
}
}
});
frame.setContentPane(imagePanel);
frame.getContentPane().add(panel1);
frame.getContentPane().add(panel2);
frame.getContentPane().add(panel3);
frame.setLocation((int) (screenSize.getWidth() * 0.75),
(int) (screenSize.getHeight() * 0.25));
frame.pack();
frame.setVisible(true);
}
#SuppressWarnings("serial")
static class Panel1 extends JPanel {
int x, y, w, h;
public Panel1(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 graphics) {
System.out.println("foo");
super.paintComponents(graphics);
setBackground(Color.WHITE);
setLocation(x, y);
setSize(w, h);
setOpaque(true);
setBorder(BorderFactory.createLineBorder(Color.WHITE));
}
}
}
Update: If you aren't able to find the issue then please could you provide me with an alternative way of doing the following. I need a frame with a background image and three panels on top of the background image. The three panels have to have pixel perfect locations and sizes on the background image to look right. That's it pretty much. I'll be repainting the three panels but the background image will remain the same.
Well, the problem is that you are not using an appropriate LayoutManager.
JLabel does not come with any LayoutManager by default. So when you add your Panel1, it has a size of 0x0 and is located in (0,0) and since no LayoutManager will change that, it will keep that size and location. With empty bounds, your component is never painted, hence your paintComponent method is never called.
Now, you should NEVER do this in paintComponent:
setBackground(Color.WHITE);
setLocation(x, y);
setSize(w, h);
setOpaque(true);
setBorder(BorderFactory.createLineBorder(Color.WHITE));
Do that in the constructor or some other method. paintComponent is meant for "painting a component", not changing its properties.
I decided to tackle the problem in a very different way. This is how I did it. I startedc completely from scratch with my code. I created a JFrame instance and a Canvas instance (the canvas was subclassed). In the canvas I used drawImage() to apply the background image. Then for each of the three areas that I wanted to animate on the background image, instead of creating three JPanels, I simply used fillRect() within the canvas to fill the right areas on the image. That's it. Nice and simple. The repaint() every second does flickr on the three areas and that's the next challenge. I'm guessing I have to use double buffering but it's not something I've used before so I'll look into that next. Anyway, using a single canvas in place of three JPanels proved a heck of a lot simpler and the reason I was able to do that was because the background image provided everything else visually. All I had to do was drawImage() and fillRect(). Thanks for all contributions.
Update: I have now completed this task. There was one thing I changed about the above. While attempting to double buffer with Canvas I had a few issues: the usual "component must have valid peer" exception. While looking into that I learnt that one should not use Canvas in Swing and that the practice of using it was mixing AWT and Swing. So I swapped it out for JComponent (as I didn't need anything that JPanel offered). And as Swing is double buffered by default my work was complete. No flicker and simplified code.

Categories