使用活动编辑器的网格上的Vaadin双击按钮会抛出异常(“java.lang.NullPointerException:Editor无法编辑null”)

时间:2018-02-16 10:46:29

标签: java vaadin vaadin8 vaadin-grid

我有一个带有主动缓冲编辑器的vaadin网格。默认情况下,双击一行时会打开编辑器。一切正常,除非我双击按钮我得到一个例外:(异常并没有指向我的代码)

java.lang.NullPointerException: Editor can't edit null
at java.base/java.util.Objects.requireNonNull(Objects.java:246)
at com.vaadin.ui.components.grid.EditorImpl.doEdit(EditorImpl.java:216)
at com.vaadin.ui.components.grid.EditorImpl$1.bind(EditorImpl.java:151)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:155)
at com.vaadin.server.ServerRpcManager.applyInvocation(ServerRpcManager.java:116)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocation(ServerRpcHandler.java:445)
at com.vaadin.server.communication.ServerRpcHandler.handleInvocations(ServerRpcHandler.java:410)
at com.vaadin.server.communication.ServerRpcHandler.handleRpc(ServerRpcHandler.java:274)
at com.vaadin.server.communication.PushHandler.lambda$new$1(PushHandler.java:145)
at com.vaadin.server.communication.PushHandler.callWithUi(PushHandler.java:235)
at com.vaadin.server.communication.PushHandler.onMessage(PushHandler.java:520)
at com.vaadin.server.communication.PushAtmosphereHandler.onMessage(PushAtmosphereHandler.java:87)
at com.vaadin.server.communication.PushAtmosphereHandler.onRequest(PushAtmosphereHandler.java:77)
at org.atmosphere.cpr.AsynchronousProcessor.action(AsynchronousProcessor.java:223)
at org.atmosphere.cpr.AsynchronousProcessor.suspended(AsynchronousProcessor.java:115)
at org.atmosphere.container.Servlet30CometSupport.service(Servlet30CometSupport.java:67)
at org.atmosphere.cpr.AtmosphereFramework.doCometSupport(AtmosphereFramework.java:2284)
at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:593)
at org.atmosphere.websocket.DefaultWebSocketProcessor$3.run(DefaultWebSocketProcessor.java:345)
at org.atmosphere.util.VoidExecutorService.execute(VoidExecutorService.java:101)
at org.atmosphere.websocket.DefaultWebSocketProcessor.dispatch(DefaultWebSocketProcessor.java:340)
at org.atmosphere.websocket.DefaultWebSocketProcessor.invokeWebSocketProtocol(DefaultWebSocketProcessor.java:447)
at org.atmosphere.container.JSR356Endpoint$3.onMessage(JSR356Endpoint.java:272)
at org.atmosphere.container.JSR356Endpoint$3.onMessage(JSR356Endpoint.java:269)
at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:395)
at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:119)
at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:495)
at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:294)
at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:133)
at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:82)
at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:171)
at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:151)
at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:148)
at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:54)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:53)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:868)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.base/java.lang.Thread.run(Thread.java:844)

这就是我将Button添加到网格的方式:

// Adding the column
    grid.addComponentColumn(this::buildAddButton);

这是返回每行按钮的方法:

// Building the button
private Button buildAddButton(ProductTemplate p) {
    Button button = new Button("add");
    // Configurate the button
    ...
    return button;
}

不幸的是,我发现这个问题很少...... 由于vaadin按钮不会停止点击事件传播,我试过这个:

  1. 通过从布局中删除单击侦听器,将按钮添加到布局以“阻止”单击事件。

    // Adding the column 
        grid.addComponentColumn(this::buildAddLayout);
    

    构建布局而不只是按钮:

    // Building the layout with the button
    private VerticalLayout buildAddLayout(ProductTemplate p) {
        Button button = new Button("add");
    
        // Configurate the button
        ...
    
        VerticalLayout layout = new VerticalLayout();
        layout.addComponent(button);
        layout.getListeners(Event.class).clear();
    
        return layout;
    }
    
  2. 单击时禁用该按钮,直到其任务为止 完成(禁止双击按钮)。

    // Building the button
    private Button buildAddButton(ProductTemplate p) {
        Button button = new Button("add");
        // Configurate the button
            ...
        button.addClickListener(e -> {
            button.setEnabled(false);
            buttonClicked(p);
            button.setEnabled(true);
        });        
        return button;
    }
    
  3. 两者都没能让我摆脱异常。任何建议,如何在启用编辑器双击网格上的按钮时如何防止此类异常?

    编辑:

    编辑器正在尝试编辑按钮的值,这显然是不可能的。我想阻止编辑这样做。 (“无法编辑null”表示vaadin无法从按钮中创建有效的bean)

    编辑2:我之前在第一次编辑中做出的假设似乎是错误的。我的按钮单击是刷新网格,我无法阻止编辑器尝试编辑行,即使所有项目都被删除并重新加载。

    这是一个类和一个pom,你可以用它来重现异常(点击按钮的速度非常快):

    MyUI.java

    package com.example.sample;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.servlet.annotation.WebServlet;
    
    import com.vaadin.annotations.Theme;
    import com.vaadin.annotations.VaadinServletConfiguration;
    import com.vaadin.server.VaadinRequest;
    import com.vaadin.server.VaadinServlet;
    import com.vaadin.ui.Button;
    import com.vaadin.ui.Grid;
    import com.vaadin.ui.Notification;
    import com.vaadin.ui.TextField;
    import com.vaadin.ui.UI;
    import com.vaadin.ui.VerticalLayout;
    
    @Theme("mytheme")
    public class MyUI extends UI {
        List<Product> items = new ArrayList<Product>();
        Grid<Product> grid;
    
        @Override
        protected void init(VaadinRequest vaadinRequest) {
            items.add(new Product("test", "test test"));
    
            final VerticalLayout layout = new VerticalLayout();
    
            grid = new Grid<Product>();
            layout.addComponent(grid);
    
            grid.getEditor().setBuffered(true);
            grid.getEditor().setEnabled(true);
    
            grid.removeAllColumns();
    
            grid.addComponentColumn(this::buildAddButton);
    
            TextField nameField = new TextField();
            TextField descriptionField = new TextField();
    
            grid.addColumn(Product::getName).setCaption("Name").setEditorComponent(nameField, Product::setName)
                    .setExpandRatio(1);
            grid.addColumn(Product::getDescription).setCaption("Description")
                    .setEditorComponent(descriptionField, Product::setDescription).setExpandRatio(1);
    
            grid.getEditor().addSaveListener(event -> {
    
                Notification.show((event.getBean() + "saved"));
            });
    
            grid.setItems(items);
            setContent(layout);
        }
    
        private Button buildAddButton(Product p) {
    
            Button button = new Button();
    
            button.addClickListener(event -> addButtonClicked(p));
    
            return button;
        }
    
        private void addButtonClicked(Product p) {
            refreshGrid();
        }
    
        private void refreshGrid() {
            grid.setItems(items);
        }
    
        @WebServlet(urlPatterns = "/*", name = "MyUIServlet", asyncSupported = true)
        @VaadinServletConfiguration(ui = MyUI.class, productionMode = false)
        public static class MyUIServlet extends VaadinServlet {
        }
    
        public class Product {
            String name;
            String description;
    
            public Product(String name, String description) {
                super();
                this.name = name;
                this.description = description;
            }
    
            public Product() {
                super();
    
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public String getDescription() {
                return description;
            }
    
            public void setDescription(String description) {
                this.description = description;
            }
    
            @Override
            public String toString() {
                return name + " " + description;
            }
    
        }
    }
    

    的pom.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.example</groupId>
        <artifactId>sample</artifactId>
        <packaging>war</packaging>
        <version>1.0-SNAPSHOT</version>
        <name>sample</name>
    
        <prerequisites>
            <maven>3</maven>
        </prerequisites>
    
        <properties>
            <vaadin.version>8.3.1</vaadin.version>
            <vaadin.plugin.version>8.3.1</vaadin.plugin.version>
            <jetty.plugin.version>9.3.9.v20160517</jetty.plugin.version>
            <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
            <maven.compiler.source>1.8</maven.compiler.source>
            <maven.compiler.target>1.8</maven.compiler.target>
            <!-- If there are no local customizations, this can also be "fetch" or "cdn" -->
            <vaadin.widgetset.mode>local</vaadin.widgetset.mode>
        </properties>
    
        <repositories>
            <repository>
                <id>vaadin-addons</id>
                <url>http://maven.vaadin.com/vaadin-addons</url>
            </repository>
        </repositories>
    
        <dependencyManagement>
            <dependencies>
                <dependency>
                    <groupId>com.vaadin</groupId>
                    <artifactId>vaadin-bom</artifactId>
                    <version>${vaadin.version}</version>
                    <type>pom</type>
                    <scope>import</scope>
                </dependency>
            </dependencies>
        </dependencyManagement>
    
        <dependencies>
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>3.0.1</version>
                <scope>provided</scope>
            </dependency>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-server</artifactId>
            </dependency>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-push</artifactId>
            </dependency>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-client-compiled</artifactId>
            </dependency>
            <dependency>
                <groupId>com.vaadin</groupId>
                <artifactId>vaadin-themes</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.0.0</version>
                    <configuration>
                        <failOnMissingWebXml>false</failOnMissingWebXml>
                        <!-- Exclude an unnecessary file generated by the GWT compiler. -->
                        <packagingExcludes>WEB-INF/classes/VAADIN/widgetsets/WEB-INF/**</packagingExcludes>
                    </configuration>
                </plugin>
                <plugin>
                    <groupId>com.vaadin</groupId>
                    <artifactId>vaadin-maven-plugin</artifactId>
                    <version>${vaadin.plugin.version}</version>
                    <executions>
                        <execution>
                            <goals>
                                <goal>update-theme</goal>
                                <goal>update-widgetset</goal>
                                <goal>compile</goal>
                                <!-- Comment out compile-theme goal to use on-the-fly theme compilation -->
                                <goal>compile-theme</goal>
                            </goals>
                        </execution>
                    </executions>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-clean-plugin</artifactId>
                    <version>3.0.0</version>
                    <!-- Clean up also any pre-compiled themes -->
                    <configuration>
                        <filesets>
                            <fileset>
                                <directory>src/main/webapp/VAADIN/themes</directory>
                                <includes>
                                    <include>**/styles.css</include>
                                    <include>**/styles.scss.cache</include>
                                </includes>
                            </fileset>
                        </filesets>
                    </configuration>
                </plugin>
    
                <!-- The Jetty plugin allows us to easily test the development build by
                    running jetty:run on the command line. -->
                <plugin>
                    <groupId>org.eclipse.jetty</groupId>
                    <artifactId>jetty-maven-plugin</artifactId>
                    <version>${jetty.plugin.version}</version>
                    <configuration>
                        <scanIntervalSeconds>2</scanIntervalSeconds>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    
        <profiles>
            <profile>
                <!-- Vaadin pre-release repositories -->
                <id>vaadin-prerelease</id>
                <activation>
                    <activeByDefault>false</activeByDefault>
                </activation>
    
                <repositories>
                    <repository>
                        <id>vaadin-prereleases</id>
                        <url>http://maven.vaadin.com/vaadin-prereleases</url>
                    </repository>
                    <repository>
                        <id>vaadin-snapshots</id>
                        <url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
                        <releases>
                            <enabled>false</enabled>
                        </releases>
                        <snapshots>
                            <enabled>true</enabled>
                        </snapshots>
                    </repository>
                </repositories>
                <pluginRepositories>
                    <pluginRepository>
                        <id>vaadin-prereleases</id>
                        <url>http://maven.vaadin.com/vaadin-prereleases</url>
                    </pluginRepository>
                    <pluginRepository>
                        <id>vaadin-snapshots</id>
                        <url>https://oss.sonatype.org/content/repositories/vaadin-snapshots/</url>
                        <releases>
                            <enabled>false</enabled>
                        </releases>
                        <snapshots>
                            <enabled>true</enabled>
                        </snapshots>
                    </pluginRepository>
                </pluginRepositories>
            </profile>
        </profiles>
    
    </project>
    

1 个答案:

答案 0 :(得分:1)

免责声明:由于缺少setHandleWidgetEvents方法,此解决方案不适用于8.3之前的版本。否则,虽然很复杂,但此解决方案可能对某人有效:

  1. 从按钮(addClickListener)中删除单击事件处理。无法获取有关双击的信息,但是

  2. 有一种方法可以使用addItemClickListener及其clickedItem.getMouseEventDetails().isDoubleClick()但是 再次

  3. 使用grid.getEditor().setEnabled(true);启用网格后,双击检查总是返回false,所以

  4. 默认情况下,您需要禁用编辑器grid.getEditor().setEnabled(false); (这里是一个很好的相关答案Couldn't capture double click event using vaadin 7

  5. 然后,而是在发生双击事件(并打开一行)后 grid.getEditor().setEnabled(true); grid.getEditor().editRow(item.getRowIndex());

  6. 重新启用它
  7. 否则,如果单击一次 button 列,则执行您的addButtonClicked操作

  8. 为了从Grid列的组件接收事件,当单击按钮时,列应该能够处理事件。这是通过grid.addComponentColumn(this::buildAddButton).setHandleWidgetEvents(true).setId("buttonClick");
  9. 实现的

来自init方法的完整修改后的代码。其他所有参数均保持不变(也请记住从按钮中删除addClickListener

        items.add(new Product("test", "test test"));

        grid = new Grid<Product>();

        grid.getEditor().setBuffered(true);
        grid.getEditor().setEnabled(false);

        grid.removeAllColumns();

        // Important! Propagate events from components to Grid
        grid.addComponentColumn(this::buildAddButton).setHandleWidgetEvents(true).setId("buttonClick");

        TextField nameField = new TextField();
        TextField descriptionField = new TextField();

        grid.addColumn(Product::getName).setCaption("Name").setEditorComponent(nameField, Product::setName)
                .setExpandRatio(1);
        grid.addColumn(Product::getDescription).setCaption("Description")
                .setEditorComponent(descriptionField, Product::setDescription).setExpandRatio(1);

        //Once close editor--> Disable it
        grid.getEditor().addSaveListener(event -> {
            grid.getEditor().setEnabled(false);

        });
        grid.getEditor().addCancelListener(e->{
            grid.getEditor().setEnabled(false);
        });
        //THIS IS WHERE ALL THE LOGIC IS HAPPENING
        grid.addItemClickListener(item->{
            //If the button column is clicked
           if("buttonClick".equals(item.getColumn().getId())){
               //Regual click--> update content; also fired twice before editor is opened
               if(!item.getMouseEventDetails().isDoubleClick()){
                  addButtonClicked(item.getItem());
               }
               //If Double click is detected, just opened editor. The data is already updated
               else{
                   grid.getEditor().setEnabled(true);
                   grid.getEditor().editRow(item.getRowIndex());
               }
            }
           //In all the other cases, when double click is detected--> open editor
           else if(item.getMouseEventDetails().isDoubleClick()){
               grid.getEditor().setEnabled(true);
               grid.getEditor().editRow(item.getRowIndex());
           }
        });

        grid.setItems(items);
        addComponent(grid);