SWT: Layout broken when refilling a shell with components - java

I want to periodically clear everything from a shell and recreate the components in it. This is a minimal Test case of what I am trying to do. At first, the labels are put side by side as the layout dictates, but when the updateThread triggers after five seconds, the newly created labels are all put over each other, completely ignoring the layout.
package testpkg;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.*;
public class Test {
public static void main(String[] args){
Display display = new Display();
Shell testShell = new Shell(display);
FillLayout testLayout = new FillLayout();
testShell.setLayout(testLayout);
for(int i = 1; i<20; i++){
Label label = new Label(testShell, SWT.BORDER);
label.setText("HELLO"+i);
label.pack();
}
testShell.open();
Thread updateThread = new Thread(new Runnable(){
public void run(){
try {Thread.sleep(5000);} catch (Exception e){};
display.getDefault().syncExec(new Runnable(){
public void run(){
for(Control control : testShell.getChildren()){
control.dispose();
}
for(int i = 1; i<20; i++){
Label label = new Label(testShell, SWT.BORDER);
label.setText("HELLO"+i);
label.pack();
}
}
});
}
});
updateThread.start();
while (!testShell.isDisposed()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
I want to update the (variable amount of) labels periodically but the layout breaks completely when I do it this way.

you can put this line after the for loop:
testShell.layout(true);
so the final run() method will look like:
public void run() {
for (Control control : testShell.getChildren()) {
control.dispose();
}
for (int i = 1; i < 20; i++) {
Label label = new Label(testShell, SWT.BORDER);
label.setText("HELLO"+i);
label.pack();
}
testShell.layout(true);
}

I noticed that resizing the window fixed everything. So if I make the window larger and then smaller for a split second, it works.

Related

refresh gui in swt button listener

I have following class.
Why the btnDecorate is allways enabled? I wanted to disable the button when the loop is under processing.
Why text.redraw() works only in the end of loop? I wanted to see the box sequently on every character.
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.*;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.*;
import org.eclipse.swt.widgets.*;
public class SampleRefreshStyledText {
public static void main(String[] args) {
final Display display = new Display();
Shell shell = new Shell(display);
shell.setLayout(new FillLayout(SWT.VERTICAL));
final Button btnDecorate = new Button(shell, SWT.NONE);
btnDecorate.setText("Decorate");
final StyledText text = new StyledText(shell, SWT.NONE);
text.setText("ABCDEFGHIJKLMNOPRQ\n1234567890");
btnDecorate.addSelectionListener(new SelectionListener() {
#Override
public void widgetSelected(SelectionEvent event) {
btnDecorate.setEnabled(false);
for (int i = 0; i < text.getText().length(); i++) {
StyleRange styleRange = new StyleRange();
styleRange.start = i;
styleRange.length = 1;
styleRange.borderColor = display.getSystemColor(SWT.COLOR_RED);
styleRange.borderStyle = SWT.BORDER_SOLID;
styleRange.background = display.getSystemColor(SWT.COLOR_GRAY);
text.setStyleRange(null);
text.setStyleRange(styleRange);
text.redraw();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
btnDecorate.setEnabled(true);
}
#Override
public void widgetDefaultSelected(SelectionEvent arg0) {}
});
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) display.sleep();
}
display.dispose();
}
}
You can't write loops like this with SWT.
All UI operations occur on the single UI thread. Calling Thread.sleep puts the UI thread to sleep and nothing at all will happen.
The redraw call only requests that the text is redrawn, it will not actually happen until the next time the display.readAndDispatch() is run, so doing this repeatedly in a loop doesn't work.
What you have to do is run the first step of your loop once. You must then arrange to run the next step 500ms later without blocking the thread. You can do this using the Display.timerExec method to request that code is run at a later time:
display.timerExec(500, runnable);
where runnable is a class implementing Runnable that does the next step. At the end of this code you call timerExec again until you have worked your way through all the steps.

Record SWT MouseEnter/Exit events on a Composite with a FillLayout

I have a Composite on which I'd like to track SWT.MouseEnter and SWT.MouseExit events. The Composite, however, has another Composite inside it which consumes the entirety of the region, due to a FillLayout.
Some sample code to illustrate what it is I'm doing:
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
public class EventListenerTest {
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
// Set up the outer Composite
Composite outerComp = new Composite(shell, SWT.BORDER);
FillLayout fl = new FillLayout();
// fl.marginHeight = 15;
// fl.marginWidth = 15;
outerComp.setLayout(fl);
// Set up a nested Composite
Composite innerComp = new Composite(outerComp, SWT.BORDER);
// Set a listener on the outer Composite for a MouseEnter event
outerComp.addListener(SWT.MouseEnter, new Listener() {
#Override
public void handleEvent(Event e) {
System.out.println("Mouse ENTER event");
}
});
// Similarly, for a MouseExit event
outerComp.addListener(SWT.MouseExit, new Listener() {
#Override
public void handleEvent(Event e) {
Composite comp = (Composite) e.widget;
// Don't report if positioned over a child control
for (Control child : comp.getChildren()) {
if (!child.getBounds().contains(new Point(e.x, e.y)))
System.out.println("Mouse EXIT event");
}
}
});
outerComp.pack();
outerComp.layout();
shell.pack();
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
return;
}
}
Unfortunately, due to the fact that innerComp fills the entirety of its parent, outerComp never does record the mouse enter/exit events unless I expose a little bit of its "area".
I'm able to expose a little bit of outerComp by creating some margins on its FillLayout (commented out lines 21-22), but this is really not ideal. For aesthetic reasons, I can't have huge margins on outerComp, and reducing the margin size to 1 doesn't consistently detect the mouse events if I'm moving my mouse over the composite quickly (I have to move my mouse very slowly over the 1px margin for it to trigger).
From a design perspective on my project, I'm not actually supposed to know what outerComp contains (or how deep the controls nest), so setting the event listeners on its children also isn't ideal.
Is there any way I can still track these mouse events on the outerComp if it has a FillLayout consuming all of its area?
What you can do is add a filter to the Display and in the handler, check if the Widget that is the source of the Event is a child of your Composite.
This should give you an idea of how to do it:
public static void main(String[] args)
{
Display display = new Display();
Shell shell = new Shell();
shell.setText("StackOverflow");
shell.setLayout(new GridLayout(2, true));
final Composite left = new Composite(shell, SWT.NONE);
Composite right = new Composite(shell, SWT.NONE);
left.setLayout(new GridLayout(3, true));
right.setLayout(new GridLayout(3, true));
for (int i = 0; i < 6; i++)
{
new Label(left, SWT.NONE).setText("label-" + i);
new Label(right, SWT.NONE).setText("label-" + i);
}
display.addFilter(SWT.MouseMove, new Listener()
{
#Override
public void handleEvent(Event e)
{
if (isChildOrSelf(e.widget, left))
System.out.println(e);
}
});
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
{
display.sleep();
}
}
display.dispose();
}
private static boolean isChildOrSelf(Widget child, Composite parent)
{
if(child == parent)
return true;
for (Control c : parent.getChildren())
{
if (c instanceof Composite)
{
boolean result = isChildOrSelf(child, (Composite)c);
if (result)
return true;
}
else if (c == child)
return true;
}
return false;
}

SWT StackLayout topControl apparently not working

I am testing a simple SWT StackLayout example to learn how it works but things are not working as I expected.
I created a StackLayout with two buttons on them, both set to cycle the top control between the two of them five times when they are selected, with a 2-second pause every time the top control changes. However, when I run the problem I do not see anything happen.
Any ideas on what I am missing?
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StackLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
public class MyApp {
protected Shell shlMyFirstSwt;
Button btnOne;
Button btnTwo;
/**
* Launch the application.
* #param args
*/
public static void main(String[] args) {
try {
MyApp window = new MyApp();
window.open();
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Open the window.
*/
public void open() {
Display display = Display.getDefault();
createContents();
shlMyFirstSwt.open();
shlMyFirstSwt.layout();
while (!shlMyFirstSwt.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
/**
* Create contents of the window.
* #throws InterruptedException
*/
protected void createContents() {
shlMyFirstSwt = new Shell();
shlMyFirstSwt.setSize(621, 416);
shlMyFirstSwt.setText("My First SWT Application");
StackLayout layout = new StackLayout();
shlMyFirstSwt.setLayout(layout);
Button btnOne = new Button(shlMyFirstSwt, SWT.NONE);
btnOne.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
for (int i = 0; i != 10; i++) {
layout.topControl = i % 2 == 0? btnOne : btnTwo;
shlMyFirstSwt.layout();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
});
btnOne.setText("One");
Button btnTwo = new Button(shlMyFirstSwt, SWT.NONE);
btnTwo.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
for (int i = 0; i != 10; i++) {
layout.topControl = i % 2 == 0? btnOne : btnTwo;
shlMyFirstSwt.layout();
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
}
}
});
btnTwo.setText("Two");
}
}
Elaborating after first answer:
Trying a simpler approach without delaying. Now I modified the event handler to simply have one button switch the top control to be the other button, as shown below. I expected the two buttons to alternate as top control, but instead when I click on the first button, the window turns blank. Any idea why?
Button btnOne = new Button(shlMyFirstSwt, SWT.NONE);
btnOne.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
layout.topControl = btnTwo;
shlMyFirstSwt.layout();
}
});
btnOne.setText("One");
Button btnTwo = new Button(shlMyFirstSwt, SWT.NONE);
btnTwo.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
layout.topControl = btnOne;
shlMyFirstSwt.layout();
}
});
btnTwo.setText("Two");
Your Thread.sleep calls are blocking the user interface thread so the GUI does not get updated. You must never block the user interface thread like this. Calls to methods like layout do not update instantly - they require that display.readAndDispatch runs to dispatch the various updates that are generated.
If you want to delay something use Display.timerExec:
Display.getDefault().timerExec(2000, new Runnable() {
#Override
public void run()
{
... code to be run after the delay
}
});
So you will have to rework your code to use this to do the timed updated.
Figured it out: just the silly mistake of including btnTwo in the first event handler before it was initialized, even though it is used after initialization.

Reorder GridLayout SWT

I Have a GridLayout filled with Composites in a random order. Now I'm sorting the Composites that are filling the GridLayout in a List/Collection and want to order them like the sort result from my List/Collection. I tried to do it by allocating them again to their parent so they are in the right order, but for some reason nothing happened. Then I tried to cache them in a Composite you don't see and then bring them back to the parent with the same method as in the first attempt. No change at all. Anyone has a pointer? I'm ordering by date, just in case /so want to know.
Thats how my grid looks like, now I want to order them like in my arrayList();
The methods you are looking for are Control#moveAbove(Control control) and Control#moveBelow(Control control) to reorder the items:
private static List<Label> labels = new ArrayList<Label>();
private static List<Color> colors = new ArrayList<Color>();
public static void main(String[] args)
{
Display display = new Display();
final Shell shell = new Shell(display);
shell.setText("Stackoverflow");
shell.setLayout(new RowLayout(SWT.VERTICAL));
colors.add(display.getSystemColor(SWT.COLOR_BLUE));
colors.add(display.getSystemColor(SWT.COLOR_CYAN));
colors.add(display.getSystemColor(SWT.COLOR_GREEN));
colors.add(display.getSystemColor(SWT.COLOR_YELLOW));
colors.add(display.getSystemColor(SWT.COLOR_RED));
for (int i = 0; i < 5; i++)
{
Label label = new Label(shell, SWT.BORDER);
label.setText("Button " + i);
label.setBackground(colors.get(i));
labels.add(label);
}
Button sortButton = new Button(shell, SWT.TOGGLE);
sortButton.setText("Sort");
sortButton.addListener(SWT.Selection, new Listener()
{
#Override
public void handleEvent(Event e)
{
Button source = (Button) e.widget;
final boolean asc = source.getSelection();
Label oldFirst = labels.get(0);
Collections.sort(labels, new Comparator<Label>()
{
#Override
public int compare(Label o1, Label o2)
{
int result = o1.getText().compareTo(o2.getText());
if (asc)
result = -result;
return result;
}
});
Label label = labels.get(0);
label.moveAbove(oldFirst);
for (int i = 1; i < labels.size(); i++)
{
labels.get(i).moveBelow(labels.get(i - 1));
}
shell.layout();
}
});
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
After starting:
After pressing the button:
I found the solution. You have to call child.moveAbove(otherChild) or .moveBelow() When you are done with the reordering just call on the parent Composite parent.layout()

SWT/JFace: remove widgets

Group group = new Group(parent, SWT.NONE);
StyledText comment = new StyledText(group, SWT.BORDER_DASH);
This creates a group with a text area inside.
How can I later delete the text (remove it from the screen so that I can replace it with something else)?
Use Widget.dispose.
public class DisposeDemo {
private static void addControls(final Shell shell) {
shell.setLayout(new GridLayout());
Button button = new Button(shell, SWT.PUSH);
button.setText("Click to remove all controls from shell");
button.addSelectionListener(new SelectionListener() {
#Override public void widgetDefaultSelected(SelectionEvent event) {}
#Override public void widgetSelected(SelectionEvent event) {
for (Control kid : shell.getChildren()) {
kid.dispose();
}
}
});
for (int i = 0; i < 5; i++) {
Label label = new Label(shell, SWT.NONE);
label.setText("Hello, World!");
}
shell.pack();
}
public static void main(String[] args) {
Display display = new Display();
Shell shell = new Shell(display);
addControls(shell);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
Another option is to use a StackLayout to switch between underlying controls. This prevents you from running into a "widget is disposed" error.
You have to either call comment.changeParent(newParent) or comment.setVisible(false) to remove/hide it from the Group. I am unsure if comment.changeParent(null) would work but I would give that a try.
We do it this way because SWT uses the Composite Pattern.
group.getChildren()[0].dispose() will remove the first child. You need to find a way to identify the precise child you want to delete. It could be comparing the id. You can do that by using the setData / getData on that control:
For example:
StyledText comment = new StyledText(group, SWT.BORDER_DASH);
comment.setData("ID","commentEditBox");
and then:
for (Control ctrl : group.getChildren()) {
if (control.getData("ID").equals("commentEditBox")) {
ctrl.dispose();
break;
}
}

Categories