Java SWT ScrolledComposite Performance with many Composites inside - java

i have a problem with my Java SWT ScrolledComposite:
In a (centered) ScrolledComposite are displayed many pictures previews (see picture images). If the images in the ScrolledComposite are loaded, it takes a long time. After that the previews lags on scolling.
Quick: it has a really poor performance.
So my idea: i calculate the scoll bar and load only the displayed pictures. If the user scoll down, it will be load the other pictures.
My (test) code:
Composite center = new Composite(form, SWT.NONE);
center.setLayout(new FillLayout());
ScrolledComposite centerScrolledComposite = new ScrolledComposite(center, SWT.V_SCROLL | SWT.BORDER);
Display display = getDisplay();
Image image1 = display.getSystemImage(SWT.ICON_WORKING);
Image image2 = display.getSystemImage(SWT.ICON_QUESTION);
Image image3 = display.getSystemImage(SWT.ICON_ERROR);
Composite wrappedScrolledComposite = new Composite(centerScrolledComposite, SWT.NONE);
for (int i = 0; i <= 5000; i++)
{
Label label = new Label(wrappedScrolledComposite, SWT.NONE);
if (i % 3 == 0)
label.setImage(image1);
if (i % 3 == 1)
label.setImage(image2);
if (i % 3 == 2)
label.setImage(image3);
}
RowLayout layout = new RowLayout(SWT.HORIZONTAL);
layout.wrap = true;
wrappedScrolledComposite.setLayout(layout);
centerScrolledComposite.setContent(wrappedScrolledComposite);
centerScrolledComposite.setExpandHorizontal(true);
centerScrolledComposite.setExpandVertical(true);
centerScrolledComposite.addControlListener(new ControlAdapter() {
public void controlResized(ControlEvent e) {
Rectangle r = centerScrolledComposite.getClientArea();
centerScrolledComposite.setMinSize(wrappedScrolledComposite.computeSize(r.width, SWT.DEFAULT));
}
});
But.. i don't know if it's possible. Have anybody a similar problem? Thanks
PS: i need a view very simular to the Windows 10 File Browser with "Big Icos" as preview.... and the same performance :)

Replace all those labels with a Table or TableViewer - just a single control.
Since tables can scroll themselves you also don't need the ScrolledComposite.
Table table = new Table(form, SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
for (int i = 0; i <= 5000; i++)
{
TableItem label = new TableItem(table, SWT.NONE);
if (i % 3 == 0)
label.setImage(image1);
if (i % 3 == 1)
label.setImage(image2);
if (i % 3 == 2)
label.setImage(image3);
}
For even better performance you can make the table "Virtual" and populate the contents as required:
Table table = new Table(composite, SWT.VIRTUAL | SWT.V_SCROLL | SWT.H_SCROLL | SWT.SINGLE);
table.setItemCount(5000);
table.addListener(SWT.SetData, event -> {
TableItem label = (TableItem)event.item;
int i = table.indexOf(label);
if (i % 3 == 0)
label.setImage(image1);
if (i % 3 == 1)
label.setImage(image2);
if (i % 3 == 2)
label.setImage(image3);
});

Related

Java SWT: Displaying a Grid over an image

I need to do the following using Java SWT:
Display a list of Pixels as an Image
Allow the user to select a subset of Pixels
Display a Grid over the image as a guide for the user. The Image still needs to handle mouse events
1) and 2) are straightforward, however I don't know how to achieve 3).
Reading up on SWT, I do not see a way to put a Transparent Overlay over an image. Is this possible? Is there another method?
Just draw the grid on top of the Image. Set GG#setAlpha(int) to a low value to make the lines transparent. This will not interfere with mouse events:
public static void main(String[] args)
{
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setText("Stackoverflow");
shell.setLayout(new FillLayout());
Image image = new Image(display, "baz.png");
Label label = new Label(shell, SWT.NONE);
label.addListener(SWT.Paint, new Listener()
{
#Override
public void handleEvent(Event event)
{
GC gc = event.gc;
gc.drawImage(image, 0, 0);
gc.setAlpha(30);
int interval = image.getBounds().height;
for (int i = 0; i < 10; i++)
{
int y = (int) Math.floor(i * (interval / 10.0));
gc.drawLine(0, y, image.getBounds().width, y);
}
interval = image.getBounds().width;
for (int i = 0; i < 10; i++)
{
int x = (int) Math.floor(i * (interval / 10.0));
gc.drawLine(x, 0, x, image.getBounds().width);
}
gc.setAlpha(255);
}
});
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
image.dispose();
}
Looks like this:

Sash becomes lost after resize

I have an application that I have created a sash for, so that users can resize the Styled Text objects as they choose. I have a bug though that if the application is maximized, the sash is moved very low on the application, and then the user un-maximizes the application, the sash is then still beyond the bounds of the screen.
How can I make it so that when the user changes the application from maximized, to a smaller size, the sash automatically moves within the bounds of the application size; as in the first picture?
Also, is there a better (more dynamic) way to control the position limits for the sash?
separator.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent event) {
double height = shlPstmKnowledgeCatalogue.getBounds().height;
double qpBtnHeight = btnQuickParts.getBounds().height;
double workLblHeight = lblWorkInstructions.getBounds().height;
if (!shlPstmKnowledgeCatalogue.getMaximized()) {
if (event.y < workLblHeight + 30) {
event.y = (int) workLblHeight + 30;
}
else if (event.y > height - qpBtnHeight - 90) {
event.y = (int) (height - qpBtnHeight - 90);
}
}
else {
if (event.y < workLblHeight + 30) {
event.y = (int) (workLblHeight + 30);
}
else if (event.y > height - qpBtnHeight - 90) {
event.y = (int) (height - qpBtnHeight - 90);
}
}
separator.setBounds(event.x, event.y, event.width, event.height);
FormData formData = new FormData();
formData.top = new FormAttachment(0, event.y);
formData.left = new FormAttachment(lblScript, 6);
formData.right = new FormAttachment(script, 0, SWT.RIGHT);
formData.height = 5;
separator.setLayoutData(formData);
/*
* Used to move the script label with the movement of the script
* text box. Otherwise, the label gets lost behind the text boxes.
*/
FormData lblScriptData = new FormData();
lblScriptData.top = new FormAttachment(0, event.y - 5);
lblScriptData.bottom = new FormAttachment(0, event.y + 12);
lblScriptData.left = new FormAttachment (workInstructions,2, SWT.LEFT);
lblScript.setLayoutData(lblScriptData);
shlPstmKnowledgeCatalogue.layout(true);
}
});
/*
* Attaches the Work Instuction text box to the sash for dynamic resizing
* The resizing is done in relation to the Script text box
*/
FormData workInstForm = new FormData();
workInstForm.left = new FormAttachment(0, 194);
workInstForm.right = new FormAttachment(100, -6);
workInstForm.bottom = new FormAttachment(separator, -15);
workInstForm.top = new FormAttachment(lblWorkInstructions, 7);
workInstructions.setLayoutData(workInstForm);
formToolkit.adapt(workInstructions, true, true);
/*
* Attaches the Script text box to the sash for dynamic resizing
* The resizing is done in relation to the work instruction text box
*/
FormData scriptForm = new FormData();
scriptForm.top = new FormAttachment(separator, 15);
scriptForm.right = new FormAttachment(workInstructions, 0, SWT.RIGHT);
scriptForm.left = new FormAttachment(workInstructions, 0, SWT.LEFT);
scriptForm.bottom = new FormAttachment(btnQuickParts, -6);
script.setLayoutData(scriptForm);
formToolkit.adapt(script, true, true);
I ended up writing a separate method to constantly check the position of the sash. Maybe it is not the most efficient way of doing it, but it does work!
/**
* Check the sash position to verify that it is within the bounds of the shell.
* This is primarily used when the user maxmimizes the application, moves the
* sash, then minimizes the application.
*/
private void checkSashPosition() {
if (!shlPstmKnowledgeCatalogue.isDisposed() && separator.getBounds().y > shlPstmKnowledgeCatalogue.getBounds().height) {
separator.setLocation(235, shlPstmKnowledgeCatalogue.getBounds().height - 248);
workInstructions.setSize(455, 100);
script.setSize(455, 130);
script.setLocation(194, 167);
lblScript.setLocation(194, 143);
}
}
The method is called inside of a while-loop, and the objects that are relative to the sash are also adjusted.

Buttons in gameplay not displaying correct message

I am coding a minesweeper game. The buttons should display the numbers 0,1 or 2 when clicked to display how many mines are adjacent. I recently changed the layout to gridlayout and since then all I get when I click on a button is '...' with the exception of the discovery of a mine which changes the button to the bomb gif.
Can anyone tell me how to get the numbers back on click?
Here is the code to create the button Array:
JPanel gridPanel = new JPanel(new GridLayout(boardsize, boardsize));
buttons = new JButton[boardsize][boardsize];
mineBoard = new int[9][9];
for (int a = 0; a < boardsize; a++)
for (int b = 0; b < boardsize; b++) {
buttons[a][b] = new JButton("");
gridPanel.add(buttons[a][b]);
buttons[a][b].addMouseListener(new MouseListener(a,b));
setx(a);
sety(b);
settried(false);
setmine(false);
}
contentPane.add(gridPanel, BorderLayout.CENTER);
When the user clicks a button:
// This method takes in an x and y value and defines what should happen when the user clicks there.
public void click(int row, int col) {
if(mineBoard[row][col] == Mine) {
buttons[row][col].setIcon( new ImageIcon( "images/bomb.gif" ) );
lose();
} else {
score += 1;
updatescore();
buttons[row][col].setText("" + numAdjMines(mineBoard, row, col));
mineBoard[row][col] = UncoveredEmpty;
buttons[row][col].setText(Character.toString(getUserChar(mineBoard[row][col])));
if(numAdjMines(mineBoard, row, col) == Empty) {
for(int dr = -1; dr <= 1; dr ++) {
for(int dc = -1; dc <= 1; dc++) {
if(row+dr >= 1 && row+dr < 10 &&
col+dc >= 1 && col+dc < 10) {
if(mineBoard[row+dr][col+dc] == Empty) {
click(row+dr,col+dc);
}
}
}
}
}
}
}
MouseLister Class:
//ACTION WHEN USER CLICKS ON A BUTTON
private class MouseListener extends MouseAdapter {
private int x = 0;
private int y = 0;
public MouseListener(int row, int col) {
this.x = row;
int i = 0;
this.y = col;
}
public void mouseClicked(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1) {
if((mineBoard[x][y] == Empty) && (Game.this.gamegoing == true)) {
Game.this.click(x, y);
} else if(mineBoard[x][y] == Mine) {
buttons[x][y].setIcon( new ImageIcon( "images/bomb.gif" ) );
Game.this.lose();
}} else if(e.getButton() == MouseEvent.BUTTON3) {
Game.this.buttons[x][y].setText("F");
}
}
}
You can try to add a few spaces to your default text of the button, something like that:
buttons[a][b] = new JButton(" ");
Or you can try to pack your window after you set the text on the button.
When a JButton only dislays dots it means that there is not enough space to write down the tekst.
So the solution would be to tell Gridlayout to make the buttons larger.
I'd recomend that you find test your game with a very big windows and then try to find the point where your windows is big enough that Gridlayout en JButton agree that the tekst can be placed.
Then you should see how big your buttons are at that point and then either:
Tell GridLayout how big your buttons should be (by calling setPrefferedSize() that should work)
Or use another layout manager that does not give you this problem.
P.S. A your bomb picture appear because picures are handled in a different way. They are always displayed I belive.
I hope this solves it for you.

Equal width of the first column between multiple composites

So the case is that I have a SWT composite A which aggregates composite B and C.
The content of B and C consists of multiple rows that consists of Label and Text.
Within composite B or C rows are aligned properly ( you can draw a straight vertical line in the place where label border ends and text starts). But if you compare B and C then C content looks like it is indented against B.
For example:
Does anyone have an idea how to achieve it ?
The only way I can think of to align the first column of each Composite is to set the GridData#widthHint to the same value. This value would have to be the maximal width of any of the elements in the first column.
I gave it a try and came up with this solution (it's not optimized and consequently might not be the most efficient way to do it):
private static Random random = new Random(System.currentTimeMillis());
public static void main(String[] args)
{
Display display = new Display();
Shell shell = new Shell(display);
shell.setText("StackOverflow");
shell.setLayout(new GridLayout(1, false));
Composite first = createComposite(shell);
Composite second = createComposite(shell);
synchronizeFirstColumn(2, first, second);
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
{
display.sleep();
}
}
display.dispose();
}
private static Composite createComposite(Shell shell)
{
Composite comp = new Composite(shell, SWT.BORDER);
comp.setLayout(new GridLayout(2, false));
comp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
for (int i = 0; i < 3; i++)
{
String content = UUID.randomUUID().toString();
content = content.substring(0, Math.round(random.nextFloat() * content.length()));
Label label = new Label(comp, SWT.RIGHT);
label.setText(content);
label.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false));
Text text = new Text(comp, SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false));
}
return comp;
}
private static void synchronizeFirstColumn(int nrOfColumns, Composite... comps)
{
if (comps == null || comps.length == 0)
return;
int maxWidth = 0;
for (Composite comp : comps)
{
Control[] controls = comp.getChildren();
for (int i = 0; i < controls.length; i += nrOfColumns)
{
int width = controls[i].computeSize(SWT.DEFAULT, SWT.DEFAULT).x;
if (width > maxWidth)
maxWidth = width;
}
}
for (Composite comp : comps)
{
Control[] controls = comp.getChildren();
for (int i = 0; i < controls.length; i += nrOfColumns)
{
Object data = controls[i].getLayoutData();
if(data instanceof GridData)
{
GridData grid = (GridData) data;
grid.widthHint = maxWidth;
}
}
}
}
Looks like this:

Nebula Grid - Select individual cells (CellSelectionEnabled)

I am using Eclipse.org's Nebula Grid and want to access an individual cell. Not an individual GridItem, which can be accomplished by grid.select(...) but a cell. So lets say i have a grid like this:
final Grid grid = new Grid(shell,SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
grid.setCellSelectionEnabled(true);
grid.setHeaderVisible(true);
GridColumn column = new GridColumn(grid, SWT.None);
column.setWidth(80);
GridColumn column2 = new GridColumn(grid, SWT.None);
column2.setWidth(80);
for(int i = 0; i<50; i++)
{
GridItem item = new GridItem(grid, SWT.None);
item.setText("Item" + i);
}
Like i said, grid.select selects the whole row, which is not what i want. I also tried grid.selectCell(...), but for some reason that wont work either. The coordinates used are with a high likeliness correct:
Button btn = new Button(shell, SWT.PUSH);
btn.setText("test");
btn.addSelectionListener(new SelectionAdapter(){
public void widgetSelected(SelectionEvent e){
Point pt = new Point(400,300);
grid.selectCell(pt);
}
});
Any Ideas?
For a Grid, the Point co-ordinates represent the intersecting column and the row item. i.e, x co-ordinate represents the index of the column and y co-ord is the row item index.
Button btn = new Button (shell, SWT.PUSH);
btn.setText ("test");
btn.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
// Here the x co-ordinate of the Point represents the column
// index and y co-ordinate stands for the row index.
// i.e, x = indexOf(focusColumn); and y = indexOf(focusItem);
Point focusCell = grid.getFocusCell();
grid.selectCell(focusCell);
// eg., selects the intersecting cell of the first column(index = 0)
// in the second row item(rowindex = 1).
Point pt = new Point(0, 1);
grid.selectCell(pt);
}
});

Categories