Asked  7 Months ago    Answers:  5   Viewed   33 times

I have a JTable which is loaded from a data-structure using table model.The data-structure is of the format NavigableMap<Float,NavigableMap<Float,Boolean>>.An example data is:

Table Format:
 Range     f1,v1   f2,v2    f3,v3   f4,v4
12.1-30.2 30,true 32,false 45,true 50,false
30.2-45.6 30,true 32.4,true 45,true 50.1,true

The above data format is represented in the DS as

DS Format:
Key  Value
12.1 <<30,true>,<32,false>,<45,true>,<50,false>>
30.2 <<30,true>,<32.4,true>,<45,true>,<50.1,true>>
45.6 null

I have managed to represent the above given data in Jtable using table-model.Once the data is loaded from the DS to the table I have to allow user edit.Now this is where I have problem.My doubt is whether is should keep the data structure synchronized with the changes in the table or should i recreate the DS from the table once the user finish editing and then replace it with the old one.

More over I need to validate the data(for example from above - Suppose the user want's to edit the value 30.1.He should only be allowed to enter values between 12.1 and 45.6.Since data the tables are string's (once loaded) I'm planning to use regex and key-listener and consume all user key presses which doesn't match the regex and values which doesn't come within the range.I'm not sure is this is a good idea or what are implications.I would like to get some suggestions on this.

 Answers

63

I would recreate your DS once the user is finised editing the table.

You can always create a custom editor to display a popup dialog where you have two separate text fields for each value of the range. Then you can edit each field as a double value within your specified range and recreate the formatted string before saving it to the model. Here's an old example I have lying around to get you started:

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.table.*;

/*
 * The editor button that brings up the dialog.
 */
//public class TablePopupEditor extends AbstractCellEditor
public class TablePopupEditor extends DefaultCellEditor
    implements TableCellEditor
{
    private PopupDialog popup;
    private String currentText = "";
    private JButton editorComponent;

    public TablePopupEditor()
    {
        super(new JTextField());

        setClickCountToStart(2);

        //  Use a JButton as the editor component

        editorComponent = new JButton();
        editorComponent.setBackground(Color.white);
        editorComponent.setBorderPainted(false);
        editorComponent.setContentAreaFilled( false );

        //  Set up the dialog where we do the actual editing

        popup = new PopupDialog();
    }

    public Object getCellEditorValue()
    {
        return currentText;
    }

    public Component getTableCellEditorComponent(
        JTable table, Object value, boolean isSelected, int row, int column)
    {

        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                System.out.println("run");
                popup.setText( currentText );
//              popup.setLocationRelativeTo( editorComponent );
                Point p = editorComponent.getLocationOnScreen();
                popup.setLocation(p.x, p.y + editorComponent.getSize().height);
                popup.show();
                fireEditingStopped();
            }
        });

        currentText = value.toString();
        editorComponent.setText( currentText );
        return editorComponent;
    }

    /*
    *   Simple dialog containing the actual editing component
    */
    class PopupDialog extends JDialog implements ActionListener
    {
        private JTextArea textArea;

        public PopupDialog()
        {
            super((Frame)null, "Change Description", true);

            textArea = new JTextArea(5, 20);
            textArea.setLineWrap( true );
            textArea.setWrapStyleWord( true );
            KeyStroke keyStroke = KeyStroke.getKeyStroke("ENTER");
            textArea.getInputMap().put(keyStroke, "none");
            JScrollPane scrollPane = new JScrollPane( textArea );
            getContentPane().add( scrollPane );

            JButton cancel = new JButton("Cancel");
            cancel.addActionListener( this );
            JButton ok = new JButton("Ok");
            ok.setPreferredSize( cancel.getPreferredSize() );
            ok.addActionListener( this );

            JPanel buttons = new JPanel();
            buttons.add( ok );
            buttons.add( cancel );
            getContentPane().add(buttons, BorderLayout.SOUTH);
            pack();

            getRootPane().setDefaultButton( ok );
        }

        public void setText(String text)
        {
            textArea.setText( text );
        }

        /*
        *   Save the changed text before hiding the popup
        */
        public void actionPerformed(ActionEvent e)
        {
            if ("Ok".equals( e.getActionCommand() ) )
            {
                currentText = textArea.getText();
            }

            textArea.requestFocusInWindow();
            setVisible( false );
        }
    }

    public static void main(String[] args)
    {
        String[] columnNames = {"Item", "Description"};
        Object[][] data =
        {
            {"Item 1", "Description of Item 1"},
            {"Item 2", "Description of Item 2"},
            {"Item 3", "Description of Item 3"}
        };

        JTable table = new JTable(data, columnNames);
        table.getColumnModel().getColumn(1).setPreferredWidth(300);
        table.setPreferredScrollableViewportSize(table.getPreferredSize());
        JScrollPane scrollPane = new JScrollPane(table);

        // Use the popup editor on the second column

        TablePopupEditor popupEditor = new TablePopupEditor();
        table.getColumnModel().getColumn(1).setCellEditor( popupEditor );

        JFrame frame = new JFrame("Popup Editor Test");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add( scrollPane );
        frame.pack();
        frame.setLocationRelativeTo( null );
        frame.setVisible(true);
    }
}
Tuesday, June 1, 2021
 
PedroKTFC
answered 7 Months ago
85

It's an interesting question, because it highlights missing api on JComponent :-)

As we all know, the recommended way to register popupMenus is to use the componentPopupMenu property. Related api is

 void setComponentPopupMenu(JPopupMenu);
 JPopupMenu getComponentPopupMenu();
 Point getPopupLocation(MouseEvent);

what is missing (and actually needed for this requirement) is

JPopupMenu getComponentPopupMenu(MouseEvent);

this lack is all the more annoying, as the getPopupLocation is called (by AWTEventHelper deep in the LAF) after getComponentPopup(). So there's no leeway for a hack like storing the last mouse event which might have triggered the popup and then decide which/if to return popup. And returning null for the location will only result in showing it at the mouse location

The only (dirty) hack (around my utter reluctance to get my hands dirty with a MouseListener ;-) is to override getComponentPopup and decide there whether or not to return it based on current mouse position

    table = new JTable(model) {

        /** 
         * @inherited <p>
         */
        @Override
        public JPopupMenu getComponentPopupMenu() {
            Point p = getMousePosition();
            // mouse over table and valid row
            if (p != null && rowAtPoint(p) >= 0) {
                // condition for showing popup triggered by mouse
                if (isRowSelected(rowAtPoint(p))) {
                    return super.getComponentPopupMenu();
                } else {
                    return null;
                }
            }
            return super.getComponentPopupMenu();
        }

    };

the side-effect is that popup showing isn't triggered by keyboard as long as the mouse is anywhere above the table, which might or not be a problem.

Wednesday, June 2, 2021
 
Wookai
answered 7 Months ago
46

Based on your example code and having to fill in the blanks, it works for me...

CopyPasteTable

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.FlavorEvent;
import java.awt.datatransfer.FlavorListener;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import javax.swing.AbstractAction;
import javax.swing.JFrame;
import javax.swing.JPopupMenu;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.AbstractTableModel;

public class TableExample {

    public static void main(String[] args) {
        new TableExample();
    }

    public TableExample() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                }

                JTable table = new JTable(new MyModel());
                final JPopupMenu pm = new JPopupMenu();
                pm.add(new CopyAction(table));
                pm.add(new PasteAction(table));

                table.addMouseListener(new MouseAdapter() {

                    @Override
                    public void mouseClicked(MouseEvent e) {
                        if (e.isPopupTrigger()) {
                            highlightRow(e);
                            doPopup(e);
                        }
                    }

                    @Override
                    public void mouseReleased(MouseEvent e) {
                        if (e.isPopupTrigger()) {
                            highlightRow(e);
                            doPopup(e);
                        }
                    }

                    protected void doPopup(MouseEvent e) {
                        pm.show(e.getComponent(), e.getX(), e.getY());
                    }

                    protected void highlightRow(MouseEvent e) {
                        JTable table = (JTable) e.getSource();
                        Point point = e.getPoint();
                        int row = table.rowAtPoint(point);
                        int col = table.columnAtPoint(point);

                        table.setRowSelectionInterval(row, row);
                        table.setColumnSelectionInterval(col, col);
                    }

                });

                JFrame frame = new JFrame("Testing");
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.setLayout(new BorderLayout());
                frame.add(table);
                frame.pack();
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);

            }
        });
    }

    public class MyModel extends AbstractTableModel {

        private String[] names = {"1", "2", "3", "4", "5"};
        private String[][] values = new String[5][5];

        public MyModel() {
            values = new String[10][names.length];
            for (int row = 0; row < 10; row++) {
                for (int col = 0; col < names.length; col++) {
                    values[row][col] = String.valueOf((char) ((row * names.length) + col + 65));
                }
            }
        }

        @Override
        public void setValueAt(Object value, int row, int col) {
            if (value instanceof Double || value instanceof Integer) {
                values[row][col] = value.toString();
            } else {
                values[row][col] = (String) value;
            }

            fireTableCellUpdated(row, col);
        }

        @Override
        public int getRowCount() {
            return values.length;
        }

        @Override
        public int getColumnCount() {
            return names.length;
        }

        @Override
        public Object getValueAt(int rowIndex, int columnIndex) {
            return values[rowIndex][columnIndex];
        }

    }

    class CopyAction extends AbstractAction {

        private JTable table;

        public CopyAction(JTable table) {
            this.table = table;
            putValue(NAME, "Copy");
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int row = table.getSelectedRow();
            int col = table.getSelectedColumn();

            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
            cb.setContents(new CellTransferable(table.getValueAt(row, col)), null);

        }

    }

    class CutAction extends AbstractAction {

        private JTable table;

        public CutAction(JTable table) {
            this.table = table;
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int row = table.getSelectedRow();
            int col = table.getSelectedColumn();

            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
            cb.setContents(new CellTransferable(table.getValueAt(row, col)), null);

        }

    }

    class PasteAction extends AbstractAction {

        private JTable table;

        public PasteAction(JTable tbl) {

            putValue(NAME, "Paste");

            table = tbl;

            final Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();

            cb.addFlavorListener(new FlavorListener() {
                @Override
                public void flavorsChanged(FlavorEvent e) {
                    setEnabled(cb.isDataFlavorAvailable(CellTransferable.CELL_DATA_FLAVOR));
                }
            });
            setEnabled(cb.isDataFlavorAvailable(CellTransferable.CELL_DATA_FLAVOR));
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            int row = table.getSelectedRow();
            int col = table.getSelectedColumn();

            Clipboard cb = Toolkit.getDefaultToolkit().getSystemClipboard();
            if (cb.isDataFlavorAvailable(CellTransferable.CELL_DATA_FLAVOR)) {
                try {
                    Object value = cb.getData(CellTransferable.CELL_DATA_FLAVOR);
                    System.out.println(value);
                    table.setValueAt(value, row, col);

                } catch (UnsupportedFlavorException | IOException ex) {
                    ex.printStackTrace();
                }
            }
        }

    }

    public static class CellTransferable implements Transferable {

        public static final DataFlavor CELL_DATA_FLAVOR = new DataFlavor(Object.class, "application/x-cell-value");

        private Object cellValue;

        public CellTransferable(Object cellValue) {
            this.cellValue = cellValue;
        }

        @Override
        public DataFlavor[] getTransferDataFlavors() {
            return new DataFlavor[]{CELL_DATA_FLAVOR};
        }

        @Override
        public boolean isDataFlavorSupported(DataFlavor flavor) {
            return CELL_DATA_FLAVOR.equals(flavor);
        }

        @Override
        public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
            if (!isDataFlavorSupported(flavor)) {
                throw new UnsupportedFlavorException(flavor);
            }
            return cellValue;
        }

    }

}

Now, maybe you'd like to provide a runnable example that demonstrates it not working...

Friday, July 23, 2021
 
Valdas
answered 5 Months ago
43

I like to work keeping my domain objects as close to the objects that they represent as possible. What I mean by this is that if an account has preferences, then the domain Account object should contain a Preferences property, most likely represented by a collection of Preference objects. If nothing else, this helps the users understand the data structure of the application easily.

As for constructing the view models, that's the easiest bit... you add just properties for anything that is required. What types of properties you would need would really depend on how you have structured your domain objects.

If your view has the requirements that you mentioned in your question and you modelled your domain objects closely on the objects that they represent, then by the sounds of it, you would just need an Account object because that would contain the Preference and Profile objects inside it.

Finally, the only 'mapping' that needs to be done can be done with a LinQ query using the Entity Framework. It is at this point that I join the tables and pull whatever data that I need for whichever object I am working on. Here is an example of instantiating objects from data from three tables (using LinQ2SQL):

public AudioTracks GetAudioTracks(AudioTrackSearchOptions searchOptions)
{
    AudioTracks audioTracks;
    using (MidasDataContext dataContext = DataContext)
    {
        audioTracks = new AudioTracks(
            from audioTrack in dataContext.DbAudioTracks
            join masterTrack in dataContext.DbMasterTracks on audioTrack.MasterTrackId equals masterTrack.Id
            join masterTrackArtist in dataContext.DbDataLists on masterTrack.ArtistId equals masterTrackArtist.Id
            orderby string.Concat(masterTrack.Title, " (", audioTrack.Mix, ") - ", masterTrackArtist.Text)
            where (searchOptions.IsInactiveAudioTrackIncluded || audioTrack.IsActive)
            && (searchOptions.IsDeletedAudioTrackIncluded || !audioTrack.IsDeleted)
            select new AudioTrack(audioTrack.Id, masterTrack.Id, audioTrack.Isrc, masterTrack.Title, masterTrackArtist.Text, audioTrack.Mix, audioTrack.IsContentExplicit, audioTrack.IsActive, audioTrack.IsDeleted));
    }
    audioTracks.Sort(a => a.TitleWithMix);
    return audioTracks ?? new AudioTracks();
}

UPDATE >>>

Extending my AudioTracks example and working backwards, the GetAudioTracks method is in a project called DataProviders. It is called from a GetAudioTracks method in a DataController class which just adds user feedback and re-try options. That in turn is called by a TracksModel in the Models project which just contains a subsection of methods from the DataController class that relate to the various types of tracks in the application.

Finally, the AudioTracksViewModel in the ViewModels project calls the TracksModel.GetAudioTracks method upon initialisation which happens when the AudioTracksView is loaded by the user. The AudioTracksView has a ListBox on the left containing all of the AudioTrack objects that meet the users search and/or filter selections. The right of the screen has the fields for the selected AudioTrack. Here is what it looks like (if the link seems broken, you can view the image here):

AudioTracksView

The more transparent fields with an edit Button on the right are read only fields connected to collections. The edit Button opens a dialog to let the user enter multiple items, which are then summarised in the field. All of the objects in the application have similar views of more or less complexity.

Saturday, August 21, 2021
 
Jeff
answered 4 Months ago
86

You could use a DataTable in .NET. It will allow you to define a set of typed columns (see the Columns property) as well as add a number of rows (see the Rows property).

Once you have that, you can easily access any row by:

  • Index
  • Column name
  • The DataColumn instance representing the column

However, consider the following: A List<T> of anonymous types (from a projection from a query in LINQ) or strong types is probably just as good, if not better (no need for strings to get columns) depending on your need (if you need dynamic access dictated from user input, then using a DataTable would be better).

If you simply want a cross matrix of values, an anonymous type is an easy way to get a strongly-typed, compile-time-checked list of values, which has many more benefits than dynamically looking up the values.

Saturday, November 13, 2021
 
Mateusz Pryczkowski
answered 3 Weeks ago
Only authorized users can answer the question. Please sign in first, or register a free account.
Not the answer you're looking for? Browse other questions tagged :  
Share