简体   繁体   中英

Java Swing: how to fire an event when a right-click menu gets *dismissed* without a selection having been made?

I have been going through Google for two days now, somewhat lost.

I can easily fire an event when a right-click get triggered. That isn't a problem. My problem is that I also need to fire a similar event if/when that right-click menu gets dismissed without any selection having been made.

This boils down to a business need: there is a list of items in a table, the table has checkboxes in the left most column to show which rows have been selected. Because these checkboxes are meant to be the UI key to determining which rows will be processed, any processing action looks at the state of the checkboxes instead of which rows have been selected.

The users can select checkboxes directly to make an action on one or more rows (either via buttons in the footer of the window or via the right-click menu). However, there also needs to be the option of just making a single active selection via the right-click event itself, without needing to explicitly check off a checkbox.

In this case, triggering the right-click on an entry will auto-check that checkbox (whether or not it has been already selected). However, if the user decides not to conduct any action on that row, that checkbox needs to be brought back to its prior state (either checked or unchecked, brought through from the firing of the right-click).

By only bringing through the original state, I don't have to care about the original state, only that it is being re-applied when the right-click is abandoned. This allows both checked and unchecked original states to be restored successfully. Essentially, this allows an accidental right-click to be dismissed without leaving that table row with a checkbox checked off each and every time - this would be undesired behaviour.

Unfortunately, I have not found any examples on the Internet that talk about targeting the dismissal of a right-click menu, and how to hook an action into that event.

My code so far is something like this:

private void setListenerForItemsTable() {
    tblItems.addMouseListener( new MouseAdapter() {
        public void mousePressed( MouseEvent evt ) {
            if ( view.showMaybePopup( evt ) ) {
                rightClickEvent(); // Fires needed code on right-click popup appearance.
            }
        }

        public void mouseReleased( MouseEvent evt ) {
            if ( view.showMaybePopup( evt ) ) {
                rightClickEvent();
            }
        }
    } );
}

Which works wonders on the creation of a right-click pop-up. It works wonderfully .

As an FYI, the code also has a listener for the right-click menu itself, which allows the selection of any item in that right-click menu to be handled:

private void setListenerForRightClickMenu() {
    // Preview
    mnuPreviewItem.addActionListener( (e)
            -> {
        previewItem();
    } );

    // Resend Fax
    mnuResendItem.addActionListener( (e)
            -> {
        resendItem();
    } );

    /// etc...
}

However, any attempt to use something like addFocusListener on the right-click menu in the same way that addMouseListener was attached to the table causes a Null Pointer Exception; presumably because the right-click menu isn't available until the right-click is actually triggered.

Suggestions?

In a nutshell, my solution involved adding a second listener to the setListenerForItemsTable() method above. Particularly, the addPopupMenuListener() listener:

tblItems.addPopupMenuListener( new PopupMenuListener() {
    @Override
    public void popupMenuWillBecomeVisible( PopupMenuEvent evt ) {
        // set checkbox
        if( rightClickRow >= 0 ) {
            mdlItems.setValueAt( true, rightClickRow, model.COL_CHECK );
        }
    }
    @Override
    public void popupMenuCanceled( PopupMenuEvent evt ) {
        // return checkbox to prior value
        if( rightClickRow >= 0 ) {
            mdlItems.setValueAt( rightClickValue, rightClickRow, model.COL_CHECK );
        }
    }
} );

However, there is a rub: if the table is smaller than the window that contains it, your right-click might not actually involve the actively-selected row (if you right-click within that area, but not on any row within the table itself). As such, you need to target whatever row you are actually right-clicking on, if it exists , and then save both that row number as well as its checkbox's current state (for reversal if the right-click is abandoned).

As such, at the root of our class we set some default containers to hold these values:

private Boolean rightClickValue = false;
private int rightClickRow = -1;

and then target the addMouseListener() to see if we are actually clicking on a row, and if so, to record which row it is and what its checkbox's status is:

tblItems.addMouseListener( new MouseAdapter() {
    @Override
    public void mouseClicked( MouseEvent evt ) {
        rightClickRow = tblItems.rowAtPoint( evt.getPoint() );
        rightClickValue = mdlItems.getValueAt(rightClickRow, model.COL_CHECK) == Boolean.TRUE;
    }
}

So in the end, we have the following:

    /**
     * Listener to decide between showing right-click menu OR calling Preview Item after double clicking a row
     */
    private void setListenerForItemsTable() {

        tblItems.addMouseListener( new MouseAdapter() {
            @Override
            public void mouseClicked( MouseEvent evt ) {
                isPreview( evt );
            }

            @Override
            public void mousePressed( MouseEvent evt ) {
                processRightClick( evt );
            }

            @Override
            public void mouseReleased( MouseEvent evt ) {
                processRightClick( evt );
            }
        } );
        itmMenu.addPopupMenuListener( new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible( PopupMenuEvent evt ) {
                processCheckbox( true );
            }

            @Override
            public void popupMenuWillBecomeInvisible( PopupMenuEvent evt ) {
                // do nothing
            }

            @Override
            public void popupMenuCanceled( PopupMenuEvent evt ) {
                processCheckbox( rightClickValue );
            }
        } );
    }

    /**
     * Checks whether a preview of the line item is warranted, and provides it
     *
     * @param evt
     */
    private void isPreview( MouseEvent evt ) {
        if ( isRow( evt ) && isDblClick( evt ) ) {
            previewItem();
        }
    }

    /**
     * Checks if the item that was clicked on was a viable table row or not
     *
     * @param evt the MouseEvent
     *
     * @return Boolean
     */
    private Boolean isRow( MouseEvent evt ) {
        return getClickedRow( evt ) >= 0;
    }

    /**
     * Checks if the click action was a double click or not
     *
     * @param evt the MouseEvent
     *
     * @return Boolean
     */
    private Boolean isDblClick( MouseEvent evt ) {
        return evt.getClickCount() == 2;
    }

    /**
     * Gets the index of the table row that was clicked on
     *
     * @param evt the MouseEvent
     *
     * @return Integer
     */
    private Integer getClickedRow( MouseEvent evt ) {
        return tblItems.rowAtPoint( evt.getPoint() );
    }

    /**
     * Bringing everything together: storing the row index and value of the checkbox, and triggering the pop-up menu
     *
     * @param evt the MouseEvent
     */
    private void processRightClick( MouseEvent evt ) {
        int row = getClickedRow( evt );
        if ( row >= 0 ) {
            rightClickRow = row;
            rightClickValue = ( Boolean.TRUE ).equals( mdlItems.getValueAt( row, model.COL_CHECK ) );
        }
        view.showMaybePopup( evt );
    }

    /**
     * Setting or unsetting the checkbox in the active row
     *
     * @param value the desired value to apply to the checkbox
     */
    private void processCheckbox( Boolean value ) {
        if ( rightClickRow >= 0 ) {
            mdlItems.setValueAt( value, rightClickRow, model.COL_CHECK );
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM