[英]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.