Setting scroll position of JScrollPane after repaint of content Pane - java

I have a JScrollPane with a JPanel in it, which shows something like a time sheet. This time sheet panel is zoomable abd scrollable. I have many entries in this time sheet panel, each entry is an own panel and has a start time and a length.
When I now click on an entry in this time sheet, I want to zoom in this entry and, of course, move to it.
For example: The actual zoom shows 3 Month from January to March. Now I have an entry at the 1st of March until the 3rd of March. This entry is now for example 10 pixel long. Wren I click on this entry, the zoom factor is changed in that way, that the time sheet only shows 3 days and starts on the 1st of March, so that the entries width is over the complete scroll panes view port.
For this, I calculate the new zoom factor and the new Start X Position of the time sheet panel and call a repaint of the panel.
This works with the zoom, but because the time sheet panel has not the new width at this moment, this leads to that the scroll pane scrollbars have not the new sizes. So I can not set the new scrollbar position at this point of code.
So, how can I set the right scrollbar position after the repaint of the scroll pane is done and the scrollers have their new sizes?
Here is some pseudo code for mor understanding (Sorry for the formating but I didnt manage to paste the code so that the indents are correct):
public class MyRootPanel extends JPanel
{
TimeSheetPanel tsPanel = new TimeSheetPanel(this);
tsPanel.setLayout(null);
scrollPanel=new JScrollPane(tsPanel,ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED,ScrollPaneConstants.HORIZONTAL_SCROLLBAR_AS_NEEDED);
this.add(scrollPanel);
public setScrollPos(int xPos)
{
scrollPanel.getHorizontalScrollBar().setValue(xPos);
}
}
public class TimeSheetPanel extends JPanel
{
MyRootPanel rootPanel;
public int pixelPerHour = 5; // Zoom factor
public int rowHeight = 30; // height of one row on table
public TimeSheetPanel(MyRootPanel root)
{
rootPanel = root;
this.setLayout(null);
setBounds(0,0, maxDays * pixelPerHour *24, maxrows * rowHeight);
for(Entry e : entryList)
{
EntryPanel entryPanel = new EntryPanel(this, entry);
this.add(entryPanel);
}
}
public void zoomToEntry(Entry entry)
{
// calc new zoom factor here.
pixelPerHour = ......
newXPos = .....
// and set it to the panel
setBounds(0,0, maxDays * pixelPerHour *24, maxrows * rowHeight);
// call repaint
repaint();
// THIS WILL NOT WORK
rootPanel.setScrollPos(newXPos);
}
}
public class EntryPanel extends JPanel
{
TimeSheetPanel parentPanel;
Entry entry;
public EntryPanel(TimeSheetPanel parent, Entry e)
{
parentPanel = parent;
Entry = e;
this.setLayout(null);
int left = entry.start * parentPanel.pixelPreHour;
int width = entry.length * parentPanel.pixelPerHour;
int top = entry.row * parentPanel.rowHeight;
int height = parentPanel.rowHeight;
setBounds(left, top, width, height);
addMouseListener(this);
}
#Override
public void mouseClicked(MouseEvent e)
{
if (e.getClickCount() == 2)
{
parentPanel.zoomToEntry(entry);
}
}
}

For anybody searching for an answer, I solved my problem by using invokeLater
repaint();
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
valuePanelScrollPane.getHorizontalScrollBar().setValue(xpos);
valuePanelScrollPane.getVerticalScrollBar().setValue(ypos);
}
});

Related

Placing an invisible button on top of an Image

I'm making a small java game for fun to practice with my GUI programming. I want to have the center of my content pane's borderLayout be an image, and I would like to put "invisible" buttons on top of the image in specific places (to be placed later, I just want to get one working for now). My issue is getting the button to actually be invisible, it seems to leave a white square where it is now. I looked around but the only things that seem to be suggested were the .setOpaque, .setContentAreaFilled, and .setBorderPainted. (game is space related, explains the names)
galaxyButton1 = new JButton();
galaxyButton1.setFont(starSystem);
galaxyButton1.setBorder(BorderFactory.createEmptyBorder(25,25,25,25) );
galaxyButton1.setOpaque(false);
galaxyButton1.setContentAreaFilled(false);
galaxyButton1.setBorderPainted(false);
Color invis = new Color(Color.TRANSLUCENT);
galaxyButton1.setForeground(invis);
galaxyButton1.setBackground(invis);
galaxyButton1.addActionListener( new ButtonHandler() );
JPanel centerPanel = new JPanel();
centerPanel.setLayout(new BorderLayout());
JPanel buttons = new JPanel();
buttons.setLayout(new GridLayout( 1,0,5,5 ) );
buttons.setOpaque(false);
buttons.add(galaxyButton1);
centerPanel.add(buttons,BorderLayout.CENTER);
centerImg.setLayout(new GridBagLayout());
centerImg.add(centerPanel);
contentPane.add(centerImg, BorderLayout.CENTER);
Here is a code outline of how to make your own button. You will need to set up a the actual mouse in some other class.
public class InvisibleButton implements MouseListener{
private final Rectangle rectangle;
public InvisibleButton(int x, int y, int width, int height){
rectangle = new Rectangle(x,y,width,height);
}
#Override
public void mouseClicked(MouseEvent e) {
int x = 0; // Set this to Mouse X
int y = 0; // Set this to Mouse Y
if(rectangle.contains(x,y)){
//Set Something to True or do action here
}
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
}

How to use the resetButton to reset parameters

Im writing a program that deals with creating a house and rearranging the neighbors (similar to game of life)
It is written in Java and it as a Reset button to restart with the current parameters.
The problem is that when i click the resetButton the whole window closes.
You can find the code at the and of the question.
These are other involved fields, so you might be able to understand the code better:
TextField to set the size of the city (number of squares on an edge).
TextFild to set the maximum percentage of neighbors not-like-you that a household will tolerate before they move. I.e., if the number is set to 70, a red house hold with 6 blue neighbors will move; a red household with 5 blue neighbors is ok according to this.
TextField to set a minimum percentage of neighbors not-like-you that a household will tolerate before they move. Make this greater than 0 for a person who LIKES diversity TextField to set the percentage of red households. Note: you can do this with probability. You do not need to have this exact percentage of red households.
TextField to set the percentage of blue households. White will be what is left over. Single step button gives every household one chance to move. You do not need to be careful about that fact that, as you progress through the city, a person early in the move chances may move to a place that later gets a chance (i.e., some households really get two or more turns).
Go button, to start and stop the program from doing steps on its own.
So my questions is how do i let those field and buttons restart with the current parameters ( restart it with what i have originally after playing a few times)
As you can see I added the resetButton to do its job but somehow it is not.
Ok it is like I run the program I have a set of numbers for red and blue as you can see in my, then when I run it it starts with those numbers. And so I keep on changing as i play, and then there is a reset button that lets me reset what I did and should bring back what it originally started with:
public final static int size = 5 and so on
This is my main program
public class Ghetto extends JFrame implements ActionListener, MouseListener,MouseMotionListener
{
protected Grids theGrid;
JButton resetButton;
javax.swing.Timer timer; // generates ticks that drive the animation
public final static int SIZE = 5;
public final static int BLUE = 10;
public final static int RED = 8;
public final static int DIVERSITY_PERCENTAGE = 70;
public static void main(String[] args)
{
new Ghetto();
}
public Ghetto() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
addMouseListener(this);
addMouseMotionListener(this);
setLayout(new FlowLayout());
theGrid = new Grids(SIZE, BLUE, RED, DIVERSITY_PERCENTAGE);
add(theGrid);
resetButton = new JButton("Reset");
add(resetButton);
resetButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
resetWithCurrent();
}
});
setSize(new Dimension(550, 600));
setVisible(true);
}
public void resetWithCurrent()
{
//this.dispose();
}
#Override
public void actionPerformed(ActionEvent e)
{
timer.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
performStep();
//if (e.getSource() == resetButton )
//{
//resetWithCurrent();
//}
}
});
}
}
Also I added this part
public class Observable{
int state = 0;
int additionalState = 0;
public final void updateState(int increment)
{
doUpdateState(increment);
notifyObservers();
}
private void notifyObservers() {
// TODO Auto-generated method stub
}
public void doUpdateState(int increment)
{
state = state + increment;
}
}
public class ConcreteObservable extends Observable{
public void doUpdateState(int increment){
super.doUpdateState(increment); // the observers are notified
additionalState = additionalState + increment; // the state is changed after the notifiers are updated
}
}
But it didnt make any difference.

Scroll Lock with JTable and JScroll Panes

I have a JTable which is created with the use of an EventTableModel and is in a JScrollPane. The EventTableModel takes live updates from an eventList and displays the result in the table. As new results come into the table and the new piece of information is displayed at the top of the table.
However, what I want to be able to do is freeze the table to show what is currently displayed when I press a button called 'Lock Table'. This button should have the same effect as the eclipse console 'Scroll Lock', therefore as new items appear the current items should remain on the screen and not be pushed off as new items appear. But new items should still be added just not automatically scrolled to.
Does anyone know how I can try achieve this functionality. So that as update come in, the data that is on the table is not forced off screen, therefore focuses remain on the current data when the check box is pressed.
Thanks for any help.
Michael
Basic procedure (for inserting above the current display area)
install a TableModelListener on the table's model
on enable lock: note the number of rows below the current visible rectangle
on receiving inserts while locked, scroll so that the number of rows below are kept constant
some working code (using JXTable, as it has convenience method for scrolling, for a core table simply do the calculations yourself :-)
public static class ScrollLock {
private JXTable table;
private boolean blocked;
private int rowsBelow;
public ScrollLock(JXTable table) {
this.table = table;
table.getModel().addTableModelListener(getTableModelListener());
}
private TableModelListener getTableModelListener() {
TableModelListener l = new TableModelListener() {
#Override
public void tableChanged(TableModelEvent e) {
if (!blocked) return;
if (e.getType() == TableModelEvent.INSERT) {
updateInsert(e.getFirstRow(), e.getLastRow());
}
}
};
return l;
}
protected void updateInsert(int firstRow, int lastRow) {
// PENDING: assumption is that insert always above
// need additional logic for other cases
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Rectangle r = table.getVisibleRect();
int row =table.rowAtPoint(new Point(0, r.y + r.height));
int lastVisible = table.getRowCount() - rowsBelow;
table.scrollRowToVisible(lastVisible);
}
});
}
public void block() {
Rectangle viewRect = table.getVisibleRect();
int lastVisibleRow = table.rowAtPoint(new Point(0, viewRect.y + viewRect.height));
rowsBelow = table.getRowCount() - lastVisibleRow;
blocked = true;
}
public void unblock() {
blocked = false;
rowsBelow = -1;
}
}

Maintaing JTextArea scroll position

I have a JScrollPane with a JTextArea set as its view port.
I update the (multi line) text shown on the JTextArea continously about once a second. Each time the text updates, JScrollPane goes all the way to the bottom of the text.
Instead, I'd like to figure out the line number that is currently shown as the first line in the original text, and have that line be the first line shown when the text has been updated (or if the new text doesn't have that many lines, then scroll all the way to the bottom).
My first attempt of doing this was to get the current caret position, figure the line based on that, and then set the text area to show that line:
int currentPos = textArea.getCaretPosition();
int currentLine = 0;
try {
for(int i = 0; i < textArea.getLineCount(); i++) {
if((currentPos >= textArea.getLineStartOffset(i)) && (currentPos < gameStateTextArea.getLineEndOffset(i))) {
currentLine = i;
break;
}
}
} catch(Exception e) { }
textArea.setText(text);
int newLine = Math.min(currentLine, textArea.getLineCount());
int newOffset = 0;
try {
newOffset = textArea.getLineStartOffset(newLine);
} catch(Exception e) { }
textArea.setCaretPosition(newOffset);
This was almost acceptable for my needs, but requires the user to click inside the text area to change the caret position, so that the scrolling will maintain state (which isn't nice).
How would I do this using the (vertical) scroll position instead ?
I encountered the same problem and found that this answer includes a nice solution that works in this case:
DefaultCaret caret = (DefaultCaret) jTextArea.getCaret();
caret.setUpdatePolicy(DefaultCaret.NEVER_UPDATE);
This is pieced together, untested, from the API documentation:
use getViewport() on your JScrollPane to get a hold of the viewport.
use Viewport.getViewPosition() to get the top-left coordinates. These are absolute, not a percentage of scrolled text.
use Viewport.addChangeListener() to be notified when the top-left position changes (among other things). You may want to create a mechanism to distinguish user changes from changes your program makes, of course.
use Viewport.setViewPosition() to set the top-left position to where it was before the disturbance.
Update:
To stop JTextArea from scrolling, you may want to override its getScrollableTracksViewport{Height|Width}() methods to return false.
Update 2:
The following code does what you want. It's amazing how much trouble I had to go to to get it to work:
apparently the setViewPosition has to be postponed using invokeLater because if it's done too early the text update will come after it and nullify its effect.
also, for some weird reason perhaps having to do with concurrency, I had to pass the correct value to my Runnable class in its constructor. I had been using the "global" instance of orig and that kept setting my position to 0,0.
public class Sami extends JFrame implements ActionListener {
public static void main(String[] args) {
(new Sami()).setVisible(true);
}
private JTextArea textArea;
private JScrollPane scrollPane;
private JButton moreTextButton = new JButton("More text!");
private StringBuffer text = new StringBuffer("0 Silly random text.\n");
private Point orig = new Point(0, 0);
public Sami() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
getContentPane().setLayout(new BorderLayout());
this.textArea = new JTextArea() {
#Override
public boolean getScrollableTracksViewportHeight() {
return false;
}
#Override
public boolean getScrollableTracksViewportWidth() {
return false;
}
};
this.scrollPane = new JScrollPane(this.textArea);
getContentPane().add(this.scrollPane, BorderLayout.CENTER);
this.moreTextButton.addActionListener(this);
getContentPane().add(this.moreTextButton, BorderLayout.SOUTH);
setSize(400, 300);
}
#Override
public void actionPerformed(ActionEvent arg0) {
int lineCount = this.text.toString().split("[\\r\\n]").length;
this.text.append(lineCount + "The quick brown fox jumped over the lazy dog.\n");
Point orig = this.scrollPane.getViewport().getViewPosition();
// System.out.println("Orig: " + orig);
this.textArea.setText(text.toString());
SwingUtilities.invokeLater(new LaterUpdater(orig));
}
class LaterUpdater implements Runnable {
private Point o;
public LaterUpdater(Point o) {
this.o = o;
}
public void run() {
// System.out.println("Set to: " + o);
Sami.this.scrollPane.getViewport().setViewPosition(o);
}
}
}

unexplained spacing in horizontal panel in GWT

i am adding widgets to a horizontal panel, and i want them to be all once next to the other on the left corner.
even though i have set the spacing=0 and alignment= left the widgets still have space between them. they are spread evenly in the panel.
please see the code here for the widget C'tor and the function that adds a new tab (toggle button)
tabsPanel is a horizontalPanel, that you can see is aligned to left/right according to the locale
any advise would be appreciated
thanks....
public TabsWidgetManager(int width, int height, int tabs_shift_direction){
DecoratorPanel decorContent = new DecoratorPanel();
DecoratorPanel decorTitle = new DecoratorPanel();
widgetPanel.setSize(Integer.toString(width), Integer.toString(height));
tabsPanel.setSize(Integer.toString(UIConst.USER_CONTENT_WIDTH), Integer.toString(UIConst.TW_DEFAULT_TAB_HEIGHT));
tabsPanel.setSpacing(0);
if (tabs_shift_direction==1)
tabsPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_LEFT);
else
tabsPanel.setHorizontalAlignment(HorizontalPanel.ALIGN_RIGHT);
decorTitle.add(tabsPanel);
contentPanel.setSize(Integer.toString(UIConst.USER_CONTENT_WIDTH), Integer.toString(UIConst.USER_CONTENT_MINUS_TABS_HEIGHT));
decorContent.add(contentPanel);
widgetPanel.add(decorTitle, 0, 0);
widgetPanel.add(decorContent, 0, UIConst.TW_DEFAULT_TAB_HEIGHT+15);
initWidget(widgetPanel);
}
public void addTab(String title, Widget widget){
widget.setVisible(false);
ToggleButton tab = new ToggleButton(title);
tabsList.add(tab);
tab.setSize(Integer.toString(UIConst.TW_TAB_DEFAULT_WIDTH), Integer.toString(UIConst.TW_TAB_DEFAULT_HEIGHT));
tab.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
handleTabClick((ToggleButton)event.getSource());
}
});
//adding to the map
tabToWidget.put(tab, widget);
// adding to the tabs bar
tabsPanel.add(tab);
//adding to the content
contentPanel.add(widget);
}
I assume contentPanel is your HorizontalPanel. HorizontalPanel will generate an html table. You're setting the width of the panel. If the width is wider than the sum of all widths of your sub widgets the table will spread the widgets evenly. Remove the setWidth() call.

Categories

Resources