You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Vaadin中从MainView向Tab内容组件传递SvgIcon实例的问题

问题描述

我尝试从MainView向作为Tab内容的独立Java类传递两个SvgIcon以实现图标修改,但编写的方法无法正常工作:

public SvgIcon getSvgIcon(String classSvgIcon) {
    return (SvgIcon) CurrentInstance.get(SvgIcon.class).findAncestor(SvgIcon.class).hasClassName(classSvgIcon);
} 

我原本预期该方法会返回对应类名的SvgIcon元素(更倾向通过ID查找,但发现没有hasIdAttribute()方法),但实际无法运行。

随后我尝试通过获取MainView的UI实例直接访问目标组件,代码如下:

public static MainView getCurrent() {
    return (MainView) CurrentInstance.get(UI.class);
}

虽然在Tab内容类中可以调用MainView.getCurrent().myIconElement,但编译器提示Cannot cast from UI to MainView错误。请问如何正确改写该方法,实现对目标SvgIcon的访问与修改?

示例代码

MainView类

/* ************************************************************ 
 *                                                            *
 *          MainView class                                    *
 *                                                            *
 ************************************************************ */
package com.myexample.application.views.main;

import java.util.HashMap;
import java.util.Map;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.html.Image;
import com.vaadin.flow.component.html.NativeLabel;
import com.vaadin.flow.component.icon.Icon;
import com.vaadin.flow.component.icon.SvgIcon;
import com.vaadin.flow.component.icon.VaadinIcon;
import com.vaadin.flow.component.orderedlayout.FlexComponent;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.tabs.Tab;
import com.vaadin.flow.component.tabs.Tabs;
import com.vaadin.flow.component.tabs.TabsVariant;
import com.vaadin.flow.internal.CurrentInstance;
import com.vaadin.flow.router.BeforeEnterEvent;
import com.vaadin.flow.router.BeforeEnterObserver;
import com.vaadin.flow.router.HighlightConditions;

import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouterLayout;
import com.vaadin.flow.router.RouterLink;
import com.vaadin.flow.server.StreamResource;

@PageTitle("Main")
@Route("")
public class MainView extends VerticalLayout implements RouterLayout, BeforeEnterObserver {

    private Map<Tab, RouterLink> tabSheets = new HashMap<>();
    private Tabs mainTabs; 

    private HorizontalLayout toolbar;       
    private NativeLabel appname;
    public SvgIcon iconToChange;

    public MainView() {
        
        RouteTabs tabsToAdd = new RouteTabs();
        tabsToAdd.addTab(new RouterLink("Tab 1", MyTab1.class), VaadinIcon.EYE.create());
        tabsToAdd.addTab(new RouterLink("Tab 2", MyTab2.class), VaadinIcon.EYE.create());
        
        add(getToolbar(), tabsToAdd);               
    }
    
    @Override
    public void beforeEnter(BeforeEnterEvent event) {
        if (event.getNavigationTarget() == MainView.class) {
            event.forwardTo(MyTab1.class);
        }       
    }    

    public static MainView getCurrent() {
        return (MainView) CurrentInstance.get(UI.class).getCurrent();       
        //return CurrentInstance.get(UI.class);
        //return UI.getCurrent();
    }    
    
    public SvgIcon getIconToChange() { return (SvgIcon) iconToChange; }
    
    private HorizontalLayout getToolbar() {                     
        
        HorizontalLayout leftLayout = new HorizontalLayout();
        
        appname = new NativeLabel("My Test");
        appname.setClassName("title-style");
        
        leftLayout.add(appname);
        leftLayout.setAlignItems(FlexComponent.Alignment.START);
        leftLayout.setWidth("100%");
                
        HorizontalLayout rightLayout = new HorizontalLayout();
        
        StreamResource svgIconResource = new StreamResource("myOff_Icon.svg",
                () -> getClass().getResourceAsStream("/img/myOff_Icon.svg"));
        svgIconResource.setCacheTime(0);        
        iconToChange = new SvgIcon(svgIconResource);
        iconToChange.addClassName("iconToChange");
        iconToChange.setId("iconToChange");
        iconToChange.setTooltipText("IconToChange");
        
        rightLayout.add(iconToChange);
        rightLayout.setJustifyContentMode(FlexComponent.JustifyContentMode.END);
        rightLayout.setWidth("100%");

        var toolbar = new HorizontalLayout(leftLayout, rightLayout);
        toolbar.setWidthFull();
        toolbar.addClassName("toolbar");      
                
        return toolbar;
    }    
    
    
    private static class RouteTabs extends Tabs implements BeforeEnterObserver {
        private final Map<RouterLink, Tab> routerLinkTabMap = new HashMap<>();
        
        public void addTab(RouterLink routerLink, Icon iconToAdd) {
            routerLink.setHighlightCondition(HighlightConditions.sameLocation());
            routerLink.setHighlightAction(
                (link, shouldHighlight) -> {
                    if (shouldHighlight) setSelectedTab(routerLinkTabMap.get(routerLink));
                }             
            );            
            routerLinkTabMap.put(routerLink, new Tab(iconToAdd, routerLink));
            add(routerLinkTabMap.get(routerLink));
            this.addThemeVariants(TabsVariant.LUMO_EQUAL_WIDTH_TABS);
            this.setMaxWidth("100%");
            this.setWidthFull();
        }

        @Override
        public void beforeEnter(BeforeEnterEvent event) {
            // In case no tabs will match
            setSelectedTab(null);
        }
    }
    
    /*public SvgIcon getSvgIcon(String idSvgIcon) {
        //return (SvgIcon) CurrentInstance.get(SvgIcon.class).equals(getElement().getAttribute("id").equals(idSvgIcon));
        //return (SvgIcon) CurrentInstance.get(SvgIcon.class).findAncestor(SvgIcon.class).hasClassName(idSvgIcon);
        return (SvgIcon) CurrentInstance.get(SvgIcon.class).getElement().hasAttribute("id");
    } */
    
}

MyTab1类

/* ************************************************************ 
 *                                                            *
 *          MyTab1.class class                                *
 *                                                            *
 ************************************************************ */
 
 package com.myexample.application.views.main;

import com.vaadin.flow.component.Text;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.icon.SvgIcon;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.server.StreamResource;

@Route(value="mytab1", layout = MainView.class)
public class MyTab1 extends VerticalLayout {
    
    private SvgIcon iconChangeTo;
    private Text localText;
    private Button localButton;
        
    public MyTab1() {                    
        localText = new Text("My Tab 1");
            
        localButton = new Button("Change Icon");
        localButton.addClickListener(e -> {
            //iconChangeTo = Element.getAttribute("id").
            //iconChangeTo = UI.getCurrent().navigate(SvgIcon.class).ifPresent(icon -> icon.hasClassName("iconToChange")).      
            
            //SvgIcon icon = ((MainView) MainView.getCurrent()).getIconToChange();
            //MainView.getCurrent()

            SvgIcon icon = MainView.getCurrent().getIconToChange();
            
            
            //UIclass.iconToChange.setSrc( new StreamResource("myON_Icon.svg",
            //  () -> getClass().getResourceAsStream("/img/myON_Icon.svg")) );
        });
                
        add(localText, localButton);
    }   
}

注:原代码中MyTab1的构造函数名为ARDevices,此处修正为MyTab1以符合类名规范。

解决方案

1. 修正getCurrent()方法

CurrentInstance.get(UI.class)返回的是UI实例,而MainView是UI下的布局组件,不能直接强制类型转换。正确做法是从当前UI的组件树中查找MainView:

public static MainView getCurrent() {
    UI currentUI = UI.getCurrent();
    if (currentUI != null) {
        // 遍历UI的根组件,找到MainView实例
        return currentUI.getChildren()
                .filter(component -> component instanceof MainView)
                .findFirst()
                .map(MainView.class::cast)
                .orElse(null);
    }
    return null;
}

调用时需判空,避免空指针:

localButton.addClickListener(e -> {
    MainView mainView = MainView.getCurrent();
    if (mainView != null) {
        SvgIcon icon = mainView.getIconToChange();
        StreamResource newIcon = new StreamResource("myON_Icon.svg",
                () -> getClass().getResourceAsStream("/img/myON_Icon.svg"));
        newIcon.setCacheTime(0);
        icon.setSrc(newIcon);
    }
});

2. 利用RouterLayout获取父布局

由于MainView是MyTab1的路由布局,可以直接通过UI的getRouterLayout方法获取,无需静态方法:

localButton.addClickListener(e -> {
    getUI().ifPresent(ui -> {
        MainView mainView = ui.getRouterLayout(MainView.class);
        if (mainView != null) {
            SvgIcon icon = mainView.getIconToChange();
            StreamResource newIcon = new StreamResource("myON_Icon.svg",
                    () -> getClass().getResourceAsStream("/img/myON_Icon.svg"));
            newIcon.setCacheTime(0);
            icon.setSrc(newIcon);
        }
    });
});

3. 通过ID查找组件(推荐)

既然已经给SvgIcon设置了IDiconToChange,可以直接通过UI的findElement方法查找,无需依赖MainView的实例:

import com.vaadin.flow.dom.By;
import com.vaadin.flow.component.Component;

// ...

localButton.addClickListener(e -> {
    getUI().ifPresent(ui -> {
        ui.findElement(By.id("iconToChange"))
                .map(element -> (SvgIcon) Component.fromElement(element))
                .ifPresent(icon -> {
                    StreamResource newIcon = new StreamResource("myON_Icon.svg",
                            () -> getClass().getResourceAsStream("/img/myON_Icon.svg"));
                    newIcon.setCacheTime(0);
                    icon.setSrc(newIcon);
                });
    });
});

注意:确保组件ID全局唯一,且组件已附加到UI后再执行查找。

4. 解耦的事件驱动方式(最佳实践)

为避免组件间直接耦合,推荐使用Vaadin的事件总线实现通信:

定义事件类

package com.myexample.application.views.main;

import com.vaadin.flow.component.ComponentEvent;
import com.vaadin.flow.server.StreamResource;

public class IconUpdateEvent extends ComponentEvent<MainView> {
    private final StreamResource newIconResource;

    public IconUpdateEvent(MainView source, StreamResource newIconResource) {
        super(source, false);
        this.newIconResource = newIconResource;
    }

    public StreamResource getNewIconResource() {
        return newIconResource;
    }
}

在MainView中注册事件监听器

public MainView() {
    // ... 现有代码
    // 注册事件监听器
    addListener(IconUpdateEvent.class, event -> {
        iconToChange.setSrc(event.getNewIconResource());
    });
}

在MyTab1中触发事件

localButton.addClickListener(e -> {
    getUI().ifPresent(ui -> {
        MainView mainView = ui.getRouterLayout(MainView.class);
        if (mainView != null) {
            StreamResource newIcon = new StreamResource("myON_Icon.svg",
                    () -> getClass().getResourceAsStream("/img/myON_Icon.svg"));
            newIcon.setCacheTime(0);
            // 触发事件
            mainView.fireEvent(new IconUpdateEvent(mainView, newIcon));
        }
    });
});

内容的提问来源于stack exchange,提问作者Luigino

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 18:19:50