如何将事件处理程序调用统一为两个堆叠的 JComponents

How to unify a Eventhandler call onto two stacked JComponents

问题:我有一个带有图标和 RolloverIcon(悬停效果)的 JButton,为此我添加了一个 via add() 包含写入该 JButton 的字符串的 JFXPanel。我怎样才能正确地将 Rollover 转发到 JFXPanel?因为目前我在 JFXPanel 中调用 setOnMouseEntered 来改变 JButton 的背景,但是从 Rollover JButton 事件到内部 JFXPanel setOnMouseEntered 的转换并不是无缝的。

更准确的解释:当我将鼠标从包装 JButton 悬停到内部 JFXPanel 上时,JButton 的滚动尝试将按钮上的图标恢复为非滚动版本。但与此同时,新调用的 setOnMouseEntered 尝试将 JButton 图标设置为滚动版本。导致视觉 Racecondition 或只是卡顿。

我把资源和src文件打包到这个Zip-File

I also have mini-video for better understanding

我的决议是什么:

主要-Class:

package main.testbench;

import java.awt.Color;
import java.awt.Dimension;
import java.net.URL;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class MainStarter implements UrlGetter{
    
    static final double MULTI = 2.0;
    
    public static void main(String[] args) {
        java.awt.EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                frame.setPreferredSize(new Dimension((int) (400*MULTI),(int) (400*MULTI)));
                frame.setMinimumSize(new Dimension((int) (400*MULTI),(int) (400*MULTI)));
                frame.setVisible(true);  
                
                JPanel pnl = new JPanel();
                pnl.setBackground(new Color(0, 255, 0));
                
                pnl.add(new ButtonIconHover("Reisen", "test", new Dimension((int) (110*MULTI),(int) (40*MULTI))));
                
                frame.add(pnl);
            }
        });
    }
}

JButton-Class:

public class ButtonIconHover extends JButton implements UrlGetter {

    public ButtonIconHover(String display, String action, Dimension dimension) {
        super();

        super.setOpaque(false);
        super.setContentAreaFilled(false);
        super.setBorderPainted(false);  
        super.setPreferredSize(dimension);
        super.setMinimumSize(dimension);

        ImageIcon imageIcon = new ImageIcon(getURL("resources/Button.jpg"));
        ImageIcon imageIconHover = new ImageIcon(getURL("resources/Button_h.jpg"));
        
        Image image = imageIcon.getImage();
        Image newimg = image.getScaledInstance(dimension.width, dimension.height,  java.awt.Image.SCALE_SMOOTH); 
        imageIcon = new ImageIcon(newimg);
        
        Image imageHover = imageIconHover.getImage();
        Image newimgHover = imageHover.getScaledInstance(dimension.width, dimension.height,  java.awt.Image.SCALE_SMOOTH); 
        imageIconHover = new ImageIcon(newimgHover);
        
        super.setIcon(imageIcon);
        super.setRolloverIcon(imageIconHover);
        super.setPressedIcon(imageIconHover);
        super.setActionCommand(action);
        
        add(new FXButtonFont(display, dimension, this));
    }
}

JFXPanel-Class:

package main.testbench;

import java.awt.Dimension;

import javax.swing.Icon;

import javafx.embed.swing.JFXPanel;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.effect.DropShadow;
import javafx.scene.effect.Light;
import javafx.scene.effect.Lighting;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.text.Font;
import javafx.scene.text.Text;
 
public class FXButtonFont extends JFXPanel implements UrlGetter{
    public FXButtonFont(String display, Dimension dimension, ButtonIconHover buttonIconHover) {
        super();
        Light.Distant light = new Light.Distant();
        light.setAzimuth(-45.0);

        Lighting lighting = new Lighting();
        lighting.setLight(light);
        lighting.setSurfaceScale(1.5*MainStarter.MULTI);
        lighting.setSpecularConstant(2.0);
        lighting.setSpecularExponent(10.0);

        Text text = new Text(0 ,0, display);
//        text.setText(display);
        text.setFill(Color.WHITE);
        Font font = Font.loadFont(getURL("resources/fonts/Arson-Regular.ttf").toString(), 20*MainStarter.MULTI);
        text.setFont(font);

        
        DropShadow shadow = new DropShadow();
        shadow.setSpread(0);
        shadow.setColor(Color.web("0x000000", 0.7));
        shadow.setOffsetX(-2);
        shadow.setOffsetY(2);
        shadow.setWidth(4);
        shadow.setRadius(4);
        shadow.setInput(lighting);
        text.setEffect(shadow);
        
        Icon imageIcon = buttonIconHover.getIcon();
        Icon imageIconHover = buttonIconHover.getRolloverIcon();
        
          text.setOnMouseEntered((event) -> {
              buttonIconHover.setIcon(imageIconHover);
          });
          
          text.setOnMouseExited((event) -> {
              buttonIconHover.setIcon(imageIcon);
          });
        
      //Setting the stage
      StackPane root = new StackPane();
      StackPane.setAlignment(text, Pos.CENTER);
      StackPane.setMargin(text, new Insets(0, 0, 0, 0));
      root.getChildren().add(text);
      Scene scene = new Scene(root, dimension.getWidth(),dimension.getHeight(), Color.TRANSPARENT);
      setPreferredSize(dimension);
      setMinimumSize(dimension);
      this.setScene(scene);
    }
}

UrlGetter 接口:

package main.testbench;

import java.net.URL;

public interface UrlGetter {
    public default URL getURL(String resource) {
        return getClass().getClassLoader().getResource(resource);
    }
}

为了将 Swing JButton 上的图标与 JavaFX 文本无缝结合,我执行了以下操作:

  1. 将 Icon 和 RolloverIcon 加载到 JButton 上,以便图像已经加载
  2. 因为awt Image是异步的,需要加载才能改变
  3. ButtonIconHover(扩展 JButton)构造函数中创建一个 FXButtonFont(扩展 FXJPanel)对象
  4. 所有后续调用都在嵌套的 Platform.runLater 中进行,因为我们将需要 JavaFX 线程
  5. 在 JFXPanel getScene 和场景 snapshot(WritableImage)
  6. 上调用
  7. 使用SwingFXUtils.fromFXImage(WritableImage, null)WritableImage转换为BufferedImage
  8. 然后调用自己编写的 mergeImages 将两个 BufferedImages 合并为一个
  9. 现在将返回的 BufferedImage 设置为 setIconJButton

这里是修改后的 ButtonIconHover:

public ButtonIconHover(String display, String action, Dimension dimension) {
    super();

    super.setOpaque(false);
    super.setContentAreaFilled(false);
    super.setBorderPainted(false);  
    super.setPreferredSize(dimension);
    super.setMinimumSize(dimension);

    ImageIcon imageIcon = new ImageIcon(getURL("resources/Button.jpg"));
    ImageIcon imageIconHover = new ImageIcon(getURL("resources/Button_h.jpg"));
    
    Image image = imageIcon.getImage();
    Image newimg = image.getScaledInstance(dimension.width, dimension.height,  java.awt.Image.SCALE_SMOOTH); 
    
    Image imageHover = imageIconHover.getImage();
    Image newimgHover = imageHover.getScaledInstance(dimension.width, dimension.height,  java.awt.Image.SCALE_SMOOTH); 

    super.setIcon(new ImageIcon(newimg));
    super.setRolloverIcon(new ImageIcon(newimgHover));
    FXButtonFont FXBtnString = new FXButtonFont(display, dimension, false);
    FXButtonFont FXBtnStringHover = new FXButtonFont(display, dimension, true);
    ButtonIconHover thisbtn = this;
    
    Platform.runLater(new Runnable() {
        @Override
        public void run() {
            WritableImage img = new WritableImage((int)dimension.getWidth(), (int)dimension.getHeight()) ;
            WritableImage imgHover = new WritableImage((int)dimension.getWidth(), (int)dimension.getHeight()) ;
            FXBtnString.getScene().snapshot(img);
            FXBtnStringHover.getScene().snapshot(imgHover);
            
            BufferedImage FXBtnStringConverted = SwingFXUtils.fromFXImage(img, null);
            BufferedImage FXBtnStringConvertedHover = SwingFXUtils.fromFXImage(imgHover, null);
            
            BufferedImage combined = ImageUtil.mergeImages(ImageUtil.convertToBufferedImage(newimg), FXBtnStringConverted);
            BufferedImage combinedRollPress = ImageUtil.mergeImages(ImageUtil.convertToBufferedImage(newimgHover), FXBtnStringConvertedHover);

            ImageIcon nonConverted = new ImageIcon(combined);
            ImageIcon rollPress = new ImageIcon(combinedRollPress);
            thisbtn.setIcon(nonConverted);
            thisbtn.setRolloverIcon(rollPress);
            thisbtn.setPressedIcon(rollPress);
        }
    });  
    
}