简体   繁体   English

如何从扩展线程的类开始在JPanel中创建动画(从一个点移动一个圆圈到另一个点)?

[英]How do I start an animation (move a circle from one point to another) in a JPanel from a class that extends thread?

I have a class called Player that extends thread, its parameter is a JPanel (called DrawPanel ) and the coordinates x, y; 我有一个名为Player的类,它扩展了线程,它的参数是一个JPanel (称为DrawPanel )和坐标x,y; in the Player constructor I draw a circle in position x, y on the panel (I don't know how correct it is). Player构造函数中,我在面板上的x,y位置画一个圆圈(我不知道它是多么正确)。

In the Player run function, I would like to start an animation that moves a small red circle from the player coordinates to an other point. Player运行功能中,我想启动一个动画,将一个小红圈从玩家坐标移动到另一个点。

like in this animation. 喜欢这个动画。

How can I do this? 我怎样才能做到这一点?

public class Player extends Thread {
 private  Graphics graphic;
 private Graphics2D g2;
 private int x;
 private int y;
 private DrawPanel panel;
    public Player(int x, int y,DrawPanel panel) 
    {
        this.x = x;
        this.y = y;
        this.panel = panel;
        graphic = panel.getGraphics();
        g2 = (Graphics2D) graphic;
        g2.fillOval( column,row, 10, 10);
    }
    public void run()
    {
     //startAnimation(this.x,this.y,destination.x,destination.y)
    }
}

I just want to start with, animation is not easy and good animation is hard. 我只想开始,动画并不容易,好动画很难。 There is a lot of theory that goes into making animation "look" good, which I'm not going to cover here, there are better people and resources for that. 有很多理论可以让动画“看起来很好”,我不会在这里介绍,有更好的人和资源。

What I am going to discuss is how you can do "good" animation in Swing at a basic level. 我将要讨论的是如何在基本级别上进行Swing中的“好”动画。

The first problem seems to be the fact that you don't have a good understanding of how painting works in Swing. 第一个问题似乎是你没有很好地理解绘画在Swing中是如何工作的。 You should start by reading Performing Custom Painting in Swing and Painting in Swing 你应该首先阅读Swing中的SwingPainting中的 Performing Custom Painting

Next, you don't seem to realise that Swing is actually NOT thread safe (and is single threaded). 接下来,您似乎没有意识到Swing实际上不是线程安全的(并且是单线程的)。 This means you should never update the UI or any state the UI relies on from outside the context of the Event Dispatching Thread. 这意味着您永远不应该在事件调度线程的上下文之外更新UI或UI所依赖的任何状态。 See Concurrency in Swing for more details. 有关更多详细信息,请参阅Swing中的并发

The simplest solution to solving this problem is to use a Swing Timer , see How to Use Swing Timers for more details. 解决此问题的最简单方法是使用Swing Timer ,有关详细信息,请参阅如何使用Swing Timers

Now, you could simply run a Timer and do a straight, linear progression until all your points meet their target, but this is not always the best solution, as it doesn't scale well and will appear different on different PC's, based on there individual capabilities. 现在,您可以简单地运行一个Timer并进行直线,线性进展,直到所有点都达到目标,但这并不总是最好的解决方案,因为它不能很好地扩展,并且在不同的PC上看起来会有所不同,基于那里个人能力。

In most cases, a duration based animation gives a better result. 在大多数情况下,基于持续时间的动画会产生更好的结果。 This allows the algorithm to "drop" frames when the PC is unable to keep up. 这允许算法在PC无法跟上时“丢弃”帧。 It scales much better (of time and distance) and can be highly configurable. 它可以更好地(时间和距离)扩展,并且可以高度配置。

I like to produce re-usable blocks of code, so I will start with a simple "duration based animation engine"... 我喜欢生成可重复使用的代码块,所以我将从一个简单的“基于持续时间的动画引擎”开始......

// Self contained, duration based, animation engine...
public class AnimationEngine {

    private Instant startTime;
    private Duration duration;

    private Timer timer;

    private AnimationEngineListener listener;

    public AnimationEngine(Duration duration) {
        this.duration = duration;
    }

    public void start() {
        if (timer != null) {
            return;
        }
        startTime = null;
        timer = new Timer(5, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                tick();
            }
        });
        timer.start();
    }

    public void stop() {
        timer.stop();
        timer = null;
        startTime = null;
    }

    public void setListener(AnimationEngineListener listener) {
        this.listener = listener;
    }

    public AnimationEngineListener getListener() {
        return listener;
    }

    public Duration getDuration() {
        return duration;
    }

    public double getRawProgress() {
        if (startTime == null) {
            return 0.0;
        }
        Duration duration = getDuration();
        Duration runningTime = Duration.between(startTime, Instant.now());
        double progress = (runningTime.toMillis() / (double) duration.toMillis());

        return Math.min(1.0, Math.max(0.0, progress));
    }

    protected void tick() {
        if (startTime == null) {
            startTime = Instant.now();
        }
        double rawProgress = getRawProgress();
        if (rawProgress >= 1.0) {
            rawProgress = 1.0;
        }

        AnimationEngineListener listener = getListener();
        if (listener != null) {
            listener.animationEngineTicked(this, rawProgress);
        }

        // This is done so if you wish to expand the 
        // animation listener to include start/stop events
        // this won't interfer with the tick event
        if (rawProgress >= 1.0) {
            rawProgress = 1.0;
            stop();
        }
    }

    public static interface AnimationEngineListener {

        public void animationEngineTicked(AnimationEngine source, double progress);
    }
}

It's not overly complicated, it has a duration of time, over which it will run. 它不是太复杂,它有一段duration ,它将运行。 It will tick at a regular interval (of no less then 5 milliseconds) and will generate tick events, reporting the current progression of the animation (as a normalised value of between 0 and 1). tick在(不小于5毫秒)以规则的间隔,并且将生成tick事件,报告了动画的当前进展(如0之间的归一化值和1)。

The idea here is we decouple the "engine" from those elements which are using it. 这里的想法是我们将“引擎”与正在使用它的那些元素分离。 This allows us to use it for a much wider range of possibilities. 这使我们能够将它用于更广泛的可能性。

Next, I need some way to keep track of the position of my moving object... 接下来,我需要一些方法来跟踪我移动物体的位置......

public class Ping {

    private Point point;
    private Point from;
    private Point to;
    private Color fillColor;

    private Shape dot;

    public Ping(Point from, Point to, Color fillColor) {
        this.from = from;
        this.to = to;
        this.fillColor = fillColor;
        point = new Point(from);

        dot = new Ellipse2D.Double(0, 0, 6, 6);
    }

    public void paint(Container parent, Graphics2D g2d) {
        Graphics2D copy = (Graphics2D) g2d.create();
        int width = dot.getBounds().width / 2;
        int height = dot.getBounds().height / 2;
        copy.translate(point.x - width, point.y - height);
        copy.setColor(fillColor);
        copy.fill(dot);
        copy.dispose();
    }

    public Rectangle getBounds() {
        int width = dot.getBounds().width;
        int height = dot.getBounds().height;

        return new Rectangle(point, new Dimension(width, height));
    }

    public void update(double progress) {
        int x = update(progress, from.x, to.x);
        int y = update(progress, from.y, to.y);

        point.x = x;
        point.y = y;
    }

    protected int update(double progress, int from, int to) {
        int distance = to - from;
        int value = (int) Math.round((double) distance * progress);
        value += from;
        if (from < to) {
            value = Math.max(from, Math.min(to, value));
        } else {
            value = Math.max(to, Math.min(from, value));
        }

        return value;
    }
}

This is a simply object which takes the start and end points and then calculates the position of the object between these points based on the progression. 这是一个简单的对象,它获取起点和终点,然后根据进展计算这些点之间的对象位置。 It can the paint itself when requested. 它可以根据要求涂漆。

Now, we just need some way to put it together... 现在,我们只需要一些方法将它组合在一起......

public class TestPane extends JPanel {

    private Point source;
    private Shape sourceShape;
    private List<Ping> pings;
    private List<Shape> destinations;

    private Color[] colors = new Color[]{Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};

    private AnimationEngine engine;

    public TestPane() {
        source = new Point(10, 10);
        sourceShape = new Ellipse2D.Double(source.x - 5, source.y - 5, 10, 10);

        Dimension size = getPreferredSize();

        Random rnd = new Random();
        int quantity = 1 + rnd.nextInt(10);
        pings = new ArrayList<>(quantity);
        destinations = new ArrayList<>(quantity);
        for (int index = 0; index < quantity; index++) {
            int x = 20 + rnd.nextInt(size.width - 25);
            int y = 20 + rnd.nextInt(size.height - 25);

            Point toPoint = new Point(x, y);

            // Create the "ping"
            Color color = colors[rnd.nextInt(colors.length)];
            Ping ping = new Ping(source, toPoint, color);
            pings.add(ping);

            // Create the destination shape...
            Rectangle bounds = ping.getBounds();
            Shape destination = new Ellipse2D.Double(toPoint.x - (bounds.width / 2d), toPoint.y - (bounds.height / 2d), 10, 10);
            destinations.add(destination);
        }

        engine = new AnimationEngine(Duration.ofSeconds(10));
        engine.setListener(new AnimationEngine.AnimationEngineListener() {
            @Override
            public void animationEngineTicked(AnimationEngine source, double progress) {
                for (Ping ping : pings) {
                    ping.update(progress);
                }
                repaint();
            }
        });
        engine.start();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(200, 200);
    }

    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g.create();

        // This is probably overkill, but it will make the output look nicer ;)
        g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
        g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

        // Lines first, these could be cached
        g2d.setColor(Color.LIGHT_GRAY);
        double fromX = sourceShape.getBounds2D().getCenterX();
        double fromY = sourceShape.getBounds2D().getCenterY();
        for (Shape destination : destinations) {
            double toX = destination.getBounds2D().getCenterX();
            double toY = destination.getBounds2D().getCenterY();
            g2d.draw(new Line2D.Double(fromX, fromY, toX, toY));
        }

        // Pings, so they appear above the line, but under the points
        for (Ping ping : pings) {
            ping.paint(this, g2d);
        }

        // Destination and source
        g2d.setColor(Color.BLACK);
        for (Shape destination : destinations) {
            g2d.fill(destination);
        }

        g2d.fill(sourceShape);

        g2d.dispose();
    }

}

Okay, this "looks" complicated, but it's really simple. 好吧,这看起来很“复杂”,但它非常简单。

  • We create a "source" point 我们创造了一个“源”点
  • We then create a random number of "targets" 然后我们创建一个随机数的“目标”
  • We then create a animation engine and start it. 然后我们创建一个动画引擎并启动它。

The animation engine will then loop through all the Ping s and update them based on the current progress value and trigger a new paint pass, which then paints the lines between the source and target points, paints the Ping s and then finally the source and all the target points. 然后,动画引擎将循环遍历所有Ping并根据当前进度值更新它们并触发新的绘制过程,然后绘制源点和目标点之间的线,绘制Ping ,然后最后绘制源和所有目标点。 Simple. 简单。

What if I want the animation to run at different speeds? 如果我希望动画以不同的速度运行怎么办?

Ah, well, this is much more complicated and requires a more complex animation engine. 啊,嗯,这要复杂得多,需要更复杂的动画引擎。

Generally speaking, you could establish a concept of something which is "animatable". 一般来说,你可以建立一个“动画”的概念。 This would then be updated by a central "engine" which continuously "ticked" (wasn't itself constrained to duration). 然后,这将由一个连续“勾选”的中央“引擎”更新(本身并不局限于持续时间)。

Each "animatable" would then need to make decisions about how it was going to update or report its state and allow other objects to be updated. 然后,每个“动画”都需要决定如何更新或报告其状态并允许更新其他对象。

In this case, I'd be looking towards a more ready made solution, for example... 在这种情况下,我会寻求更现成的解决方案,例如......

Runnable example.... 可运行的例子....

平我

import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class JavaApplication124 {

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

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

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new TestPane());
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
            }
        });
    }

    public class TestPane extends JPanel {

        private Point source;
        private Shape sourceShape;
        private List<Ping> pings;
        private List<Shape> destinations;

        private Color[] colors = new Color[]{Color.BLACK, Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};

        private AnimationEngine engine;

        public TestPane() {
            source = new Point(10, 10);
            sourceShape = new Ellipse2D.Double(source.x - 5, source.y - 5, 10, 10);

            Dimension size = getPreferredSize();

            Random rnd = new Random();
            int quantity = 1 + rnd.nextInt(10);
            pings = new ArrayList<>(quantity);
            destinations = new ArrayList<>(quantity);
            for (int index = 0; index < quantity; index++) {
                int x = 20 + rnd.nextInt(size.width - 25);
                int y = 20 + rnd.nextInt(size.height - 25);

                Point toPoint = new Point(x, y);

                // Create the "ping"
                Color color = colors[rnd.nextInt(colors.length)];
                Ping ping = new Ping(source, toPoint, color);
                pings.add(ping);

                // Create the destination shape...
                Rectangle bounds = ping.getBounds();
                Shape destination = new Ellipse2D.Double(toPoint.x - (bounds.width / 2d), toPoint.y - (bounds.height / 2d), 10, 10);
                destinations.add(destination);
            }

            engine = new AnimationEngine(Duration.ofSeconds(10));
            engine.setListener(new AnimationEngine.AnimationEngineListener() {
                @Override
                public void animationEngineTicked(AnimationEngine source, double progress) {
                    for (Ping ping : pings) {
                        ping.update(progress);
                    }
                    repaint();
                }
            });
            engine.start();
        }

        @Override
        public Dimension getPreferredSize() {
            return new Dimension(200, 200);
        }

        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g.create();

            // This is probably overkill, but it will make the output look nicer ;)
            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);

            // Lines first, these could be cached
            g2d.setColor(Color.LIGHT_GRAY);
            double fromX = sourceShape.getBounds2D().getCenterX();
            double fromY = sourceShape.getBounds2D().getCenterY();
            for (Shape destination : destinations) {
                double toX = destination.getBounds2D().getCenterX();
                double toY = destination.getBounds2D().getCenterY();
                g2d.draw(new Line2D.Double(fromX, fromY, toX, toY));
            }

            // Pings, so they appear above the line, but under the points
            for (Ping ping : pings) {
                ping.paint(this, g2d);
            }

            // Destination and source
            g2d.setColor(Color.BLACK);
            for (Shape destination : destinations) {
                g2d.fill(destination);
            }

            g2d.fill(sourceShape);

            g2d.dispose();
        }

    }

    // Self contained, duration based, animation engine...
    public static class AnimationEngine {

        private Instant startTime;
        private Duration duration;

        private Timer timer;

        private AnimationEngineListener listener;

        public AnimationEngine(Duration duration) {
            this.duration = duration;
        }

        public void start() {
            if (timer != null) {
                return;
            }
            startTime = null;
            timer = new Timer(5, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    tick();
                }
            });
            timer.start();
        }

        public void stop() {
            timer.stop();
            timer = null;
            startTime = null;
        }

        public void setListener(AnimationEngineListener listener) {
            this.listener = listener;
        }

        public AnimationEngineListener getListener() {
            return listener;
        }

        public Duration getDuration() {
            return duration;
        }

        public double getRawProgress() {
            if (startTime == null) {
                return 0.0;
            }
            Duration duration = getDuration();
            Duration runningTime = Duration.between(startTime, Instant.now());
            double progress = (runningTime.toMillis() / (double) duration.toMillis());

            return Math.min(1.0, Math.max(0.0, progress));
        }

        protected void tick() {
            if (startTime == null) {
                startTime = Instant.now();
            }
            double rawProgress = getRawProgress();
            if (rawProgress >= 1.0) {
                rawProgress = 1.0;
            }

            AnimationEngineListener listener = getListener();
            if (listener != null) {
                listener.animationEngineTicked(this, rawProgress);
            }

            // This is done so if you wish to expand the 
            // animation listener to include start/stop events
            // this won't interfer with the tick event
            if (rawProgress >= 1.0) {
                rawProgress = 1.0;
                stop();
            }
        }

        public static interface AnimationEngineListener {

            public void animationEngineTicked(AnimationEngine source, double progress);
        }
    }

    public class Ping {

        private Point point;
        private Point from;
        private Point to;
        private Color fillColor;

        private Shape dot;

        public Ping(Point from, Point to, Color fillColor) {
            this.from = from;
            this.to = to;
            this.fillColor = fillColor;
            point = new Point(from);

            dot = new Ellipse2D.Double(0, 0, 6, 6);
        }

        public void paint(Container parent, Graphics2D g2d) {
            Graphics2D copy = (Graphics2D) g2d.create();
            int width = dot.getBounds().width / 2;
            int height = dot.getBounds().height / 2;
            copy.translate(point.x - width, point.y - height);
            copy.setColor(fillColor);
            copy.fill(dot);
            copy.dispose();
        }

        public Rectangle getBounds() {
            int width = dot.getBounds().width;
            int height = dot.getBounds().height;

            return new Rectangle(point, new Dimension(width, height));
        }

        public void update(double progress) {
            int x = update(progress, from.x, to.x);
            int y = update(progress, from.y, to.y);

            point.x = x;
            point.y = y;
        }

        protected int update(double progress, int from, int to) {
            int distance = to - from;
            int value = (int) Math.round((double) distance * progress);
            value += from;
            if (from < to) {
                value = Math.max(from, Math.min(to, value));
            } else {
                value = Math.max(to, Math.min(from, value));
            }

            return value;
        }
    }

}

Isn't there something simpler 😓 是不是有更简单的东西😓

As I said, good animation, is hard. 正如我所说,好动画,很难。 It takes a lot of effort and planning to do well. 需要付出很多努力才能做好。 I've not even talked about easement, chained or blending algorithms, so trust me when I say, this is actually a simple, reusable solution 我甚至没有谈论缓和,链接或混合算法,所以当我说,相信我,这实际上是一个简单,可重复使用的解决方案

Don't believe me, check out JButton hover animation in Java Swing 不要相信我, 在Java Swing中查看JButton悬停动画

Try this one : 试试这个:

public class Player extends Thread {
 private  Graphics graphic;
 private Graphics2D g2;
 private int x;
 private int y;
 private DrawPanel panel;
 private numberOfIteration=5;
 private currentNumberOfIteration=0;
    public Player(int x, int y,DrawPanel panel) 
    {
        this.x = x;
        this.y = y;
        this.panel = panel;
        graphic = panel.getGraphics();
        g2 = (Graphics2D) graphic;
        g2.fillOval( column,row, 10, 10);
    }
    public Player(int x, int y,DrawPanel panel,int numberOfIteration) 
    {
        this.x = x;
        this.y = y;
        this.panel = panel;
        this.numberOfIteration=numberOfIterarion;
        graphic = panel.getGraphics();
        g2 = (Graphics2D) graphic;
        g2.fillOval( column,row, 10, 10);
    }
    public void run()
    {
     //startAnimation(this.x,this.y,destination.x,destination.y)
        currentNumberOfIteration=(++currentNumberOfIteration)%numberOfIteration;
        currentX=(int)((destinationX*currentNumberOfIteration+this.x)/(currentNumberOfIteration+1));
        currentY=(int)((destinationY*currentNumberOfIteration+this.y)/(currentNumberOfIteration+1));
        g2.fillOval( currentX,currentY, 10, 10);

    }
}

I don't see any declaration part of destinationX and destinationY . 我没有看到destinationXdestinationY任何声明部分。

暂无
暂无

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

相关问题 如何获取扩展 JPanel 以显示在另一个 JPanel 类中的类的实例? - How do I get an instance of a class that extends a JPanel to display in another classes JPanel? 我如何在一个JFrame中添加两个扩展JPanel的类 - How can i add two class that extends JPanel in One JFrame 如何从另一个线程中断一个线程中的循环? - How do I break a loop in one Thread from another Thread? 如何指示泛型类从一个或另一个类扩展? - How can I indicate that a generic class extends from one class or another? Java:如何在另一个扩展Thread的类中调用方法,并且该方法是我的主类中的Thread Array的一部分? - Java : How do I call a method in a different class that extends Thread and is a part of a Thread Array from my main class? 如何将 JPanel 从应用程序的东侧移动到西侧? - How do I move a JPanel from the East side of an application to the West? 我可以从扩展JFrame而不是JPanel的类中调用componentShown()方法吗? 如果是,怎么办? - Can I call componentShown() method from a class that extends JFrame instead of JPanel ? If yes, how? 在Java中,如果我调用一个从另一个可运行对象扩展Thread的类,哪个线程将执行? - In Java, if I call a class that extends Thread from another runnable object, which Thread executes? 将一个 JPanel 放到另一个来自不同 Java 类的 JPanel 上 - Put one JPanel onto another JPanel from a different Java class 我如何在不扩展的情况下将价值从一门课转移到另一门课? - How can I get value from one class to another without extends it?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM