I'm writing a Swing application that needs to function either as an applet in a browser or as a standalone application, i.e. it may be contained in either a JFrame or a JApplet.
In this context, I'd like to display a custom modal dialog box to the user (i.e. a complex dialog with a custom layout and logic, not just one of the simple JOptionPane prompts). It is fine if the dialog is a lightweight component fully contained within the application window.
At the same time, there will be background processing happening in the application (network threads, animations etc.). This needs to continue while the dialog is displayed.
What would be the best approach to implement this?
Take a look at JDialog. If you set it modal it will run its own event processing to keep the GUI up to date, while capturing mouse and keyboard events for its own use.
I've looked at the code it uses and it's really not something you want to try to reinvent.
If you run it non modal, you 'll probably need to add a listener to be called when it finally closes. That is done with addWindowListener and a WindowAdapter that overrides windowClosing.
As for the owner parameter for the constructor, I use
Window w = (Window) SwingUtilities.getAncestorOfClass(Window.class, comp);
where comp is some visible component.
It works because there is always a top level Window, whether running as an applet or application.
Here interesting method for showing frames as modal to specified owner is described:
Show the given frame as modal to the specified owner
However, start() method of class EventPump should be modified in such way:
protected void start() throws Exception
{
Class<?> cl = Class.forName("java.awt.Conditional");
Object conditional = Proxy.newProxyInstance(cl.getClassLoader(), new Class[] { cl }, this);
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
String name = Thread.currentThread().getName();
EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue();
Constructor constructor = Class.forName("java.awt.EventDispatchThread").
getDeclaredConstructor(ThreadGroup.class, name.getClass(), eventQueue.getClass());
constructor.setAccessible(true);
Object eventDispatchThread = constructor.newInstance(threadGroup, name, eventQueue);
Method pumpMethod = eventDispatchThread.getClass().getDeclaredMethod("pumpEvents", cl);
pumpMethod.setAccessible(true);
pumpMethod.invoke(eventDispatchThread, conditional);
}
Related
I have started implementing JCEF in a project of mine, and I am initializing the embedded browser in a JInternalFrame inside of a JFrame, alongside a series of form fields on a JPanel next to the JInternalFrame. The browser component doesn't fully initialize until the JFrame actually becomes visible, and I'm finding that my JTextFields are uneditable unless the JFrame loses and regains focus.
Any idea of what could be happening and how to fix it? This only happens when using a JInternalFrame with the JCEF component...
It also happens every time I call loadURL to load a new page in the browser: the JTextFields become uneditable again, until I lose/gain focus in the JFrame.
UPDATE:
I have found a hack which allows the JTextFields to become editable again, but I wouldn't call it a solution because it is not very elegant. I added a load handler to the CefClient instance ( client.addLoadHandler(new CefLoadHandlerAdapter()) ) with an #Ovveride on the onLoadingStateChange method, which in turn gives access to the current browser component. From there I can detect when loading in the browser is complete, and use SwingUtilities to get the Window that the browser component is in. Then I setVisible(false) and setVisible(true) on that Window. I say it's not a solution because every time the browser is done loading the Window disappears and reappears. Even though the JTextFields are editable again, it is quite ugly to see the window flashing. I've tried all kinds of revalidate() and repaint() methods to no avail, unless I didn't call them right...
client.addLoadHandler(new CefLoadHandlerAdapter() {
#Override
public void onLoadingStateChange(CefBrowser browser, boolean isLoading,
boolean canGoBack, boolean canGoForward) {
if (!isLoading) {
//browser_ready = true;
System.out.println("Browser has finished loading!");
SwingUtilities.windowForComponent( browser.getUIComponent() ).setVisible(false);
SwingUtilities.windowForComponent( browser.getUIComponent() ).setVisible(true);
}
}
});
If anyone can suggest a better solution, please do!
I figured out the problem by studying the sample JCEF application a little better. I need to implement a FocusHandler in order to release the embedded browser's hold on keyboard input:
private boolean browserFocus_ = true;
---
jTextField1.addFocusListener(new FocusAdapter() {
#Override
public void focusGained(FocusEvent e) {
if (!browserFocus_) return;
browserFocus_ = false;
KeyboardFocusManager.getCurrentKeyboardFocusManager().clearGlobalFocusOwner();
jTextField1.requestFocus();
}
});
How do I go about creating what I describe below?
First, here is the basic look of my GUI:
When I click on Add New Account I want to have the GUI pop up a small window where the user can enter log-in credentials. I would need this information to be passed back into the main GUI, so I am lost as how to approach this.
The same goes for Preferences or Remove Account. How do I go about creating a "GUI Overlay" of sorts. Sorry, I can't figure out the correct terminology for the effect I am looking for.
I wanted to attempt to use JOptionPane's, but after some research this seemed like it was not the route to be taking.
I was also toying with the idea of creating a new JFrame when the action was preformed. How should this be approached?
Start by using dialogs over frames. Dialogs are designed to gather small pieces of information from the user.
I would create a separate component for each operation you want to perform. Within these components I would provide setters and getters to allow you to gain access to the information managed by the component.
From there I would either use a JOptionPane or JDialog to display the component to the user. The reason for using one over the other for me comes down to begin able to control the action buttons (Okay and Cancel for example). For something like the login dialog, I want to restrict the user from begin able to hit the Login button until they've provided enough information to make the attempt.
The basic follow would be something like this...
LoginDialog dialog = new LoginDialog(SwingUtilities.getWindowAncestor(this)); // this is a reference any valid Component
dialog.setModal(true); // I would have already done this internally to the LoginDialog class...
dialog.setVisible(true); // A modal dialog will block at this point until the window is closed
if (dialog.isSuccessfulLogin()) {
login = dialog.getLogin(); // Login is a simple class containing the login information...
}
The LoginDialog might look something like this...
public class LoginDialog extends JDialog {
private LoginPanel loginPane;
public LoginDialog(Window wnd) {
super(wnd);
setModal(true);
loginPane = new LoginPanel();
setLayout(new BorderLayout());
add(loginPane);
// Typically, I create another panel and add the buttons I want to use to it.
// These buttons would call dispose once they've completed there work
}
public Login getLogin() {
return loginPane.getLogin();
}
public boolean isSuccessfulLogin() {
return loginPane.isSuccessfulLogin();
}
}
The dialog is simply acting as proxy/container for the login pane.
This is, of course an overview, you will need to fill in the blanks ;)
Now, if you don't want to go to the trouble of creating your own dialog, you can take advantage of the JOptionPane instead.
LoginPanel loginPane = new LoginPanel();
int option = JOptionPane.showOptionDialog(
this, // A reference to the parent component
loginPane,
"Login", // Title
JOptionPane.YES_NO_OPTION,
JOptionPane.QUESTION_MESSAGE,
null, // You can supply your own icon it if you want
new Object[]{"Login", "Cancel"}, // The available options to the user
"Login" // The "initial" option
);
if (option == 0) {
// Attempt login...
}
I have a Java/Swing desktop application (Java 6u16 on Windows XP) which occasionally appears to the users to hang. I say appears to because in reality what is happening is that the application is showing a modal dialog but this dialog is not being rendered. If the user uses Alt-Tab to switch away from the application and then subsequently returns to it, the dialog gets rendered correctly. Additionally, if a remote user connects to the session via NetOp (a VNC/Remote Desktop workalike) this also causes the GUI to be redrawn correctly.
The app runs via JavaWebstart. Since I've heard of rendering issues being caused by DirectDraw, I added the following to the JNLP
<property name="sun.java2d.noddraw" value="true"/>
but the problem still occurs (If I have understood correctly, this will switch off DirectDraw and Direct3d completely: see http://download.oracle.com/javase/1.5.0/docs/guide/2d/flags.html#noddraw)
I'm out of ideas on this one, any suggestions would be greatly appreciated.
Thanks,
Phil
Edit...
I have an abstract dialog class which extends JDialog and which all other dialogs extend. It contains the following method:
public void showDialog() {
initKeyBindings();
Application.getApplication().deactivateScannerListener();
setVisible(true);
}
Whenever I want to display a dialog, I call showDialog(). The initKeyBindings method sets up an ActionMap while the second line is application specific (Application is a singleton, I'm disabling the JPOS scanner listener while the dialog is displaying).
There is a corresponding hideDialog() method as follows:
public void hideDialog() {
setVisible(false);
Application.getApplication().activateScannerListener();
dispose();
}
Thanks,
Phil
Edit...
Sorry about this, one more edit: all of the dialogs have a parent. The AbstractDialog class will default to the main application frame if no other parent is specified.
FYI
For anyone following this, I've added the following to my code:
if (SwingUtilities.isEventDispatchThread()) {
initialiseAndShowDialog();
} else {
SwingUtilities.invokeAndWait(new Runnable() {
#Override
public void run() {
initialiseAndShowDialog();
}
});
}
This ensures that the dialog is only opened from the EDT.
Which thread are you calling showDialog() from? Swing components should be accessed on the Event Dispatch Thread only.
You could try SwingUtilities.invokeAndWait()
and the Runnable argument passed to it should call showDialog().
Let us know if it fixed the problem.
I have an applet that calls a JDialog that contains a JProgressBar component. I subclass the JDialog to expose a method to update the JProgressBar, something like:
public class ProgressDialog extends javax.swing.JDialog {
public void setProgress(double progress) {
jProgressBar1.setValue(jProgressBar1.getMinimum() + (int) (progress * jProgressBar1.getMaximum()));
}
...
}
I use this dialog in the following manner:
public void test() throws Exception {
progressDialog = new ProgressDialog(null, true);
try {
progressDialog.setLocationRelativeTo(null);
// show the dialog
EventQueue.invokeLater(new Runnable() {
public void run() {
progressDialog.setVisible(true);
}
});
// business logic code that calls progressDialog.setProgress along the way
doStuff();
} finally {
progressDialog.setVisible(false);
progressDialog.dispose();
}
}
It works fine on Windows/any browser. However, when invoking the above function on Firefox 2/3/3.5 on a Mac, the progressDialog is displayed indefinitely, i.e. it doesn't close.
I suspected that calling setVisible(true) inside the EventQueue was causing the problem, since it's a blocking call and might block the queue completely, so I tried changing it to:
// show the dialog
new Thread() {
public void run() {
progressDialog.setVisible(true);
}
}.start();
With this change, the progressDialog now closes correctly, but a new problem emerged - the contents of the dialog (which included the progressbar, an icon and a JLabel used to show a message string) were no longer shown inside the dialog. It was still a problem only on Mac Firefox.
Any ideas? I realize it's probably some AWT threading issue, but I've been at this for a couple of days and can't find a good solution. Wrapping the doStuff() business logic in a separate new Thread seems to work, but it's not easy to refactor the actual business logic code into a separate thread, so I'm hoping there's a simpler solution.
The envt is:
Mac OSX 10.5
Java 1.5
Firefox 2/3/3.5
Found out that the problem was that the applet function was executing inside the AWT dispatcher thread, therefore the thread blocks and no events are processed until the applet function finishes execution.
Solution was to move the processing logic into a separate thread spawned by the ProgressDialog object before calling setVisible(true). setVisible(true) would block the main thread but still allow the event dispatcher to continue processing, hence rendering the contents of the dialog until the spawned thread calls setVisible(false) to hide the dialog.
I can see how to instantiate a second MVC group, e.g.
def (loginPaneModel, loginPaneView, loginPaneController) =
createMVCGroup('LoginPane', 'LoginPane', [:]);
view.loginPanel = loginPaneView.loginPanel
But I don't want to show as part of my main window. I want it to pop up over it. What do I call to do that? Thanks!
The easiest way would be to use the view panel as the root of a dialog in the parent MVC group. In the view for the group that yor code snippet is the controller of you could do something like this...
application(title:'your app', ....) {
// your existing code...
loginDialog = dialog(title:'Login Panel', visible:false) {
panel(loginPanel)
}
}
And then when you need to show the dialog (in the same controller)
view.loginDialog.visible = true
Nesting a dialog inside of another window has the side effect of setting the dialog's owner to the frame or dialog of the parent. Having a dialog owned by another dialog/window is what causes the dialog to be linked with the parent and always float on top of that parent. It will also raise/lower with the parent as well.
Well, it seems that only the first line is needed. That was enough to pop up a window. I believe the key, though, was to make the view a frame.
def frame = frame(title:'Login', pack:true, locationByPlatform:true) {
...
}
frame.pack()
frame.show()