JTextArea in JScrollPane, view coordinate translation - java

I'm trying to translate between view and viewport coordinates.
But the JViewport/JScrollpane doesn't seem to work as documented. JViewport.toViewCoordinates()
thinks the view is always at the top left of the component, even though that's clearly not the case.
String text = "blahblahblah\nblahblah\nblah";
JFrame frame = new JFrame("title");
JTextArea textArea = new JTextArea(text, 1, 30); // shows only one line
frame.add(new JScrollPane(textArea));
frame.pack();
frame.setVisible(true);
textArea.setCaretPosition(text.length()); // now showing the last line
JViewport viewport = ((JViewport)textArea.getParent());
viewport.getViewRect(); // returns java.awt.Rectangle[x=0,y=0,width=330,height=16]
viewport.getViewPosition(); // returns java.awt.Point[x=0,y=0]
viewport.toViewCoordinates(new Point(0,0)); // returns java.awt.Point[x=0,y=0]
The above is contrived example. My real JTextArea is larger than one line. I don't need JTextArea "model" coordinate (the offset in the text). I need genuine 2d coordinates.
The view position shouldn't be (0,0), as the first visible character in the viewport is actually in the 3rd line of the JTextArea.
Any other suggestions on how I can translate between view and component coordinates when using JScrollPane?
--- added ---
This also fails.
SwingUtilities.convertPoint(viewport,0,0, textArea);
(java.awt.Point) java.awt.Point[x=0,y=0]
--- added ---
Here is the final working version, based on the answer I received.
it shows java.awt.Point[x=0,y=32] which is what I expected.
#Test
public void test() throws InterruptedException {
String text = "blahblahblah\nblahblah\nblah";
JFrame frame = new JFrame("title");
JTextArea textArea = new JTextArea(text, 1, 30);
frame.add(new JScrollPane(textArea));
frame.pack();
frame.setVisible(true);
textArea.setCaretPosition(text.length());
final JViewport viewport = ((JViewport)textArea.getParent());
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run() {
System.out.println(viewport.getViewPosition());
}
});
Thread.sleep(1000);
}

The problem is that the method to get the viewPosition() executes before the viewport has actually been scrolled. This is because sometimes Swing adds code to the end of the event thread for later processing.
Usually this problem can be solved by wrapping your code in a SwingUtilities.invokeLater() so the code is executed after Swing has done all its processing. However in the simple demo below I found I needed to add two invokeLater() methods. I'm not sure why.
Move the caret up/down and you will see the view position change. The second value will contain the correct position:
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
public class Test5
{
public static void createAndShowGUI()
{
String text = "one\ntwo\nthree\nfour\nfive";
JFrame frame = new JFrame("title");
JTextArea textArea = new JTextArea(text, 1, 30); // shows only one line
JScrollPane scrollPane = new JScrollPane( textArea );
frame.add(scrollPane);
frame.pack();
frame.setVisible(true);
final JViewport viewport = scrollPane.getViewport();
textArea.addCaretListener( new CaretListener()
{
public void caretUpdate(CaretEvent e)
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
System.out.println("First : " + viewport.getViewPosition() );
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
System.out.println("Second: " + viewport.getViewPosition() );
}
});
}
});
}
});
textArea.setCaretPosition(text.length());
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
}

could
SwingUtilities.convertPoint
be of use?

Related

Why doesn't the image paint over my JPanel?

I have been struggling with this for some time. At first, I only used ActionListener, then I added the paintComponent, but I have no idea what to put there. I read some tutorials and used their code as an example, but it still doesn't work. Right now, the end result is the same as it was without PaintComponent.
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
public class Scream extends JPanel {
private JButton button = new JButton("OK");
private Color screenColor;
private JPanel panel = new JPanel();
private JFrame frame;
private Dimension screenSize;
private ImageIcon image;
private JLabel label = new JLabel(image);
private int x;
private int y;
private boolean mouseClicked;
public Scream() {
button.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e ) {
if (e.getSource() == button) {
mouseClicked = true;
frame.getContentPane().add(label);
frame.setSize(image.getIconWidth(), image.getIconHeight());
panel.repaint();
}
}
});
frame = new JFrame ("Existential angst");
screenColor = new Color(150, 100, 0);
panel.setBackground( screenColor );
frame.add(button, BorderLayout.PAGE_END);
frame.add(panel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1300, 700);
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
image.paintComponent(this, g, 1300, 700);
}
public static void main (String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Scream scream = new Scream();
}
});
}
}
If you are trying to dynamically add an image to a panel then you need to add the label to the panel. There is no need for any custom painting.
The basic code for adding components to a visible GUI is:
panel.add(...);
panel.revalidate();
panel.repaint();
Also, don't attempt to set the size of the frame to the size of the image. A frame contains a titlebar and borders. Instead you can use frame.pack();
I noticed a couple of issues:
image is never initialized to anything so it is null, effectively making the label empty. I assume maybe your example was just incomplete?
Once I initialized the image to something, your example still did not work. Turns out adding label without specifying any constraint basically does nothing (I assume since adding a component to a border layout without a constraint puts it in the center where panel already is). When I added the label to BorderLayout.NORTH, everything worked (though resizing the frame to the size of the image makes it only partially visible since the frame includes the OK button)

Setting divider location on a JSplitPane

I'd like to have 3 resizable horizontally JPanels. It works fine but I can not set the position of the first JSlitPane: sp.setDividerLocation(.3); doesn't work.
public class JSplitPanelProva extends JFrame {
public JSplitPanelProva() {
this.setLayout(new BorderLayout());
JPanel leftPanel = new JPanel();
leftPanel.setBackground(Color.BLUE);
JPanel centerPanel = new JPanel();
centerPanel.setBackground(Color.CYAN);
JPanel rightPanel = new JPanel();
rightPanel.setBackground(Color.GREEN);
JSplitPane sp = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel, centerPanel);
JSplitPane sp2 = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, sp, rightPanel);
sp.setOneTouchExpandable(true);
sp2.setOneTouchExpandable(true);
this.add(sp2, BorderLayout.CENTER);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setSize(1000, 600);
this.setVisible(true);
sp.setDividerLocation(.3);
sp2.setDividerLocation(.6);
}
/**
* #param args the command line arguments
*/
public static void main(String[] args) {
new JSplitPanelProva();
}
}
I get this:
Can someone help me?
Thanks.
Change:
sp.setDividerLocation(.3);
sp2.setDividerLocation(.6);
To:
sp2.setDividerLocation(.6);
ActionListener splitListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
sp.setDividerLocation(.3);
}
};
Timer t = new Timer(200, splitListener);
t.setRepeats(false);
t.start();
And it will work as expected. The delay gives time for the GUI to recalculate sizes.
The documentation of the setDividerLocation(double proportionalLocation) method says:
If the split pane is not correctly realized and on screen, this method
will have no effect (new divider location will become (current size *
proportionalLocation) which is 0).
What you can do instead is using the setDividerLocation(int location) method like this:
sp.setDividerLocation(300);
sp2.setDividerLocation(600);
It looks like 3 things need to happen:
The divider location can't be set until the frame is visible
Setting the location of the second split pane needs to be done first
Setting the location of the first split pane needs to be added to the end of on the Event Dispatch Thread (EDT)
The following code will accomplish all 3:
this.setVisible(true);
sp2.setDividerLocation(.6);
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
sp.setDividerLocation(.3);
}
});
Note: all Swing components should be create on the EDT. So you should also be using the following to create the frame:
EventQueue.invokeLater(new Runnable()
{
public void run()
{
new JSplitPaneProva();
}
});

Java Swing blank screen instead of window displayed

I'm following through a book called "The JFC Swing Tutorial (Second Edition)" and I'm pretty much at the start I have followed this code and it should be displaying the button and the label in the content pane, but All im getting is a blank screen. any ideas?
Thanks.
import java.awt.GridLayout;
import javax.swing.*;
public class m extends JFrame
{
void UserFrame()
{
//JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Hellow You");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel jp = new JPanel(new GridLayout(0,1));
//makes label
JLabel label = new JLabel("Sup ");
//adds to the frames content pane a label
frame.getContentPane().add(label);
JButton button = new JButton("Hai");
frame.getContentPane().add(button);
jp.add(button);
jp.add(label);
jp.setBorder(BorderFactory.createEmptyBorder(30,30,10,30));
//pack set the window to what it needs AKA to display all components
frame.pack();
//frame.setSize(250, 250);
//shows window
frame.setVisible(true);
}
public static void main(String[] args)
{
final m window = new m();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
window.UserFrame();
}
});
}
}
Simply add
frame.add(jp);
just before
frame.pack();
What's happening here? You correctly add all your widgets to a JPane, but you basically threw that JPane away and didn't use it anywhere.
This will be sufficient just to get it to work properly.
If you want to do it correctly, you should also remove frame.getContentPane().add(label); and frame.getContentPane().add(button); (Thank you #dic19 for noting that!). These will not work the way you used it.

How to redraw JTextPane when one line is changed to a larger font

I have a JTextPane whose model is a DefaultStyledDocument. I'm noticing that if the text is displayed, and I then use setCharacterAttributes to change every character on a line to a much larger font, the font of the characters on that line is changed in the display as I expect, but the lines below it don't move, so that the lines overlap in the display.
Is there a way to force the JTextPane to recompute the text locations and redisplay itself (or a portion of itself)? I tried setting up a DocumentListener with changedUpdate, and changedUpdate is indeed called, but I can't find a way to make it redraw the JTextPane. revalidate() didn't work.
EDIT: The lines do shift by themselves with a smaller test case, so apparently something else I'm doing in the program is interfering, but I haven't figured out what. Anyway, repaint() without revalidate() works if I can't determine what feature is causing a problem and how to get around it.
EDIT 2: The problem occurs when the JTextPane is added to a JPanel and the JPanel is set up with BoxLayout and BoxLayout.X_AXIS. A sample:
public class Demo extends JFrame {
JPanel panel;
JTextPane textPane;
DefaultStyledDocument doc;
SimpleAttributeSet smallText, bigText;
public Demo() {
super("Demo");
doc = new DefaultStyledDocument ();
textPane = new JTextPane(doc);
panel = new JPanel();
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
// problem goes away if above line is removed
panel.add(textPane);
panel.setPreferredSize(new Dimension(1000, 500));
textPane.setCaretPosition(0);
textPane.setMargin(new Insets(5,5,5,5));
getContentPane().add(panel, BorderLayout.CENTER);
smallText = new SimpleAttributeSet();
StyleConstants.setFontFamily(smallText, "SansSerif");
StyleConstants.setFontSize(smallText, 16);
bigText = new SimpleAttributeSet();
StyleConstants.setFontFamily(bigText, "Times New Roman");
StyleConstants.setFontSize(bigText, 32);
initDocument();
textPane.setCaretPosition(0);
}
protected void initDocument() {
String initString[] =
{ "This is the first line.",
"This is the second line.",
"This is the third line." };
for (int i = 0; i < initString.length; i ++) {
try {
doc.insertString(doc.getLength(), initString[i] + "\n",
smallText);
} catch (BadLocationException e) {
}
}
}
private void createAndShowGUI() throws InterruptedException {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
pack();
setVisible(true);
}
public static void main(String[] args) throws Exception {
new Demo().runMain();
}
private void runMain() throws Exception {
SwingUtilities.invokeAndWait(new Runnable() {
public void run() {
UIManager.put("swing.boldMetal", Boolean.FALSE);
try {
createAndShowGUI();
} catch (InterruptedException e) {
}
}
});
Thread.sleep(2000);
doc.setCharacterAttributes(24, 24, bigText, false);
}
}
Call repaint() it will redraw the container. DocumentListener reflects changes to a text document, so in your case is inappropriate. You can use DefaultStyledDocument.getStyle().addChangeListener() to handle changes of the attributes.
I've just found that it also works to add a setMinimumSize call to the JTextPane:
.......
panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
// problem goes away if above line is removed
panel.add(textPane);
textPane.setMinimumSize(new Dimension(1000, 500)); // NEW
// These also work:
// textPane.setMinimumSize(new Dimension(1, 1)); or
// textPane.setMinimumSize(new Dimension(0, 0));
panel.setPreferredSize(new Dimension(1000, 500));
textPane.setCaretPosition(0);
.......
This is slightly preferable to the solution of wrapping the JTextPane inside a JScrollPane, because the latter displays some extra lines near the border even when no scrollbars are displayed.

Internal JFrames

I want to know how to show an internal frame in swing. That means,when a JFrame is needed, normally what I do is,
new MyJFrame().setVisible(true);
Let's say the previous form should be displayed as well. And when this new frame is displayed,another new icon is displayed on the task bar.(it sounds like two separate applications run in one application) I want to avoid showing that icon and display both frames as they are in one application. Thank you
..want to avoid showing that icon and display both frames as they are in one application.
Another solution is to put the 2nd and subsequent free floating elements in a JDialog.
E.G. of using both a frame and dialog to hold extra content.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
class FrameTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
initGui();
}
});
}
public static void initGui() {
final JFrame f = new JFrame("Frame Test");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel gui = new JPanel(new GridLayout(0,1,5,5));
final Content c = new Content();
JButton frame = new JButton("Frame");
frame.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JFrame f2 = new JFrame("Content");
f2.add(c.getContent());
f2.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f2.pack();
f2.setLocationByPlatform(true);
f2.setVisible(true);
}
});
gui.add(frame);
JButton dialog = new JButton("Dialog");
dialog.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
JDialog d = new JDialog(f);
d.add(new Content().getContent());
d.pack();
d.setLocationByPlatform(true);
d.setVisible(true);
}
});
gui.add(dialog);
f.add(gui);
f.pack();
f.setVisible(true);
}
}
class Content {
public Component getContent() {
JPanel p = new JPanel();
p.add(new JLabel("Hello World!"));
return p;
}
}
You have one JFrame for an application.
You can display multiple JPanels within a JFrame.
Or, as trashgod pointed out, you can have multiple JInternalFrames within a JDesktopFrame.

Categories