[英]Running a function in Java Swing EDT if necessary
如果一个类的公共函数更新了一些 java swing GUI 元素,我必须检查这个函数在哪个上下文中被调用,并确保更新是在 EDT 中完成的。 这就是我解决它的方法,但由于可能有很多函数我必须做同样的事情(但调用类的不同私有方法)或其他类我必须做同样的事情,我厌倦了总是写相同的代码
public void updateGUI()
{
if(!SwingUtilities.isEventDispatchThread())
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
update();
}
});
}
else
{
update();
}
}
private void update()
{
//update the swing elements here
}
一种可能的解决方案可能是创建一个具有此更新方法的接口(名为 UpdateInterface)。 该类现在将实现这个接口,我创建了一个带有静态方法的辅助类,它引用这个接口并在 EDT 上下文中调用 update 方法:
public static void callUpdateInEDT(UpdateInterface f)
{
if(!SwingUtilities.isEventDispatchThread())
{
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
f.update();
}
});
}
else
{
f.update();
}
}
但这不是很灵活,因为我可能有一些其他的私有方法,我应该在 EDT 上下文中调用它们。 SwingUtilities 是否有任何我目前没有找到的解决方案,或者任何其他比我提出的更灵活的解决方案?
因此,在多年编写更多包装器代码之后,我开始考虑在内部/匿名类出现之前,我编写了一个基于反射的 API,它基本上可以简化调用具有任意数量参数的任何方法的过程
所以,你的代码会变得更像......
SwingSafeMethodInvoker.invokeLater(this, "update").execute();
这只是一种可能的解决方案(而且我很懒,所以我想减少我必须(重新)编写的代码量)
该代码足够智能,可以知道您是否在 EDT 上并采取适当的措施。 关于这个的绝妙之处在于它允许我调用protected
方法(请记住,在内部/匿名类之前,您必须创建一个独立的外部Runnable
类,因此它无法调用protected
方法)
API 建立在额外的反射实用程序之上,所以它有点复杂。
警告此 API 使用反射,众所周知,反射效率较低,因此您需要知道何时不使用它。 它还“打破”了protected
和private
应用于类的访问限制,因此您可以做一些非常有趣的事情,其中大部分我会劝阻您不要尝试/做。
这也不会进行您可能对代码进行的任何重构,因此如果您重命名一个方法,这将看不到它,请小心。
记住:这是我编写的更大的反射 API 的一部分,它允许我在类上执行方法,所以它故意分解为几个类
SwingSafeMethodInvoker
public class SwingSafeMethodInvoker {
public static InvokeLater invokeLater(Object obj, String methodName) {
return new InvokeLater(obj, methodName);
}
public static InvokeLater invokeLater(Class clazz, String methodName) {
return new InvokeLater(clazz, methodName);
}
public static InvokeAndWait invokeAndWait(Object obj, String methodName) {
return new InvokeAndWait(obj, methodName);
}
public static InvokeAndWait invokeAndWait(Class clazz, String methodName) {
return new InvokeAndWait(clazz, methodName);
}
public static InvokeAfter invokeAfter(Object obj, String methodName) {
return new InvokeAfter(obj, methodName);
}
public static InvokeAfter invokeAfter(Class clazz, String methodName) {
return new InvokeAfter(clazz, methodName);
}
public static InvokeAfterAndWait invokeAfterAndWait(Object obj, String methodName) {
return new InvokeAfterAndWait(obj, methodName);
}
public static InvokeAfterAndWait invokeAfterAndWait(Class clazz, String methodName) {
return new InvokeAfterAndWait(clazz, methodName);
}
public static MethodInvoker invoke(Object obj, String methodName) {
return new MethodInvoker(obj, methodName);
}
public static MethodInvoker invoke(Class clazz, String methodName) {
return new MethodInvoker(clazz, methodName);
}
}
稍后调用
import java.awt.EventQueue;
import javax.swing.SwingUtilities;
/**
* This will make a synchronised call into the EventQueue.
*
* There is no way to determine when the actually call will be made. If you
* need to wait for the result of the call, you are better of using
* InvokeAndWait instead
*
* If the invoke method is called within the ETD, it will be executed
* immediately.
*
* If you want the call to occur later (ie have it placed at the end
* of the EventQueue, use InvokeAfter)
*
* The invoke method will always return null.
* @author shane
*/
public class InvokeLater extends AbstractSwingMethodInvoker {
public InvokeLater(Object parent, Class parentClass, String methodName) {
super(parent, parentClass, methodName);
}
public InvokeLater(Object parent, String methodName) {
this(parent, parent.getClass(), methodName);
}
public InvokeLater(Class clazz, String methodName) {
this(null, clazz, methodName);
}
@Override
public Object execute() throws SynchronisedDispatcherException {
if (EventQueue.isDispatchThread()) {
run();
} else {
SwingUtilities.invokeLater(this);
}
return null;
}
}
调用和等待
import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import javax.swing.SwingUtilities;
public class InvokeAndWait extends AbstractSwingMethodInvoker {
public InvokeAndWait(Object parent, Class parentClass, String methodName) {
super(parent, parentClass, methodName);
}
public InvokeAndWait(Object parent, String methodName) {
this(parent, parent.getClass(), methodName);
}
public InvokeAndWait(Class clazz, String methodName) {
this(null, clazz, methodName);
}
@Override
public Object execute() {
if (EventQueue.isDispatchThread()) {
run();
} else {
try {
SwingUtilities.invokeAndWait(this);
} catch (InterruptedException ex) {
throw new InvocationException("Failed to invokeAndWait", ex);
} catch (InvocationTargetException ex) {
throw new InvocationException("Failed to invokeAndWait", ex);
}
}
return getResult();
}
}
调用之后
import javax.swing.SwingUtilities;
/**
* This will place the method call onto the end of the event dispatching
* queue and return immediately.
*
* There is no means to known when the call actually takes and place and if
* you are interested in the return result, you are better of using InvokeAndWait
* @author shane
*/
public class InvokeAfter extends InvokeLater {
public InvokeAfter(Object parent, Class parentClass, String methodName) {
super(parent, parentClass, methodName);
}
public InvokeAfter(Object parent, String methodName) {
this(parent, parent.getClass(), methodName);
}
public InvokeAfter(Class clazz, String methodName) {
this(null, clazz, methodName);
}
@Override
public Object execute() throws SynchronisedDispatcherException {
SwingUtilities.invokeLater(this);
return null;
}
}
AbstractSwingMethodInvoker
import core.util.MethodInvoker;
public abstract class AbstractSwingMethodInvoker extends MethodInvoker implements Runnable {
private Object result;
public AbstractSwingMethodInvoker(Object parent, Class parentClass, String methodName) {
super(parent, parentClass, methodName);
}
public AbstractSwingMethodInvoker(Object parent, String methodName) {
this(parent, parent.getClass(), methodName);
}
public AbstractSwingMethodInvoker(Class clazz, String methodName) {
this(null, clazz, methodName);
}
@Override
public void run() {
result = super.execute();
}
public Object getResult() {
return result;
}
public class SynchronisedDispatcherException extends Error {
public SynchronisedDispatcherException(String message) {
super(message);
}
public SynchronisedDispatcherException(String message, Throwable cause) {
super(message, cause);
}
}
}
方法调用者
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* Provides a means to invoke any method on any class/object using reflection
*
* @author Shane Whitegead
*/
public class MethodInvoker {
private Object parent;
private Class parentClass;
private String methodName;
private List<Parameter> lstParameters;
public MethodInvoker() {
}
public MethodInvoker(Object parent, Class parentClass, String methodName) {
this.parent = parent;
this.parentClass = parentClass;
this.methodName = methodName;
lstParameters = new ArrayList<Parameter>(5);
}
public MethodInvoker(Object parent, String methodName) {
this(parent, parent.getClass(), methodName);
}
public MethodInvoker(Class clazz, String methodName) {
this(null, clazz, methodName);
}
public static MethodInvoker invoke(Object parent, String methodName) {
return new MethodInvoker(parent, methodName);
}
public static MethodInvoker invoke(Class parent, String methodName) {
return new MethodInvoker(parent, methodName);
}
public static MethodInvoker invoke(Object parent, Class clazz, String methodName) {
return new MethodInvoker(parent, clazz, methodName);
}
public MethodInvoker setParent(Object parent) {
this.parent = parent;
if (parent != null) {
setParentClass(parentClass.getClass());
}
return this;
}
public MethodInvoker setParentClass(Class parent) {
this.parentClass = parent;
return this;
}
public MethodInvoker setMethodName(String method) {
this.methodName = method;
return this;
}
public <T> MethodInvoker with(Class<T> type, T value) {
with(new Parameter(value, type));
return this;
}
public MethodInvoker with(Class[] types, Object[] parameters) {
if (types == null || parameters == null) {
} else if (types.length != parameters.length) {
} else {
for (int index = 0; index < types.length; index++) {
with(types[index], parameters[index]);
}
}
return this;
}
public MethodInvoker with(Parameter parameter) {
lstParameters.add(parameter);
return this;
}
public Object getParent() {
return parent;
}
public Class getParentClass() {
return parentClass;
}
public String getMethodName() {
return methodName;
}
public Class[] getParameterTypes() {
List<Class> lstTypes = new ArrayList<Class>(lstParameters.size());
for (Parameter parameter : lstParameters) {
lstTypes.add(parameter.getType());
}
return lstTypes.toArray(new Class[lstTypes.size()]);
}
public Object[] getParameterValues() {
List<Object> lstTypes = new ArrayList<Object>(lstParameters.size());
for (Parameter parameter : lstParameters) {
lstTypes.add(parameter.getValue());
}
return lstTypes.toArray(new Object[lstTypes.size()]);
}
public Object execute() {
Object result = null;
Class type = getParentClass();
String methodName = getMethodName();
Class[] lstTypes = getParameterTypes();
Object[] lstValues = getParameterValues();
Object parent = getParent();
try {
Method method = findMethod(type, methodName, lstTypes);
if (method == null) {
throw new NoSuchMethodException(getMethodDescription(type, methodName, lstTypes));
}
method.setAccessible(true);
// logger.info("Method = " + method);
result = method.invoke(parent, lstValues);
} catch (Exception ex) {
StringBuilder sb = new StringBuilder(64);
sb.append("parent = ").append(parent).append("\n");
sb.append("type = ").append(type).append("\n");
sb.append("methodName = ").append(methodName).append("\n");
for (int index = 0; index < lstTypes.length; index++) {
sb.append("[").append(index).append("] ").append(lstTypes[index].getName()).append(lstValues[index]).append("\n");
}
System.err.println("Called by\n" + sb.toString());
throw new InvocationException("Failed to invoke " + methodName, ex);
}
return result;
}
public static Field findField(Class parent, String name) {
Field field = null;
try {
field = parent.getDeclaredField(name);
} catch (NoSuchFieldException noSuchFieldException) {
try {
field = parent.getField(name);
} catch (NoSuchFieldException nsf) {
if (parent.getSuperclass() != null) {
field = findField(parent.getSuperclass(), name);
}
}
}
if (field != null) {
field.setAccessible(true);
}
return field;
}
/**
* This method basically walks the class hierarchy looking for a matching
* method.
*
* The issue is getXMethod only returns a list of the methods for the current
* class and not the inherited methods. This method basically over comes that
* limitation.
*
* If no method can be found matching the criteria, then this method returns a
* null value.
*
* This makes this method not only very powerful, but also very dangerous, as
* it has the power of finding ANY declared method within the hierarchy,
* public, protected or private.
*
* @param parent
* @param name
* @param lstTypes
* @return
*/
public static Method findMethod(Class parent, String name, Class[] lstTypes) {
Method method = null;
try {
method = parent.getDeclaredMethod(name, lstTypes);
} catch (NoSuchMethodException noSuchMethodException) {
try {
method = parent.getMethod(name, lstTypes);
} catch (NoSuchMethodException nsm) {
if (parent.getSuperclass() != null) {
method = findMethod(parent.getSuperclass(), name, lstTypes);
}
}
}
return method;
}
/**
* Builds up a description of the method call in the format of
* [package.class.method([{package.class}{, package.class}{...}])
*
* This is typically used to throw a NoMethodFound exception.
*
* @param clazz
* @param name
* @param lstTypes
* @return
*/
public static String getMethodDescription(Class clazz, String name, Class[] lstTypes) {
StringBuilder sb = new StringBuilder();
sb.append(clazz.getName()).append(".").append(name).append("(");
for (Class type : lstTypes) {
sb.append(type.getName()).append(", ");
}
if (lstTypes.length > 0) {
sb.delete(sb.length() - 2, sb.length());
}
sb.append(")");
return sb.toString();
}
public class Parameter<T> {
private T value;
private Class<T> clazz;
public Parameter(T value, Class<T> clazz) {
this.value = value;
this.clazz = clazz;
}
public T getValue() {
return value;
}
public Class<T> getType() {
return clazz;
}
}
public class InvocationException extends Error {
public InvocationException(String message) {
super(message);
}
public InvocationException(String message, Throwable cause) {
super(message, cause);
}
}
public static boolean hasMethod(Object instance, String methodName) {
return hasMethod(instance.getClass(), methodName);
}
public static boolean hasMethod(Object instance, String methodName, Class[] types) {
return hasMethod(instance.getClass(), methodName, types);
}
public static boolean hasMethod(Class clazz, String methodName) {
return hasMethod(clazz, methodName, new Class[0]);
}
public static boolean hasMethod(Class clazz, String methodName, Class[] types) {
return findMethod(clazz, methodName, types) != null;
}
}
我已经通过代理解决了这样的样板代码。
您的 UI 管理抽象:
public interface UIManager {
void normal();
void internalCall();
void throwException();
}
跟踪实现:
public class UIManagerImpl implements UIManager {
private void traceCall(String method) {
new Exception(Thread.currentThread().getName() + " > " + method).printStackTrace(System.out);
}
@Override
public void normal() {
traceCall("normal");
}
@Override
public void internalCall() {
traceCall("internalCall");
normal();
}
@Override
public void throwException() {
traceCall("throwException");
throw new RuntimeException("doB");
}
}
一个简单的 EDT 感知代理:
public class EdtHandler implements InvocationHandler {
private Object target;
public EdtHandler(Object target) {
this.target = target;
}
public static <T> T newProxy(Class<T> contract, T impl) {
return (T) Proxy.newProxyInstance(impl.getClass().getClassLoader(), new Class<?>[] { contract }, new EdtHandler(impl));
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (SwingUtilities.isEventDispatchThread()) {
dispatch(method, args);
} else {
SwingUtilities.invokeLater(() -> dispatch(method, args));
}
return null;
}
protected void dispatch(Method method, Object[] args) {
try {
method.invoke(target, args);
} catch (IllegalAccessException | InvocationTargetException e) {
onError(e);
}
}
protected void onError(Throwable e) {
throw new IllegalStateException(e);
}
}
现在主要检查:
public static void main(String[] args) {
UIManagerImpl impl = new UIManagerImpl();
UIManager edt = EdtHandler.newProxy(UIManager.class, impl);
Runnable block = () -> { System.out.println("---- Start ---- "); edt.normal(); edt.internalCall(); edt.throwException(); System.out.println("---- Stop ---- "); };
block.run();
SwingUtilities.invokeLater(block);
}
实施注意事项:
invokeLater
可能会更好。null
,更适合void
方法。return
感知代理:
在阅读了一些关于 Lambda 的内容后,我得到了以下解决方案。 我使用与 SwingUtilites 同名的静态方法创建了一个简单的类,例如 invokeLater:
public static void invokeLater(Runnable doRun)
{
if(SwingUtilities.isEventDispatchThread())
{
doRun.run();
}
else
{
SwingUtilities.invokeLater(() -> {
doRun.run();
});
}
}
现在,假设新类是 SwingUtilities2(我仍在为新类寻找一个好名字 :-)),那么将此函数与 Lambda 一起使用非常容易:
SwingUtilities2.invokeLater(() -> {
//here is the code which should run in the EDT
});
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.