Transfer focus from a Jtable cell right after typing time

Asked

Viewed 1,056 times

8

Hello, I’m using a JTable for typing time card. I need help with this, but I’ve already done a search and nothing helped me very much. I’ve tried to do it in many ways and I couldn’t.

I have several hits (entrances and exits) on the time card and, all of them are timetables ("##:##"). I’m currently shifting focus to a JFormattedTextField that contains that mask and, I do a check, when you are with length.trim() == 5 and the caretPosition == 5, i shift the focus to the next cell. But this method is outdated - as I am programming for a company, such a company requires it to be the JTable editable, or create an interface for better typing of these databases. however, the JTable editable gets more beautiful, and this method is "locking" my system, IE, I can’t evolve the point card facility because of this - besides getting pretty ugly the project interface.

The doubt: how to make a JFormattedTextField inside the cell, so that you have this "mask" that when you finish typing, changes the focus?

What I’ve already tried:

  • I’ve already created a JFormattedTextField and added how defaultCellEditor (or defaultCellRenderer) for each column.

  • Problem: when I transferred the focus to the bottom cell, it treated like it was only 1 JFormattedTextField, That is, the check fell as true to the cell that was above. Also, as I was typing, I changed the value to all the cells that contained that JFormattedTextField.

  • Solution in this case: if there is a way to add a List<JFormattedTextField> as cellRenderer (or cellEditor) for a specific cell instead of an entire column.

  • What I tried: I know this method that I will describe, is very gambiarra and, does not follow at all the principles of good programming practice, but the same JFormattedTextField that I was transferring the focus of JTable for him, or vice versa, I tried to change the entire screen to setBounds and use it on him, using the getCellRect(row, col, true) to pick up the right spot.

  • The problem: the TextField was behind the JTable, and I didn’t find a method to put it back up, besides the code getting longer and, getting too clumsy the interface (because it really is a gambiarra).

I appreciate anyone who can help me, I’ve been racking my brain about it for a long time!

imagem imagem2 imagem3 imagem4

Basically it starts at input 1, as soon as you finish typing the time, it goes to 2, and typing 2, going to 3, and so on, and when you get to the last, it changes line and goes to input 1 of the next line.

My Model:

@Override
public String getColumnName(int col) {
    return colName.get(col);
}

@Override
public int getColumnCount() {
    return colName.size();
}

@Override
public Class<?> getColumnClass(int columnIndex) {
    return String.class;
}

@Override
public boolean isCellEditable(int row, int col) {
    return false;
}

@Override
public int getRowCount() {
    if (first) {
        Date adm = new Date();
        Date dem = new Date();
        try {
            dateAdm.setTime(sdf.parse(datAdm));
            dateDem.setTime(sdf.parse(datDem));
            adm = sdf.parse(datAdm);
            dem = sdf.parse(datDem);
        } catch (Exception E) {
            E.printStackTrace();
        }
        int dias = stT.dataDiff(adm, dem) + 1;
        this.first = false;
        return dias;
    } else {
        return lCard.size();
    }
}

Has the getValueAt and the setValueAt also, but I decided not to post because they are too big.

Man DefaultTableCellRenderer:

class celRenderModel extends DefaultTableCellRenderer {

/**
 *
 */
private static final long serialVersionUID = 1L;

ModelCartao mCard;

public celRenderModel(ModelCartao mc) {
    this.mCard = mc;
}

@Override
public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
    Color c = Color.WHITE;
    if (mCard.isCor(row)) {//Verifica se é para pintar ou não (funcionando perfeitamente!)
        c = Color.GREEN;
        setBackground(c);
    } else {
        c = Color.WHITE;
        setBackground(c);
    }
    return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
}
}

I found in an old backup of my system, my CellEditor, if it helps:

public class EditarCelulaTabela extends AbstractCellEditor implements TableCellEditor {
    JComponent component = new JTextField();
    public Component getTableCellEditorComponent(JTable table, Object value,
            boolean isSelected, int rowIndex, int vColIndex) {
        ((JTextField) component).setText((String) value);
        addCellEditorListener(new CellEditorListener() {
            @Override
            public void editingCanceled(ChangeEvent e0) {
                System.out.println("Cancelou");
            }

            @Override
            public void editingStopped(ChangeEvent e1) {
                if(((JTextField)component).getText().trim().length()!=5){
                    component.requestFocus();
                    System.out.println("Parou!");
                }
            }
        });

        // Retorna a configuracao do componente  
        return component;
    }

    public Object getCellEditorValue() {
        System.out.println(((JTextField) component).getText());               
        return ((JTextField) component).getText();
    }

    public EditarCelulaTabela(final JTextField tf) {
        component = tf;
    }

}

If you need any more information, just let me know!

  • mas esse método está ultrapassado - what it means?

  • I’ll edit, for better understanding!

  • 1

    Dude, a hint, there’s a lot of unnecessary text there, just focus on the problem faced. I even understood your problem, but the long text ended up confusing.

  • From what I understand you have a table with columns of editable hours, and you want it to be applied masks in the cells and, when filled, you want the focus to be passed to next. Correct?

  • accurate, but I need dynamics to do this (because Jtable is going to have some features that are not the case at the moment), I wanted to explain as detailed as possible precisely to better understand, sorry for the excess, if I can help, any questions just talk!

  • I did something similar, but this change of focus I think is not interesting not huh. You use your own Tablemodel? If yes, everything is easier.

  • Yes, I have my own Model, using the Abstract method! I’ll make some images here to better explain, then I’ll post the functionality I want ok?

  • Add, if you have, your Cellrenderer and cellEditor code in addition to your tablemodel.

  • Dude, we can go to chat, because it’s complicated this system, it’s going to be something gigantic here in the topic

  • Could it be by pressing TAB? It must be automatic when filling the whole field?

  • It can’t be by pressing tab, you have to transfer at the end of the typing, because as I said, imagine that you have 10 beats per day, for 5 years, you need agility not to stay so long doing this (there are days that the guys here have about 20 processes from 5 months to 20 years here), so I need it to be at the time that the user finished typing all the transfer, the tab is already standard, you can use it automatically, but it will "reduce the efficiency" of what I need

  • Well, I do not see all this difficulty in pressing tab to each field, is a less complex solution that facilitates the chance of an answer that meets and I have already found in the ready soen.

  • If you find anything, put it here.

Show 9 more comments

1 answer

5


It was really hard to do, but I did it. :D

The following code is complete, compileable, testable and executable. It includes, among the necessary explanations, the complete code of all classes (even the imports). Therefore, other people will be able to test and reuse it.

Your problem is with hour-type fields. However, to make sure my solution is good, I have solved the problem with time, date, and CPF fields, as well as leaving open the way to implement with any other required formats. The fields with these formats JTable have the following characteristics in this implementation:

  • Are data with masks. ##:## is used for hours, ##/##/#### for dates and ###.###.###-## for Cpfs. It never lets the mask be overwritten or erased.

  • Due to the use of the mask, it does not let invalid characters be typed.

  • To JTable does not let you leave the editing field if its content is neither valid nor totally blank. In the case of the hours, he will check if the time is between 00 and 23 and if the minutes are between 00 and 59. On the dates, he will check whether the months are 30 or 31 days and will check whether the year is leap or not on 29 February. In the CPF field, the calculation of the verifier digits shall be made.

  • After you finish typing one of these fields with a valid value (and only a valid value), it automatically jumps to the next field without you having to click or press TAB.

First, let’s start with our classes to represent simple data. In this case, times, dates and Cpfs.

Filing cabinet Hora.java:

import java.util.stream.Stream;

/**
 * @author Victor Stafusa
 */
public final class Hora {

    private final int horas;
    private final int minutos;

    public static Hora parse(String formatado) {
        try {
            int[] parts = Stream.of(formatado.split(":")).mapToInt(Integer::parseInt).toArray();
            if (parts.length != 2) return null;
            return new Hora(parts[0], parts[1]);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public Hora(int horas, int minutos) {
        if (horas < 0 || horas > 23 || minutos < 0 || minutos > 59) throw new IllegalArgumentException();
        this.horas = horas;
        this.minutos = minutos;
    }

    public int getHoras() {
        return horas;
    }

    public int getMinutos() {
        return minutos;
    }

    // Não estou usando o toString() para você poder ver que não preciso confiar na existência de um toString() em sua classe.
    public String converte() {
        return String.format("%02d:%02d", horas, minutos);
    }
}

Filing cabinet Data.java:

import java.util.Arrays;
import java.util.stream.Stream;

/**
 * @author Victor Stafusa
 */
public final class Data {

    private final int dia;
    private final int mes;
    private final int ano;

    public static Data parse(String formatado) {
        try {
            int[] parts = Stream.of(formatado.split("/")).mapToInt(Integer::parseInt).toArray();
            if (parts.length != 3) return null;
            return new Data(parts[0], parts[1], parts[2]);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public Data(int dia, int mes, int ano) {
        if (dia <= 0 || dia > 31 || mes <= 0 || mes > 12 || ano < 1583 || ano > 9999) throw new IllegalArgumentException();
        if (dia == 31 && Arrays.asList(2, 4, 6, 9, 11).contains(mes)) throw new IllegalArgumentException();
        if (dia == 30 && mes == 2) throw new IllegalArgumentException();
        if (dia == 29 && mes == 2 && (ano % 4 != 0 || Arrays.asList(100, 200, 300).contains(ano % 400))) throw new IllegalArgumentException();
        this.dia = dia;
        this.mes = mes;
        this.ano = ano;
    }

    public int getDia() {
        return dia;
    }

    public int getMes() {
        return mes;
    }

    public int getAno() {
        return ano;
    }

    // Não estou usando o toString() para você poder ver que não preciso confiar na existência de um toString() em sua classe.
    public String converte() {
        return String.format("%02d/%02d/%04d", dia, mes, ano);
    }
}

Filing cabinet Cpf.java:

import java.util.regex.Pattern;

/**
 * @author Victor Stafusa
 */
public final class Cpf {

    private static final Pattern REGEX = Pattern.compile("^\\d{3}\\.\\d{3}\\.\\d{3}\\-\\d{2}$");
    private final String digitos;

    public static Cpf parse(String formatado) {
        try {
            return new Cpf(formatado);
        } catch (IllegalArgumentException e) {
            return null;
        }
    }

    public Cpf(String digitos) {
        if (!REGEX.matcher(digitos).matches()) throw new IllegalArgumentException();
        String d2 = digitos.replaceAll("\\.|\\-", "");
        int[] numeros = new int[11];
        for (int i = 0; i < 11; i++) {
            numeros[i] = d2.charAt(i) - '0';
        }
        int a = 0;
        int b = 0;
        for (int i = 0; i < 9; i++) {
            a += (i + 1) * numeros[i];
            b += (11 - i) * numeros[i];
        }
        a = a % 11 % 10;
        b += 2 * a;
        b = b * 10 % 11 % 10;
        if (a != numeros[9] || b != numeros[10]) throw new IllegalArgumentException();
        this.digitos = digitos;
    }

    public String getDigitos() {
        return digitos;
    }
}

These three classes up there are relatively simple Beans. However, note that each of them has a static method parse(String) which returns an instance of the class if a String the parameter is valid or null if it is not, which will be useful further on in our CellEditor.

The logic that decides whether or not the data is valid is in the constructors, so that invalid instances cannot be created. However, this logic could be somewhere else if it were necessary, because what matters to the CellEditor (that will be explained below) is that the static method returns an instance only when it is valid, returning null otherwise. Also note that these classes are immutable, to avoid that valid instances may become invalid in the future, or even change from one valid state to another, but at an inopportune time.

To the JTable, each row is represented by an instance of any class. In this example, I have each row with five columns, called campo1, hora, campo2, data and cpf. The columns campo1 and campo2 are just normal columns with standard behavior, while the other three are our special columns. These fields for each row are stored in the class MeuElemento, nothing more is than a very simple bean with getters and setters with nothing special.

Here is the file MeuElemento.java:

/**
 * @author Victor Stafusa
 */
public class MeuElemento {

    private String campo1;
    private String campo2;
    private Hora hora;
    private Data data;
    private Cpf cpf;

    public MeuElemento() {
    }

    public String getCampo1() {
        return campo1;
    }

    public void setCampo1(String campo1) {
        this.campo1 = campo1;
    }

    public String getCampo2() {
        return campo2;
    }

    public void setCampo2(String campo2) {
        this.campo2 = campo2;
    }

    public Hora getHora() {
        return hora;
    }

    public void setHora(Hora hora) {
        this.hora = hora;
    }

    public Data getData() {
        return data;
    }

    public void setData(Data data) {
        this.data = data;
    }

    public Cpf getCpf() {
        return cpf;
    }

    public void setCpf(Cpf cpf) {
        this.cpf = cpf;
    }
}

Having then the class MeuElemento that represents each of the lines, we can then create a list of elements and with it build our TableModel. Here is the file MeuTableModel.java:

import java.util.List;
import javax.swing.table.AbstractTableModel;

/**
 * @author Victor Stafusa
 */
public class MeuTableModel extends AbstractTableModel {

    private static final long serialVersionUID = 1L;
    private final List<MeuElemento> elementos;

    public MeuTableModel(List<MeuElemento> elementos) {
        this.elementos = elementos;
    }

    @Override
    public int getRowCount() {
        return elementos.size();
    }

    @Override
    public int getColumnCount() {
        return 5;
    }

    @Override
    public String getColumnName(int columnIndex) {
        return new String[]{"Nome 1", "Horas", "Nome 2", "Data", "CPF"}[columnIndex];
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return new Class<?>[]{String.class, Hora.class, String.class, Data.class, Cpf.class}[columnIndex];
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return true;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        MeuElemento elemento = elementos.get(rowIndex);
        switch (columnIndex) {
            case 0:
                return elemento.getCampo1();
            case 1:
                return elemento.getHora();
            case 2:
                return elemento.getCampo2();
            case 3:
                return elemento.getData();
            case 4:
                return elemento.getCpf();
            default:
                throw new IllegalArgumentException();
        }
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        MeuElemento elemento = elementos.get(rowIndex);
        switch (columnIndex) {
            case 0:
                elemento.setCampo1((String) aValue);
                break;
            case 1:
                elemento.setHora((Hora) aValue);
                break;
            case 2:
                elemento.setCampo2((String) aValue);
                break;
            case 3:
                elemento.setData((Data) aValue);
                break;
            case 4:
                elemento.setCpf((Cpf) aValue);
                break;
            default:
                throw new IllegalArgumentException();
        }
        fireTableCellUpdated(rowIndex, columnIndex);
    }
}

Having the TableModel, our next step is to CellRenderer. Therefore, the file follows MeuCellRenderer.java:

import java.awt.Color;
import java.awt.Component;
import javax.swing.JTable;
import javax.swing.table.DefaultTableCellRenderer;

/**
 * @author Victor Stafusa
 */
public class MeuCellRenderer extends DefaultTableCellRenderer {

    private static final long serialVersionUID = 1L;
    private final ModelCartao mCard;

    public MeuCellRenderer(ModelCartao mCard) {
        this.mCard = mCard;
    }

    @Override
    protected void setValue(Object value) {
        setText(value == null ? ""
                : value instanceof Hora ? ((Hora) value).converte()
                : value instanceof Data ? ((Data) value).converte()
                : value instanceof Cpf ? ((Cpf) value).getDigitos()
                : value.toString());
    }

    @Override
    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {
        Color c = mCard.isCor(row) ? Color.GREEN : Color.WHITE;
        setBackground(c);
        return super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);
    }
}

The method setValue(Object) is responsible for completing the value to be displayed according to the object referring to the cell that will be obtained from the TableModel. Because of this, this method ends up being responsible for converting the value (whatever class it is) to the representation in String that will show you on the screen.

Note that I have kept the same logic of yours to choose whether the cell is white or green. As I do not know what its class ModelCartao does and does not even the criteria it uses to decide the color of the cell, I decided to put a minimalist implementation of it so that you then replace it with the implementation you want. This is my file ModelCartao.java:

public class ModelCartao {
    public boolean isCor(int row) {
        return row % 2 == 0; // Use o critério que você achar melhor.
    }
}

Having done all this, now comes the most difficult and laborious part. We come to our CellEditor. Here is the file MeuCellEditor.java:

import java.awt.AWTException;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.text.ParseException;
import java.util.function.Predicate;
import javax.swing.AbstractCellEditor;
import javax.swing.JFormattedTextField;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableCellEditor;
import javax.swing.text.JTextComponent;
import javax.swing.text.MaskFormatter;

/**
 * @author Victor Stafusa
 */
public class MeuCellEditor extends AbstractCellEditor implements TableCellEditor {

    private static final long serialVersionUID = 1L;
    private JTextComponent component;
    private int colunaEditando;
    private int linhaEditando;

    public MeuCellEditor() {
        this.colunaEditando = -1;
        this.linhaEditando = -1;
    }

    private boolean celulaHora() {
        return colunaEditando == 1;
    }

    private boolean celulaData() {
        return colunaEditando == 3;
    }

    private boolean celulaCpf() {
        return colunaEditando == 4;
    }

    @Override
    public boolean stopCellEditing() {
        boolean vazio = component.getText().replaceAll(":|_| |\\/|\\-|\\.", "").isEmpty();
        if (!vazio && getCellEditorValue() == null) return false;
        return super.stopCellEditing();
    }

    @Override
    public Object getCellEditorValue() {
        return celulaHora() ? Hora.parse(component.getText())
                : celulaData() ? Data.parse(component.getText())
                : celulaCpf() ? Cpf.parse(component.getText())
                : component.getText();
    }

    @Override
    public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {
        this.colunaEditando = column;
        this.linhaEditando = row;
        if (celulaHora()) {
            component = campoHora();
            if (value instanceof Hora) component.setText(((Hora) value).converte());
        } else if (celulaData()) {
            component = campoData();
            if (value instanceof Data) component.setText(((Data) value).converte());
        } else if (celulaCpf()) {
            component = campoCpf();
            if (value instanceof Cpf) component.setText(((Cpf) value).getDigitos());
        } else {
            component = new JTextField();
            if (value != null) component.setText(String.valueOf(value));
        }
        EventQueue.invokeLater(() -> component.requestFocusInWindow());
        return component;
    }

    private static Robot ROBOT;

    private synchronized static Robot obterRobot() {
        if (ROBOT == null) {
            try {
                ROBOT = new Robot();
            } catch (AWTException e) {
                throw new UnsupportedOperationException(e);
            }
        }
        return ROBOT;
    }

    private static JFormattedTextField campoMascara(String mascara, int posicaoFinal, Predicate<String> teste) {
        Robot robot = obterRobot();
        MaskFormatter formatter;
        try {
            formatter = new MaskFormatter(mascara);
        } catch (ParseException e) {
            throw new AssertionError(e);
        }
        formatter.setValidCharacters("0123456789");
        formatter.setPlaceholderCharacter('_');
        JFormattedTextField jftf = new JFormattedTextField(formatter);
        jftf.addKeyListener(new KeyListener() {
            @Override
            public void keyTyped(KeyEvent e) {
                if (jftf.getCaretPosition() == posicaoFinal && teste.test(jftf.getText().replace("_", "") + String.valueOf(e.getKeyChar()))) {
                    robot.keyPress(KeyEvent.VK_TAB);
                    robot.keyRelease(KeyEvent.VK_TAB);
                }
            }

            @Override
            public void keyPressed(KeyEvent e) {
            }

            @Override
            public void keyReleased(KeyEvent e) {
            }
        });
        return jftf;
    }

    private static JFormattedTextField campoHora() {
        return campoMascara("##:##", 4, x -> Hora.parse(x) != null);
    }

    private static JFormattedTextField campoData() {
        return campoMascara("##/##/####", 9, x -> Data.parse(x) != null);
    }

    private static JFormattedTextField campoCpf() {
        return campoMascara("###.###.###-##", 13, x -> Cpf.parse(x) != null);
    }
}

This archive demands greater explanations:

  1. First, the CellEditor keeps the fields colunaEditando and linhaEditando to find out which cell JTable which is being edited in order to find the most appropriate component type for editing. Such component is stored in the field component when it becomes available (initially null).

  2. The methods celulaHora(), celulaData() and celulaCpf() to identify the cell type of the component being edited. The logic of these methods is simple, and consists only of checking a fixed column number, but for some JTableComplicated things you’re going to do in the real world, surely there will be cases where it won’t be that simple.

  3. The method stopCellEditing() is fundamental. It is the method responsible for holding the issue in the cell (when returning false) if the contents given in the entry are not filled correctly without being blank. The replaceAll(":|_| |\\/|\\-|\\.", "") is the part that eliminates the special characters of the mask, where ":|_| |\\/|\\-|\\." that is to say "two dots or underline or white space or bar or dash or dot". The idea is that the stopCellEditing() just come back true if the field is either properly filled or empty, returning false otherwise (if partially, incomplete and/or invalid). Therefore, the user cannot leave this field filled incorrectly or partially, forcing a filling that is valid or leaves the field empty.

  4. The method getCellEditorValue() is responsible for obtaining the object (not necessarily a String) represented by the editing cell. In our case, in addition to String, he can return Hora, Data or Cpf.

  5. The method getTableCellEditorComponent(JTable, Object, boolean, int, int) is responsible for creating the CellEditor. It also records which cell is being edited. It is important to note that this method does not reuse components from one cell to another, discarding them when a change of the chosen cell occurs (and this is on purpose). Otherwise, it is possible to get some bugs from making the values of a cell start to be incorrectly copied into other cells when the component is reused.

  6. The EventQueue.invokeLater(() -> component.requestFocusInWindow()) is an important workaround, as sometimes a field starts receiving typing without being focused, which would cause problems to jump alone to the next field afterwards without needing the TAB. With that gambiarra, humm, ops I mean... special technique the component gains focus alone when it starts receiving typing.

  7. The method campoMascara(String, int, Predicate<String>) is responsible for creating the editing component. It creates a JFormattedTextField according to the given mask in the parameter. It is in this method that it is possible to see the technique I used to make it jump to the subsequent field alone when typing is finished. I used a master-power-super-gambeta-plus with the java.awt.Robot which keeps sending to the operating system a pressing of the TAB key from inside a KeyListener to jump field whenever he realizes that it has been filled properly. The parameter posicaoFinal serves so that he can know in which position of the text he should jump from the field and the teste serves to enable him to assess whether the field has been filled in correctly or not. It is important to note that the TAB will only be triggered when the field is Caret in the last position and this will make it valid (and this is what is tested), because this criterion has to agree with the situation where the stopCellEditing() returns true with the field completed.

  8. Finally, the static methods campoHora(), campoData() and campoCpf() are responsible for creating the JFormattedTextField necessary, specifying for each different case, the mask, the position in which the automatic TAB can be triggered and the test to verify whether or not the fill was correct.

Finally, to complete, our main class to run and test our program. Here’s the file TesteJTable.java:

import java.awt.EventQueue;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JTable;

/**
 * @author Victor Stafusa
 */
public class TesteJTable {

    public static void main(String[] args) {
        EventQueue.invokeLater(TesteJTable::rodar);
    }

    private static void rodar() {
        JFrame jf = new JFrame("Teste");
        jf.setBounds(20, 20, 500, 200);
        jf.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        List<MeuElemento> elementos = new ArrayList<>(4);
        elementos.add(new MeuElemento());
        elementos.add(new MeuElemento());
        elementos.add(new MeuElemento());
        elementos.add(new MeuElemento());
        MeuTableModel tm = new MeuTableModel(elementos);
        JTable jt = new JTable(tm);
        jt.setDefaultEditor(Object.class, new MeuCellEditor());
        jt.setDefaultRenderer(Object.class, new MeuCellRenderer(new ModelCartao()));
        jf.add(jt);
        jf.setVisible(true);
    }
}
  • Where do you capture the field offset, to know that it is already in the last filled position and so change to the next one? I broke my head here using Documentfilter, Plaindocument and none worked kkkk Another detail I noticed is that the stopcellEditing is called before the keyListener, so I could not identify also that the person was pressing tab by this System before the editing was stopped.

  • @diegofm The position detection is on jftf.getCaretPosition() == posicaoFinal in the keyTyped. The value posicaoFinal is 4, 9 or 13, depending on the field (I could have used simply mascara.length() - 1, but this would go wrong if some mask appeared in which the last character is fixed). To get to that I also broke my head for several hours trying to use PlainDocument, FocusListener, ActionMap+InputMap. I even tried to write down the method setText" and I also tried a lot of other things that didn’t work out.

  • @diegofm And as for the stopCellEditing come before or after the keyTyped, it is important to note that the two must agree with each other. The keyTyped checks that the field has been filled correctly and is in the last position before sending the TAB, and the stopCellEditing returns true only if blank or completely filled in. Thus, for the relevant cells, keyTyped only sends a TAB if, and only if, the stopCellEditing to return true and the field is not empty.

  • Victor, but if stopcellediting returns true, won’t it change the editable status of the field? The problem I had here was that, when returning true and checking if tab was pressed, it skipped 2 fields, because the stopcellediting when returning true, jumped to the next cell, then the keypressed detected the TAB and skipped one more.

  • @diegofm No. When the stopCellEditing returns true it lets the user leave the field. When it returns false the user cannot leave the editing field and is obliged to keep editing until the stopCellEditing return true. This has no direct relation to whether a field is editable or not.

  • I expressed myself badly, that’s exactly what you said I meant kkk so you reversed the logic of stopCellEditing, making it not leave the field be filled or empty?

  • @diegofm Its logic is to not let out of the field if it is partially or incorrectly filled. This forces the user to either leave it blank or fill in correctly. Anyway, I invite you to test my code. I took care to post a complete, compileable, testable, and executable code, and for that I gave the complete code of all my classes (including the imports) just so other people could test what I did.

  • 1

    Worse than I’ve ever tried, but I was stalling here to understand the logic in the celleditor. Now I understood the most "polemical" excerpt that cost me a few hours here yesterday hehe. I found it interesting you use EventQueue.invokeLater(() -> component.requestFocusInWindow()), I was forcing it on writing the method changeSelection jtable’s. Anyway, my congratulations, the logic applied in the editor Cell became excellent and flexible not only to the question problem, but to other adaptations +1 :D

  • 1

    Man, thank you very much, and congratulations on your explanation, the methods you used I knew very little, but in addition to solving the problem I managed to learn from it, due to what you did. And relax, because your "gambiarra" is no worse than the one I was trying to do (kkkk) (from overwriting Jtable with a Formattedtextfield and using setBounds to transfer from one to another) again, my congratulations and many thanks!

Show 4 more comments

Browser other questions tagged

You are not signed in. Login or sign up in order to post.