Java Swing 中的选框效果

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3617326/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-14 02:44:46  来源:igfitidea点击:

Marquee effect in Java Swing

javaswingmarquee

提问by Lalchand

How can I implement Marquee effect in Java Swing

如何在 Java Swing 中实现 Marquee 效果

采纳答案by trashgod

Here's an example using javax.swing.Timer.

这是一个使用javax.swing.Timer.

Marquee.png

选框.png

import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;

/** @see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {

    private void display() {
        JFrame f = new JFrame("MarqueeTest");
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        String s = "Tomorrow, and tomorrow, and tomorrow, "
        + "creeps in this petty pace from day to day, "
        + "to the last syllable of recorded time; ... "
        + "It is a tale told by an idiot, full of "
        + "sound and fury signifying nothing.";
        MarqueePanel mp = new MarqueePanel(s, 32);
        f.add(mp);
        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
        mp.start();
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                new MarqueeTest().display();
            }
        });
    }
}

/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {

    private static final int RATE = 12;
    private final Timer timer = new Timer(1000 / RATE, this);
    private final JLabel label = new JLabel();
    private final String s;
    private final int n;
    private int index;

    public MarqueePanel(String s, int n) {
        if (s == null || n < 1) {
            throw new IllegalArgumentException("Null string or n < 1");
        }
        StringBuilder sb = new StringBuilder(n);
        for (int i = 0; i < n; i++) {
            sb.append(' ');
        }
        this.s = sb + s + sb;
        this.n = n;
        label.setFont(new Font("Serif", Font.ITALIC, 36));
        label.setText(sb.toString());
        this.add(label);
    }

    public void start() {
        timer.start();
    }

    public void stop() {
        timer.stop();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        index++;
        if (index > s.length() - n) {
            index = 0;
        }
        label.setText(s.substring(index, index + n));
    }
}

回答by locka

Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.

基本答案是您将文本/图形绘制到位图中,然后实现一个将位图偏移量绘制一定量的组件。通常选取框/行情向左滚动,因此偏移量会增加,这意味着位图是在 -offset 处绘制的。您的组件运行一个定期触发的计时器,增加偏移量并使自身无效,以便重新绘制。

Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.

像包装这样的事情处理起来有点复杂,但相当简单。如果偏移量超过位图宽度,则将其重置为 0。如果偏移量 + 组件宽度 > 位图宽度,则从位图的开头开始绘制组件的其余部分。

The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.

一个体面的自动收报机的关键是使滚动尽可能平滑和无闪烁。因此,可能需要考虑对结果进行双缓冲,首先将滚动位绘制成位图,然后一次性渲染,而不是直接绘制到屏幕中。

回答by Erick Robertson

Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeControllerclass to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.

这是我拼凑起来的一些代码,以帮助您入门。我通常会将 ActionListener 代码放在某种MarqueeController类中,以使此逻辑与面板分离,但这是关于组织 MVC 架构的不同问题,在这样一个足够简单的类中,它可能并不那么重要。

There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.

还有各种动画库可以帮助您做到这一点,但我通常不喜欢将库包含到项目中只是为了解决这样的一个问题。

public class MarqueePanel extends JPanel {
  private JLabel textLabel;
  private int panelLocation;
  private ActionListener taskPerformer;
  private boolean isRunning = false;

  public static final int FRAMES_PER_SECOND = 24;
  public static final int MOVEMENT_PER_FRAME = 5;

  /**
   * Class constructor creates a marquee panel.
   */

  public MarqueePanel() {
    this.setLayout(null);
    this.textLabel = new JLabel("Scrolling Text Here");
    this.panelLocation = 0;
    this.taskPerformer = new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
        MarqueePanel.this.tickAnimation();
      }
    }
  }

  /**
   * Starts the animation.
   */

  public void start() {
    this.isRunning = true;
    this.tickAnimation();
  }

  /**
   * Stops the animation.
   */

  public void stop() {
    this.isRunning = false;
  }

  /**
   * Moves the label one frame to the left.  If it's out of display range, move it back
   * to the right, out of display range.
   */

  private void tickAnimation() {
    this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
    if (this.panelLocation < this.textLabel.getWidth())
      this.panelLocaton = this.getWidth();
    this.textLabel.setLocation(this.panelLocation, 0);
    this.repaint();
    if (this.isRunning) {
      Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
      t.setRepeats(false);
      t.start();
    }
  }
}

回答by camickr

I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.

我知道这是一个迟到的答案,但我刚刚看到另一个关于已关闭的选取框的问题,因为它被认为是此答案的重复。

So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.

所以我想我会添加我的建议,该建议采用与此处建议的其他答案不同的方法。

The MarqueePanelscrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.

MarqueePanel滚动面板不只是文本上的组件。因此,这允许您充分利用任何 Swing 组件。通过添加带有文本的 JLabel 可以使用简单的选取框。更高级的选框可能会使用带有 HTML 的 JLabel,因此您可以为文本使用不同的字体和颜色。您甚至可以添加带有图像的第二个组件。

回答by Raghunandan

Add a JLabel to your frame or panel.

将 JLabel 添加到您的框架或面板。

ScrollText s=   new ScrollText("ello Everyone.");
jLabel3.add(s);


public class ScrollText extends JComponent {
private BufferedImage image;

private Dimension imageSize;

private volatile int currOffset;

private Thread internalThread;

private volatile boolean noStopRequested;

public ScrollText(String text) {
currOffset = 0;
buildImage(text);

setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);

noStopRequested = true;
Runnable r = new Runnable() {
  public void run() {
    try {
      runWork();
    } catch (Exception x) {
      x.printStackTrace();
    }
  }
};

internalThread = new Thread(r, "ScrollText");
internalThread.start();
}

private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
    RenderingHints.KEY_ANTIALIASING,
    RenderingHints.VALUE_ANTIALIAS_ON);

renderHints.put(RenderingHints.KEY_RENDERING,
    RenderingHints.VALUE_RENDER_QUALITY);

BufferedImage scratchImage = new BufferedImage(1, 1,
    BufferedImage.TYPE_INT_RGB);

Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);

Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);

FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());

int horizontalPad = 600;
int verticalPad = 10;

imageSize = new Dimension(textWidth + horizontalPad, textHeight
    + verticalPad);

image = new BufferedImage(imageSize.width, imageSize.height,
    BufferedImage.TYPE_INT_RGB);

Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);

int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());

g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);

g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);

// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
 }
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);

int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);

// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
  }
private void runWork() {
while (noStopRequested) {
  try {
    Thread.sleep(10); // 10 frames per second

    // adjust the scroll position
    currOffset = (currOffset + 1) % imageSize.width;

    // signal the event thread to call paint()
    repaint();
  } catch (InterruptedException x) {
    Thread.currentThread().interrupt();
  }
  }
 }

public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}

public boolean isAlive() {
return internalThread.isAlive();
}


}

回答by Spiff

This is supposed to be an improvement of @camickr MarqueePanel. Please see above.

这应该是对@camickr MarqueePanel 的改进。请参阅上文。

To map mouse events to the specific components added to MarqueePanel

将鼠标事件映射到添加到 MarqueePanel 的特定组件

Override add(Component comp)of MarqueePanel in order to direct all mouse events of the components

覆盖add(Component comp)MarqueePanel 以引导组件的所有鼠标事件

An issue here is what do do with the MouseEvents fired from the individual components. My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.

这里的一个问题是如何处理从各个组件触发的 MouseEvents。我的方法是从添加的组件中删除鼠标侦听器,并让 MarqueePanel 将事件重定向到正确的组件。

In my case these components are supposed to be links.

在我的情况下,这些组件应该是链接。

    @Override
    public Component add(Component comp) {
        comp = super.add(comp);

        if(comp instanceof MouseListener)
             comp.removeMouseListener((MouseListener)comp);

        comp.addMouseListener(this);

        return comp;
    }

Then map the component x to a MarqueePanel x and finally the correct component

然后将组件 x 映射到 MarqueePanel x 并最终映射到正确的组件

@Override
public void mouseClicked(MouseEvent e)
{
    Component source = (Component)e.getSource();
    int x = source.getX() + e.getX();
    int y = source.getY();

    MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
    double x2 = marqueePanel.getWidth();
    double x1 = Math.abs(marqueePanel.scrollOffset);



    if(x >= x1 && x <= x2)
    {
        System.out.println("Bang " + x1);
        Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);

        if(comp instanceof MouseListener)
             ((MouseListener) componentAt).mouseClicked(e);

        System.out.println(componentAt.getName());
    }
    else
    {
        return;
    }


    //System.out.println(x);
}