Disable close button in java JFace dialog? - java

How can i disable the close button (or make it disappear completely if possible) in a java JFace dialog?

Buttons in a Dialog are created with the method createButton(). To "filter out" the cancel button, you can override it as follows:
protected Button createButton(Composite parent, int id,
String label, boolean defaultButton) {
if (id == IDialogConstants.CANCEL_ID) return null;
return super.createButton(parent, id, label, defaultButton);
}
However, the Dialog's close button (provided by the OS) still works. To disable it, you can override canHandleShellCloseEvent():
protected boolean canHandleShellCloseEvent() {
return false;
}
Here is a complete, minimal example:
package stackoverflow;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogConstants;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class JFaceDialogNoCloseButton {
private static final Display DISPLAY = Display.getDefault();
public static void main(String[] args) {
Shell shell = new Shell(DISPLAY, SWT.CLOSE | SWT.RESIZE);
shell.setSize(200, 100);
shell.setLayout(new FillLayout());
final Dialog dialog = new InputDialog(shell, "Title", "Message",
"initial value", null) {
#Override
protected Button createButton(Composite parent, int id,
String label, boolean defaultButton) {
if (id == IDialogConstants.CANCEL_ID)
return null;
return super.createButton(parent, id, label, defaultButton);
}
#Override
protected boolean canHandleShellCloseEvent() {
return false;
}
};
Button button = new Button(shell, SWT.PUSH);
button.setText("Launch JFace Dialog");
button.addSelectionListener(new SelectionAdapter() {
#Override
public void widgetSelected(SelectionEvent e) {
dialog.open();
}
});
shell.open();
while (!shell.isDisposed()) {
if (!DISPLAY.readAndDispatch()) {
DISPLAY.sleep();
}
}
DISPLAY.dispose();
}
}

To make the X button not visible on a Dialog you have to turn off the SWT.CLOSE style property. Important to note that this has to be done before the dialog is opened, so in the constructor of your dialog would work.
public NoCloseDialog(...){
super(...);
setShellStyle(getShellStyle() & ~SWT.CLOSE);
}
The default shell style on a JFace Window is SWT.SHELL_TRIM which is equal to SWT.CLOSE | SWT.TITLE | SWT.MIN | SWT.MAX | SWT.RESIZE

See here for an example showing how to hide the close button in a Dialog. You simply override following method:
protected void setShellStyle(int arg0){
//Use the following not to show the default close X button in the title bar
super.setShellStyle(SWT.TITLE);
}
Otherwise override close() and return false to prevent closing.
Update: While the above code "solves" the problem at hand, it doesn't explain a lot, and introduces a nasty bug. Please see Goog's answer, for a way better version.

For a dialog extended from org.eclipse.jface.dialogs.Dialog, overrding canHandleShellCloseEvent did not work for me,
Then closing the entire application worked as a good strategy for my situation, because I had to do the same if the user choose to cancel.
I know this is not the exact answer to the question but can be used as a workaround to handle the situation.
under open() or createContents() method,
shell.addListener(SWT.Close, new Listener() {
public void handleEvent(Event event) {
System.exit(0);
}
});

Related

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.

How can i open a shell without making it active?

I need to know if there is any way to open a shell and not make it active, even if i click a control in it. The best way to explain what i need is to show you this little example. I need to keep the first shell active, even if i click the second one, or any widget that it contains.
public class TestMeOut
{
public static void main(final String[] args)
{
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
final Shell shell2 = new Shell(shell);
shell2.setLayout(new GridLayout());
final Button btn = new Button(shell, SWT.PUSH);
final Button btn2 = new Button(shell2, SWT.PUSH);
btn.setText("Test me");
btn2.setText("I steal focus");
btn.addSelectionListener(new SelectionAdapter()
{
#Override
public void widgetSelected(final SelectionEvent e)
{
shell2.setVisible(true);
}
});
btn2.addSelectionListener(new SelectionAdapter()
{
#Override
public void widgetSelected(final SelectionEvent e)
{
shell2.setVisible(false);
}
});
shell.pack();
shell.open();
shell.addShellListener(new ShellListener()
{
public void shellIconified(final ShellEvent e)
{
}
public void shellDeiconified(final ShellEvent e)
{
}
public void shellDeactivated(final ShellEvent e)
{
System.out.println("Deactivated! This isn't supposed to happen.");
}
public void shellClosed(final ShellEvent e)
{
}
public void shellActivated(final ShellEvent e)
{
System.out.println("Activated!");
}
});
shell2.pack();
shell2.open();
shell2.setVisible(false);
while (!shell.isDisposed())
{
if (!display.readAndDispatch())
display.sleep();
}
display.dispose();
}
}
Thank you in advance!
You can change the style of your second Shell and use SWT.APPLICATION_MODAL which will make sure that you cannot interact with the parent shell unless you close the child shell:
final Shell shell2 = new Shell(shell, SWT.SHELL_TRIM | SWT.APPLICATION_MODAL);
The modality of an instance may be specified using style bits. The modality style bits are used to determine whether input is blocked for other shells on the display. The PRIMARY_MODAL style allows an instance to block input to its parent. The APPLICATION_MODAL style allows an instance to block input to every other shell in the display. The SYSTEM_MODAL style allows an instance to block input to all shells, including shells belonging to different applications.
Even SWT.PRIMARY_MODAL would suffice in this case.
UPDATE
If on the other hand you don't want the parent to loose focus when the child is clicked, just disable the child:
shell2.setEnabled(false);

How to auto click OK on JOptionPane when testing?

In my program I am testing a button which when clicked throws up a JOptionPane window in the application.
However when this window pops up it expects the user to click OK which means that the test can't continue unless the window is manually closed down.
I want to be able to close this window programmatically when it appears within my test or prevent these JOptionPane windows from popping up at all for this test.
What I typically do in such case is to make sure the UI does not appear in my test. Just to illustrate this with some code. Let's say the dialog is triggered somewhere in an Action
public class ActionWithUI extends AbstractAction{
#Override
public void actionPerformed( ActionEvent e ){
//... do some stuff
int result = JOptionPane.show... //show the dialog
//do some other stuff based on the result of the JOptionPane
}
}
I would then refactor my code so that the showing of the JOptionPane is in its own separate method.
public class ActionWithUI extends AbstractAction{
#Override
public void actionPerformed( ActionEvent e ){
//... do some stuff
int result = askUserForInput();
//do some other stuff based on the result of the JOptionPane
}
int askUserForInput(){
return JOptionPane.show...
}
}
Now in my test, I can test a custom version of that action where I override the askUserForInput method and just return the "OK" (or "Cancel", ... ) option. That will avoid any clever tricks in my test and keeps my code readable.
Another approach I sometimes use is not calling JOptionPane.show directly, but rather make sure my class accepts in its constructor a delegate class for showing such dialogs. In my test, I can then insert a mock instead of the real class, and intercept the call to the UI.
Of course, both of these approaches do not actually test whether the JOptionPane is correctly shown. But seeing as that is a JDK method, I do not really feel the need to test this. And it is not like I am bypassing some business logic. I just got rid of a JOptionPane.show call.
If none of these are an option, I normally use a listener attached to the DefaultKeyboardFocusManager. When the component which has the focus changes, I see whether it is the JOptionPane (using the Swing hierarchy) and dispose of it. This turns out to work quite well, but still not 100% reliable. Therefore (and certainly for new code), I try to stick to one of the two previously outlined approaches
There are two basic ways to overcome this issue. Both require you to be able to find the button.
This will require to be able to use a second Thread to find the window/dialog and walk it's container to find the button...
Depending on how your tests are set up will depend on how much additional work you might need to go through. The UI should be operating within the Event Dispatching Thread, if your tests are running in the same thread, then you will need to use separate Thread, as the JOptionPane will block the EDT (in a special way), making it impossible for your code to run until the option pane is closed.
If your tests are already running in a separate Thread, then you have a little more capability...
This is a very simple example. It initialises a JOptionPane within the context of the EDT, but has methods that are running in the main thread which find and execute the "OK" button of the message dialog.
import java.awt.Button;
import java.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Window;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class FindButton {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JOptionPane.showMessageDialog(null, "This is message dialog", "Message", JOptionPane.INFORMATION_MESSAGE);
}
});
JDialog frame = waitForDialog("Message");
System.out.println("Found window " + frame);
if (frame != null) {
final JButton btn = getButton(frame, "OK");
System.out.println("Found button " + btn);
if (btn != null) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
btn.doClick();
}
});
}
}
}
public static JDialog waitForDialog(String title) {
JDialog win = null;
do {
for (Window window : Frame.getWindows()) {
if (window instanceof JDialog) {
JDialog dialog = (JDialog) window;
System.out.println(dialog.getTitle());
if (title.equals(dialog.getTitle())) {
win = dialog;
break;
}
}
}
if (win == null) {
try {
Thread.sleep(250);
} catch (InterruptedException ex) {
break;
}
}
} while (win == null);
return win;
}
public static JButton getButton(Container container, String text) {
JButton btn = null;
List<Container> children = new ArrayList<Container>(25);
for (Component child : container.getComponents()) {
System.out.println(child);
if (child instanceof JButton) {
JButton button = (JButton) child;
if (text.equals(button.getText())) {
btn = button;
break;
}
} else if (child instanceof Container) {
children.add((Container) child);
}
}
if (btn == null) {
for (Container cont : children) {
JButton button = getButton(cont, text);
if (button != null) {
btn = button;
break;
}
}
}
return btn;
}
}
This example uses the doClick method of the JButton to simulate a clicking of the button. You could use the locationOnScreen information from the button and java.awt.Robot to physically click the button. but this just seemed simpler.
You could also take a look at Jemmy which is a utiliy library designed to make it easiert to test Swing (and JavaFX) based applications...
First, create a JDialog from a JOptionPane object. Then, create a timer to run for the time you want( exemple 1min) , and dispose the dialog once it has finished. Then, retrieve the chosen value from your JOptionPane object, making sure to account for an uninitialized value if the dialog was disposed by your timer.
final JOptionPane pane = new JOptionPane(......., JOptionPane.OK_CANCEL_OPTION);
final JDialog dialog = pane.createDialog(.....);
Timer timer = new Timer(you_time, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
dialog.dispose();
}
});
timer.start();
dialog.setVisible(true);
dialog.dispose();
Integer choice = (Integer) (pane.getValue() == JOptionPane.UNINITIALIZED_VALUE ? JOptionPane.OK_OPTION : pane.getValue());

Overriding Cut/Copy/Paste in SWT Text control

What is the correct way to override the cut(), copy(), and paste() methods of the Text control? What triggers the execution of these methods?
I have created an example application with a custom class that overrides these methods. Unfortunately, nothing seems to execute these overridden methods, including the act of using Ctrl+X / Ctrl+C / Ctrl+V or selecting cut/copy/paste from the context menu.
Custom Text Class:
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
public class TextCopyable extends Text{
public TextCopyable(Composite parent, int style) {
super(parent, style);
}
#Override
public void checkSubclass() {
}
#Override
public void cut() {
System.out.println("Cut!");
}
#Override
public void copy() {
System.out.println("Copy!");
}
#Override
public void paste() {
System.out.println("Paste!");
}
}
Test Shell:
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.layout.GridData;
public class CopyPasteTest extends Shell {
private TextCopyable text;
public static void main(String args[]) {
try {
Display display = Display.getDefault();
CopyPasteTest shell = new CopyPasteTest(display);
shell.open();
shell.layout();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public CopyPasteTest(Display display) {
super(display, SWT.SHELL_TRIM);
createContents();
}
protected void createContents() {
setText("SWT Application");
setSize(450, 300);
GridLayout gridLayout = new GridLayout();
setLayout(gridLayout);
text = new TextCopyable(this, SWT.BORDER);
text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
}
#Override
protected void checkSubclass() {
// Disable the check that prevents subclassing of SWT components
}
}
My expectation was that the copy() method would be called any time that I use the Ctrl+C command to copy text from the textbox. However, the methods do not trigger at all. Is my assumption faulty?

SWT Popup Composite unexpected disposal

So I've stolen this cool PopupComposite, and I am really satisfied with it.
There's just one issue. If it put a org.eclipse.swt.widgets.Text in it, I open the popup, focus the Text, and press ESC, then both the Text and the PopupComposite dispose themselves.
I really can't figure out where the dispose call is coming from. Is it a Shell issue? What Shell should I use with the popup?
SSCCE:
/**
*
* #author ggrec
*
*/
public class PopupCompositeTester
{
public static void main(final String[] args)
{
new PopupCompositeTester();
}
private PopupCompositeTester()
{
final Display display = new Display();
final Shell shell = new Shell(display);
shell.setLayout(new GridLayout(1, false));
createContents(shell);
shell.pack();
shell.open();
while (!shell.isDisposed())
{
if ( !display.readAndDispatch() )
display.sleep();
}
display.dispose();
}
private static void createContents(final Composite parent)
{
final Button button = new Button(parent, SWT.PUSH);
button.setText("Poke Me");
final PopupComposite popup = new PopupComposite(parent.getShell());
new Text(popup, SWT.NONE);
popup.pack();
button.addSelectionListener(new SelectionAdapter()
{
#Override public void widgetSelected(final SelectionEvent e)
{
popup.show( Display.getDefault().map(parent, null, button.getLocation()) );
}
});
}
}
The reason for this is because when you focus the text field and press Escape, the field sends a SWT.TRAVERSE_ESCAPE event to its parent shell. The shell (in your case not being a top-level shell) responds by calling Shell.close(). You can work around that by adding a traverse listener to your text field, which would cancel the event (code below).
new Text(popup, SWT.NONE).addTraverseListener(new TraverseListener() {
#Override
public void keyTraversed(TraverseEvent e) {
if(e.detail == SWT.TRAVERSE_ESCAPE) {
e.doit = false;
}
}
});
Keep in mind, this is a rather crude solution to your specific issue. I would not recommend using this for anything other than testing purposes. You can read more about this here -> http://help.eclipse.org/indigo/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fswt%2Fevents%2FTraverseEvent.html
And here: http://help.eclipse.org/helios/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fswt%2Fwidgets%2FShell.html
Because my "bug" is actually a normal behaviour of the SWT platform, I've used the following workaround:
/**
* Lazy initialization of the popup composite
*/
private void createPopup()
{
// popupContainer is now a field
if (popupContainer != null && !popupContainer.isDisposed())
return;
// ... create popup AND its contents ...
}
and in the button listener:
createPopup();
popup.show( Display.getDefault().map(parent, null, button.getLocation()) );
Thank you #blgt

Categories