简体   繁体   English

如有必要,在 Java Swing EDT 中运行函数

[英]Running a function in Java Swing EDT if necessary

If a public function of a class updates some java swing GUI elements, I have to check in which context this function is called and to be sure the updates are done in the EDT.如果一个类的公共函数更新了一些 java swing GUI 元素,我必须检查这个函数在哪个上下文中被调用,并确保更新是在 EDT 中完成的。 This is how I solved it, but as there are maybe much functions which I have to do the same (but call a different private method of the class) or other classes where I have to do the same, I'm tired of writing always the same code这就是我解决它的方法,但由于可能有很多函数我必须做同样的事情(但调用类的不同私有方法)或其他类我必须做同样的事情,我厌倦了总是写相同的代码

public void updateGUI() 
{
    if(!SwingUtilities.isEventDispatchThread())
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                update();
            }
        });
    }
    else
    {
        update();
    }
}

private void update()
{
    //update the swing elements here
}

One possible solution may be to create an interface (named UpdateInterface) which has this update method.一种可能的解决方案可能是创建一个具有此更新方法的接口(名为 UpdateInterface)。 The class will now implement this interface and I create a helper class with a static method which takes a reference of this interface and call the update method in the EDT context:该类现在将实现这个接口,我创建了一个带有静态方法的辅助类,它引用这个接口并在 EDT 上下文中调用 update 方法:

public static void callUpdateInEDT(UpdateInterface f) 
{
    if(!SwingUtilities.isEventDispatchThread())
    {
        SwingUtilities.invokeLater(new Runnable() 
        {
            public void run() 
            {
                f.update();
            }
        });
    }
    else
    {
        f.update();
    }
}

But this is not very flexible as I may have some other private methods which I should call in the EDT context.但这不是很灵活,因为我可能有一些其他的私有方法,我应该在 EDT 上下文中调用它们。 Are there any solutions with the SwingUtilities which I didn't find so far or any other solutions which are more flexible as the one I proposed? SwingUtilities 是否有任何我目前没有找到的解决方案,或者任何其他比我提出的更灵活的解决方案?

So after many years of writing more wrapper code then I care to think about and before the days of inner/anonymous classes, I wrote a reflection based API which would basically simplify the process of calling any method with any number of parameters因此,在多年编写更多包装器代码之后,我开始考虑在内部/匿名类出现之前,我编写了一个基于反射的 API,它基本上可以简化调用具有任意数量参数的任何方法的过程

So, your code would become something more like...所以,你的代码会变得更像......

SwingSafeMethodInvoker.invokeLater(this, "update").execute();

This is just ONE possible solution (and I'm lazy, so I like to reduce the amount of code I have to (re)write)这只是一种可能的解决方案(而且我很懒,所以我想减少我必须(重新)编写的代码量)

The code is smart enough to know if you're on the EDT or not and take appropriate action.该代码足够智能,可以知道您是否在 EDT 上并采取适当的措施。 The brilliant thing about this is it allowed me to call protected methods (remember, before inner/anonymous classes, you would have had to create a stand along, external Runnable class, so it wouldn't have had the ability to call protected methods)关于这个的绝妙之处在于它允许我调用protected方法(请记住,在内部/匿名类之前,您必须创建一个独立的外部Runnable类,因此它无法调用protected方法)

The API is built on top of additional refection utilities, so it's a little involved. API 建立在额外的反射实用程序之上,所以它有点复杂。

WARNING This API makes use of reflection, reflection is known for been less then efficient, so you need to know when not to use it.警告此 API 使用反射,众所周知,反射效率较低,因此您需要知道何时不使用它。 It also "breaks" the access restrictions that protected and private apply to classes, so you can do some VERY interesting things, most of which I would discourage you from trying/doing.它还“打破”了protectedprivate应用于类的访问限制,因此您可以做一些非常有趣的事情,其中​​大部分我会劝阻您不要尝试/做。

This will also not pick up any refactoring you might do to your code, so if you rename a method, this won't see it, be careful.这也不会进行您可能对代码进行的任何重构,因此如果您重命名一个方法,这将看不到它,请小心。

Remember: This is part of bigger reflection API I wrote which allows me to execute methods on classes, so it's deliberately broken down over several classes记住:这是我编写的更大的反射 API 的一部分,它允许我在类上执行方法,所以它故意分解为几个类

SwingSafeMethodInvoker 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);

    }

}

InvokeLater稍后调用

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;

    }

}

InvokeAndWait调用和等待

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();

    }

}

InvokeAfter调用之后

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 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);
        }

    }

}

MethodInvoker方法调用者

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;

    }
}

I have already solve such boilerplate code through Proxy.我已经通过代理解决了这样的样板代码。

Your UI management abstraction:您的 UI 管理抽象:

public interface UIManager {
    void normal();
    void internalCall();
    void throwException();
}

A tracing implementation:跟踪实现:

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");
    }
}

A simple EDT-aware proxier:一个简单的 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);
    }
}

Now the main to check:现在主要检查:

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);
}

Implementation notes:实施注意事项:

  • It might be better to always call invokeLater whatever you're on EDT or not.无论您是否在 EDT 上,始终调用invokeLater可能会更好。
  • Handler always returns null and is more suitable for void methods. Handler 总是返回null ,更适合void方法。
  • You can easily implements a return aware proxy:您可以轻松实现return感知代理:
    1. Using blocking mechanism such as a SynchronousQueue使用阻塞机制,例如SynchronousQueue
    2. (work with current implementation) Introducing callback parameter (ie Consumer ) (使用当前实现)引入回调参数(即Consumer

After some readings about Lambda's I arrived with the following solution.在阅读了一些关于 Lambda 的内容后,我得到了以下解决方案。 I create a simple class with static methods with the same name as the SwingUtilites for example the invokeLater:我使用与 SwingUtilites 同名的静态方法创建了一个简单的类,例如 invokeLater:

  public static void invokeLater(Runnable doRun)
  {
    if(SwingUtilities.isEventDispatchThread())
    {
      doRun.run();      
    }
    else
    {
      SwingUtilities.invokeLater(() -> {        
        doRun.run();        
      });
    }
  }

Now using this function with Lambda is very easy assuming the new class is SwingUtilities2 (I'm still searching for a good name for the new class :-)):现在,假设新类是 SwingUtilities2(我仍在为新类寻找一个好名字 :-)),那么将此函数与 Lambda 一起使用非常容易:

SwingUtilities2.invokeLater(() -> {

  //here is the code which should run in the EDT

});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM