Swing Gui Listeners Without Awt

Swing GUI listeners without AWT

You're mis-interpreting the information given to you. You should avoid using Swing components with AWT components. It's OK to use Swing with the AWT listener structure, layout managers, etc. and in fact it's impossible not to.

How to temporarily disable event listeners in Swing?

You could use a common base class for your listeners and in it, have a static method to turn the listeners on or off:

public abstract class BaseMouseListener implements ActionListener{

private static boolean active = true;
public static void setActive(boolean active){
BaseMouseListener.active = active;
}

protected abstract void doPerformAction(ActionEvent e);

@Override
public final void actionPerformed(ActionEvent e){
if(active){
doPerformAction(e);
}
}
}

Your listeners would have to implement doPerformAction() instead of actionPerformed().

(This would be awful in an enterprise scenario, but in a single-VM model like in Swing, it should work just fine)

Java and GUI - Where do ActionListeners belong according to MVC pattern?

That's a very difficult question to answer with Swing, as Swing is not a pure MVC implementation, the view and controller are mixed.

Technically, a model and controller should be able to interact and the controller and view should be able to interact, but the view and model should never interact, which clearly isn't how Swing works, but that's another debate...

Another issue is, you really don't want to expose UI components to anybody, the controller shouldn't care how certain actions occur, only that they can.

This would suggest that the ActionListeners attached to your UI controls should be maintained by the view. The view should then alert the controller that some kind of action has occurred. For this, you could use another ActionListener, managed by the view, to which the controller subscribes to.

Better yet, I would have a dedicated view listener, which described the actions that this view might produce, for example...

public interface MainViewListener {
public void didPerformClose(MainView mainView);
}

The controller would then subscribe to the view via this listener and the view would call didPerformClose when (in this case) the close button is pressed.

Even in this example, I would be tempted to make a "main view" interface, which described the properties (setters and getters) and actions (listeners/callbacks) that any implementation is guaranteed to provide, then you don't care how these actions occur, only that when they do, you are expected to do something...

At each level you want to ask yourself, how easy would it be to change any element (change the model or the controller or the view) for another instance? If you find yourself having to decouple the code, then you have a problem. Communicate via interfaces and try and reduce the amount of coupling between the layers and the amount that each layer knows about the others to the point where they are simply maintaining contracts

Updated...

Let's take this for an example...

Login

There are actually two views (discounting the actual dialog), there is the credentials view and the login view, yes they are different as you will see.

CredentialsView

The credentials view is responsible for collecting the details that are to be authenticated, the user name and password. It will provide information to the controller to let it know when those credentials have been changed, as the controller may want to take some action, like enabling the "login" button...

The view will also want to know when authentication is about to take place, as it will want to disable it's fields, so the user can't update the view while the authentication is taking place, equally, it will need to know when the authentication fails or succeeds, as it will need to take actions for those eventualities.

public interface CredentialsView {

public String getUserName();
public char[] getPassword();

public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();

public void setCredentialsViewController(CredentialsViewController listener);

}

public interface CredentialsViewController {

public void credientialsDidChange(CredentialsView view);

}

CredentialsPane

The CredentialsPane is the physical implementation of a CredentialsView, it implements the contract, but manages it's own internal state. How the contract is managed is irrelevent to the controller, it only cares about the contract been upheld...

public class CredentialsPane extends JPanel implements CredentialsView {

private CredentialsViewController controller;

private JTextField userNameField;
private JPasswordField passwordField;

public CredentialsPane(CredentialsViewController controller) {
setCredentialsViewController(controller);
setLayout(new GridBagLayout());
userNameField = new JTextField(20);
passwordField = new JPasswordField(20);

GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("Username: "), gbc);

gbc.gridy++;
add(new JLabel("Password: "), gbc);

gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(userNameField, gbc);
gbc.gridy++;
add(passwordField, gbc);

DocumentListener listener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}

@Override
public void removeUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}

@Override
public void changedUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
};

userNameField.getDocument().addDocumentListener(listener);
passwordField.getDocument().addDocumentListener(listener);

}

@Override
public CredentialsViewController getCredentialsViewController() {
return controller;
}

@Override
public String getUserName() {
return userNameField.getText();
}

@Override
public char[] getPassword() {
return passwordField.getPassword();
}

@Override
public void willAuthenticate() {
userNameField.setEnabled(false);
passwordField.setEnabled(false);
}

@Override
public void authenticationFailed() {
userNameField.setEnabled(true);
passwordField.setEnabled(true);

userNameField.requestFocusInWindow();
userNameField.selectAll();

JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE);
}

@Override
public void authenticationSucceeded() {
// Really don't care, but you might want to stop animation, for example...
}

public void setCredentialsViewController(CredentialsViewController controller){
this.controller = controller;
}

}

LoginView

The LoginView is responsible for managing a CredentialsView, but also for notifying the LoginViewController when authentication should take place or if the process was cancelled by the user, via some means...

Equally, the LoginViewController will tell the view when authentication is about to take place and if the authentication failed or was successful.

public interface LoginView {

public CredentialsView getCredentialsView();

public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();

public void dismissView();

public LoginViewController getLoginViewController();

}

public interface LoginViewController {

public void authenticationWasRequested(LoginView view);
public void loginWasCancelled(LoginView view);

}

LoginPane

The LoginPane is kind of special, it is acting as the view for the LoginViewController, but it is also acting as the controller for the CredentialsView. This is important, as there is nothing saying that a view can't be a controller, but I would be careful about how you implement such things, as it might not always make sense to do it this way, but because the two views are working together to gather information and manage events, it made sense in this case.

Because the LoginPane will need to change it's own state based on the changes in the CredentialsView, it makes sense to allow the LoginPane to act as the controller in this case, otherwise, you'd need to supply more methods that controlled that state of the buttons, but this starts to bleed UI logic over to the controller...

public static class LoginPane extends JPanel implements LoginView, CredentialsViewController {

private LoginViewController controller;
private CredentialsPane credientialsView;

private JButton btnAuthenticate;
private JButton btnCancel;

private boolean wasAuthenticated;

public LoginPane(LoginViewController controller) {
setLoginViewController(controller);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(8, 8, 8, 8));

btnAuthenticate = new JButton("Login");
btnCancel = new JButton("Cancel");

JPanel buttons = new JPanel();
buttons.add(btnAuthenticate);
buttons.add(btnCancel);

add(buttons, BorderLayout.SOUTH);

credientialsView = new CredentialsPane(this);
add(credientialsView);

btnAuthenticate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().authenticationWasRequested(LoginPane.this);
}
});
btnCancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().loginWasCancelled(LoginPane.this);
// I did think about calling dispose here,
// but's not really the the job of the cancel button to decide what should happen here...
}
});

validateCreientials();

}

public static boolean showLoginDialog(LoginViewController controller) {

final LoginPane pane = new LoginPane(controller);

JDialog dialog = new JDialog();
dialog.setTitle("Login");
dialog.setModal(true);
dialog.add(pane);
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
pane.getLoginViewController().loginWasCancelled(pane);
}
});
dialog.setVisible(true);

return pane.wasAuthenticated();

}

public boolean wasAuthenticated() {
return wasAuthenticated;
}

public void validateCreientials() {

CredentialsView view = getCredentialsView();
String userName = view.getUserName();
char[] password = view.getPassword();
if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) {

btnAuthenticate.setEnabled(true);

} else {

btnAuthenticate.setEnabled(false);

}

}

@Override
public void dismissView() {
SwingUtilities.windowForComponent(this).dispose();
}

@Override
public CredentialsView getCredentialsView() {
return credientialsView;
}

@Override
public void willAuthenticate() {
getCredentialsView().willAuthenticate();
btnAuthenticate.setEnabled(false);
}

@Override
public void authenticationFailed() {
getCredentialsView().authenticationFailed();
validateCreientials();
wasAuthenticated = false;
}

@Override
public void authenticationSucceeded() {
getCredentialsView().authenticationSucceeded();
validateCreientials();
wasAuthenticated = true;
}

public LoginViewController getLoginViewController() {
return controller;
}

public void setLoginViewController(LoginViewController controller) {
this.controller = controller;
}

@Override
public void credientialsDidChange(CredentialsView view) {
validateCreientials();
}

}

Working example

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.EmptyBorder;
import javax.swing.event.DocumentEvent;
import javax.swing.event.DocumentListener;
import sun.net.www.protocol.http.HttpURLConnection;

public class Test {

protected static final Random AUTHENTICATION_ORACLE = new Random();

public static void main(String[] args) {
new Test();
}

public interface CredentialsView {
public String getUserName();
public char[] getPassword();
public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();
public CredentialsViewController getCredentialsViewController();
}

public interface CredentialsViewController {
public void credientialsDidChange(CredentialsView view);
}

public interface LoginView {
public CredentialsView getCredentialsView();
public void willAuthenticate();
public void authenticationFailed();
public void authenticationSucceeded();
public void dismissView();
public LoginViewController getLoginViewController();
}

public interface LoginViewController {
public void authenticationWasRequested(LoginView view);
public void loginWasCancelled(LoginView view);
}

public Test() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}

LoginViewController controller = new LoginViewController() {

@Override
public void authenticationWasRequested(LoginView view) {
view.willAuthenticate();
LoginAuthenticator authenticator = new LoginAuthenticator(view);
authenticator.authenticate();
}

@Override
public void loginWasCancelled(LoginView view) {

view.dismissView();

}
};

if (LoginPane.showLoginDialog(controller)) {

System.out.println("You shell pass");

} else {

System.out.println("You shell not pass");

}

System.exit(0);

}
});
}

public class LoginAuthenticator {

private LoginView view;

public LoginAuthenticator(LoginView view) {
this.view = view;
}

public void authenticate() {

Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
if (AUTHENTICATION_ORACLE.nextBoolean()) {
view.authenticationSucceeded();
view.dismissView();
} else {
view.authenticationFailed();
}
}
});
}
});
t.start();

}

}

public static class LoginPane extends JPanel implements LoginView, CredentialsViewController {

private LoginViewController controller;
private CredentialsPane credientialsView;

private JButton btnAuthenticate;
private JButton btnCancel;

private boolean wasAuthenticated;

public LoginPane(LoginViewController controller) {
setLoginViewController(controller);
setLayout(new BorderLayout());
setBorder(new EmptyBorder(8, 8, 8, 8));

btnAuthenticate = new JButton("Login");
btnCancel = new JButton("Cancel");

JPanel buttons = new JPanel();
buttons.add(btnAuthenticate);
buttons.add(btnCancel);

add(buttons, BorderLayout.SOUTH);

credientialsView = new CredentialsPane(this);
add(credientialsView);

btnAuthenticate.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().authenticationWasRequested(LoginPane.this);
}
});
btnCancel.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
getLoginViewController().loginWasCancelled(LoginPane.this);
// I did think about calling dispose here,
// but's not really the the job of the cancel button to decide what should happen here...
}
});

validateCreientials();

}

public static boolean showLoginDialog(LoginViewController controller) {

final LoginPane pane = new LoginPane(controller);

JDialog dialog = new JDialog();
dialog.setTitle("Login");
dialog.setModal(true);
dialog.add(pane);
dialog.pack();
dialog.setLocationRelativeTo(null);
dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE);
dialog.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
pane.getLoginViewController().loginWasCancelled(pane);
}
});
dialog.setVisible(true);

return pane.wasAuthenticated();

}

public boolean wasAuthenticated() {
return wasAuthenticated;
}

public void validateCreientials() {

CredentialsView view = getCredentialsView();
String userName = view.getUserName();
char[] password = view.getPassword();
if ((userName != null && userName.trim().length() > 0) && (password != null && password.length > 0)) {

btnAuthenticate.setEnabled(true);

} else {

btnAuthenticate.setEnabled(false);

}

}

@Override
public void dismissView() {
SwingUtilities.windowForComponent(this).dispose();
}

@Override
public CredentialsView getCredentialsView() {
return credientialsView;
}

@Override
public void willAuthenticate() {
getCredentialsView().willAuthenticate();
btnAuthenticate.setEnabled(false);
}

@Override
public void authenticationFailed() {
getCredentialsView().authenticationFailed();
validateCreientials();
wasAuthenticated = false;
}

@Override
public void authenticationSucceeded() {
getCredentialsView().authenticationSucceeded();
validateCreientials();
wasAuthenticated = true;
}

public LoginViewController getLoginViewController() {
return controller;
}

public void setLoginViewController(LoginViewController controller) {
this.controller = controller;
}

@Override
public void credientialsDidChange(CredentialsView view) {
validateCreientials();
}

}

public static class CredentialsPane extends JPanel implements CredentialsView {

private CredentialsViewController controller;

private JTextField userNameField;
private JPasswordField passwordField;

public CredentialsPane(CredentialsViewController controller) {
setCredentialsViewController(controller);
setLayout(new GridBagLayout());
userNameField = new JTextField(20);
passwordField = new JPasswordField(20);

GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.insets = new Insets(2, 2, 2, 2);
gbc.anchor = GridBagConstraints.EAST;
add(new JLabel("Username: "), gbc);

gbc.gridy++;
add(new JLabel("Password: "), gbc);

gbc.gridx = 1;
gbc.gridy = 0;
gbc.anchor = GridBagConstraints.WEST;
gbc.fill = GridBagConstraints.HORIZONTAL;
add(userNameField, gbc);
gbc.gridy++;
add(passwordField, gbc);

DocumentListener listener = new DocumentListener() {
@Override
public void insertUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}

@Override
public void removeUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}

@Override
public void changedUpdate(DocumentEvent e) {
getCredentialsViewController().credientialsDidChange(CredentialsPane.this);
}
};

userNameField.getDocument().addDocumentListener(listener);
passwordField.getDocument().addDocumentListener(listener);

}

@Override
public CredentialsViewController getCredentialsViewController() {
return controller;
}

@Override
public String getUserName() {
return userNameField.getText();
}

@Override
public char[] getPassword() {
return passwordField.getPassword();
}

@Override
public void willAuthenticate() {
userNameField.setEnabled(false);
passwordField.setEnabled(false);
}

@Override
public void authenticationFailed() {
userNameField.setEnabled(true);
passwordField.setEnabled(true);

userNameField.requestFocusInWindow();
userNameField.selectAll();

JOptionPane.showMessageDialog(this, "Authentication has failed", "Error", JOptionPane.ERROR_MESSAGE);
}

@Override
public void authenticationSucceeded() {
// Really don't care, but you might want to stop animation, for example...
}

public void setCredentialsViewController(CredentialsViewController controller) {
this.controller = controller;
}

}

}

MouseListener without a gui

While this can be done with Java using JNI or JNA or other tools that are needed in order to get Java closer to the OS, please understand that Java has been built to be as operating system agnostic as possible -- in other words it was built so that this sort of thing would be very hard to do without jumping through the hoops mentioned. Your best bet, I think, is to use a tool that does get closer to the OS, such as C++ for all or C# or AutoIt (I recommend the latter highly) if Windows and other other similar tools for other OS's.

JavaBeanProperties in JavaFX without pulling in java.desktop (Swing, AWT)

You could use a generic solution that doesn’t use Reflection at all:

public class DelegatingProperty<B,T> extends ObjectPropertyBase<T>
implements JavaBeanProperty<T> {
/**
* Create a property without PropertyChangeEvent based notification.
*/
public static <O, V> JavaBeanProperty<V> get(O bean, String name,
Function<? super O, ? extends V> getter,
BiConsumer<? super O, ? super V> setter) {
return new DelegatingProperty<>(bean, name, getter, setter, null, null);
}
/**
* Create a property with PropertyChangeEvent based notification.
*/
public static <O, V> JavaBeanProperty<V> get(O bean, String name,
Function<? super O, ? extends V> getter, BiConsumer<? super O, ? super V> setter,
BiConsumer<? super O, ? super PropertyChangeListener> register,
BiConsumer<? super O, ? super PropertyChangeListener> unregister) {
return new DelegatingProperty<>(bean, name, getter, setter, register, unregister);
}
B bean;
String name;
Function<? super B, ? extends T> getter;
BiConsumer<? super B, ? super T> setter;
BiConsumer<? super B, ? super PropertyChangeListener> unregister;
PropertyChangeListener listener;

private DelegatingProperty(B bean, String name,
Function<? super B, ? extends T> getter,
BiConsumer<? super B, ? super T> setter,
BiConsumer<? super B, ? super PropertyChangeListener> register,
BiConsumer<? super B, ? super PropertyChangeListener> unregister) {
this.bean = Objects.requireNonNull(bean);
this.name = name;
this.getter = Objects.requireNonNull(getter);
this.setter = Objects.requireNonNull(setter);
if(register != null || unregister != null) {
Objects.requireNonNull(register);
this.unregister = Objects.requireNonNull(unregister);
register.accept(bean, listener = event -> fireValueChangedEvent());
}
}

@Override
public Object getBean() {
return bean;
}

@Override
public String getName() {
return name;
}

@Override
public T get() {
return getter.apply(bean);
}

@Override
public void set(T value) {
if(isBound()) throw new IllegalStateException("bound property");
T old = getter.apply(bean);
setter.accept(bean, value);
T now = getter.apply(bean);
if(!Objects.equals(old, now)) fireValueChangedEvent();
}

@Override
protected void invalidated() {
if(isBound()) {
setter.accept(bean, super.get());
}
}

@Override
public void fireValueChangedEvent() {
super.fireValueChangedEvent();
}

@Override
public void dispose() {
if(unregister != null && listener != null) {
unregister.accept(bean, listener);
listener = null;
}
}
}

Then, to stay at your example, you could get the name property of Entity as

JavaBeanProperty<String> prop = DelegatingProperty.get(bean, "name",
Entity::getName, Entity::setName,
Entity::addPropertyChangeListener, Entity::removePropertyChangeListener);

It’s more verbose, but on the other hand, provides more compile time safety, as the presence of all methods required for the property is checked at compile-time, and will likely have a higher runtime performance.

When you have a lot of properties in one bean class with event support, you may benefit from a dedicated factory method, e.g.

public static <V> JavaBeanProperty<V> property(Entity theBean, String name,
Function<? super Entity, ? extends V> getter,
BiConsumer<? super Entity, ? super V> setter) {
return DelegatingProperty.get(theBean, name, getter, setter,
Entity::addPropertyChangeListener, Entity::removePropertyChangeListener);
}

which you then can use as



Related Topics



Leave a reply



Submit