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
相关产品推荐
相关产品推荐

