ผู้ฟังเปลี่ยนค่าเป็น JTextField


215

ฉันต้องการให้กล่องข้อความปรากฏขึ้นทันทีหลังจากที่ผู้ใช้เปลี่ยนค่าในฟิลด์ข้อความ ปัจจุบันฉันต้องกดปุ่ม Enter เพื่อให้กล่องข้อความปรากฏขึ้น มีอะไรผิดปกติกับรหัสของฉันหรือไม่

textField.addActionListener(new java.awt.event.ActionListener() {
    public void actionPerformed(java.awt.event.ActionEvent e) {

        if (Integer.parseInt(textField.getText())<=0){
            JOptionPane.showMessageDialog(null,
                    "Error: Please enter number bigger than 0", "Error Message",
                    JOptionPane.ERROR_MESSAGE);
        }       
    }
}

ความช่วยเหลือใด ๆ ที่จะได้รับการชื่นชม!

คำตอบ:


373

เพิ่มผู้ฟังลงในเอกสารอ้างอิงซึ่งสร้างขึ้นโดยอัตโนมัติสำหรับคุณ

// Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
  public void changedUpdate(DocumentEvent e) {
    warn();
  }
  public void removeUpdate(DocumentEvent e) {
    warn();
  }
  public void insertUpdate(DocumentEvent e) {
    warn();
  }

  public void warn() {
     if (Integer.parseInt(textField.getText())<=0){
       JOptionPane.showMessageDialog(null,
          "Error: Please enter number bigger than 0", "Error Message",
          JOptionPane.ERROR_MESSAGE);
     }
  }
});

รูปแบบที่ดีสำหรับการเตือน / พิมพ์ชนิด รูปแบบเดียวกันจะมีประโยชน์สำหรับการจัดการจำนวนสองเท่า (ตัวเลขยอดขาย / ราคาที่ป้อนหรือแสดง)
Max West

มันใช้งานได้ดี แต่ฉันมีคำถามที่เมื่อฉันแทรกข้อความใน textfield แล้วฉันต้องการเรียกวิธี ฉันไม่มีความคิดมากเกี่ยวกับวิธีการทำ ..

ฉันมีปัญหากับ JTable ที่ไม่ได้รับการปรับปรุงกล่องข้อความจาก JComboBox ที่แก้ไขได้เมื่อคลิกเซลล์ตารางอื่นและฟังก์ชั่น insertUpdate ที่นี่เป็นวิธีเดียวที่จะทำให้มันทำงานได้อย่างถูกต้อง
winchella

14
"การนวดผิดพลาด"
2560

51

คำตอบปกตินี้คือ "ใช้DocumentListener" อย่างไรก็ตามฉันมักจะพบว่าอินเตอร์เฟซที่ยุ่งยาก อินเทอร์เฟซถูก over-engineered จริง ๆ มันมีสามวิธีสำหรับการแทรกการลบและการแทนที่ข้อความเมื่อต้องการเพียงหนึ่งวิธี: การแทนที่ (การแทรกสามารถดูเป็นการแทนที่ไม่มีข้อความด้วยข้อความบางส่วนและการลบสามารถดูเป็นการแทนที่ข้อความบางส่วนที่ไม่มีข้อความ)

โดยปกติแล้วสิ่งที่คุณต้องการทราบคือเมื่อข้อความในกล่องเปลี่ยนไปดังนั้นโดยทั่วไปDocumentListenerใช้งานมีสามวิธีที่เรียกวิธีหนึ่ง

ดังนั้นผมจึงทำวิธีสาธารณูปโภคต่อไปนี้ซึ่งช่วยให้คุณใช้ที่เรียบง่ายมากกว่าChangeListener DocumentListener(มันใช้ไวยากรณ์แลมบ์ดาของ Java 8 แต่คุณสามารถปรับใช้กับ Java แบบเก่าได้หากต้องการ)

/**
 * Installs a listener to receive notification when the text of any
 * {@code JTextComponent} is changed. Internally, it installs a
 * {@link DocumentListener} on the text component's {@link Document},
 * and a {@link PropertyChangeListener} on the text component to detect
 * if the {@code Document} itself is replaced.
 * 
 * @param text any text component, such as a {@link JTextField}
 *        or {@link JTextArea}
 * @param changeListener a listener to receieve {@link ChangeEvent}s
 *        when the text is changed; the source object for the events
 *        will be the text component
 * @throws NullPointerException if either parameter is null
 */
public static void addChangeListener(JTextComponent text, ChangeListener changeListener) {
    Objects.requireNonNull(text);
    Objects.requireNonNull(changeListener);
    DocumentListener dl = new DocumentListener() {
        private int lastChange = 0, lastNotifiedChange = 0;

        @Override
        public void insertUpdate(DocumentEvent e) {
            changedUpdate(e);
        }

        @Override
        public void removeUpdate(DocumentEvent e) {
            changedUpdate(e);
        }

        @Override
        public void changedUpdate(DocumentEvent e) {
            lastChange++;
            SwingUtilities.invokeLater(() -> {
                if (lastNotifiedChange != lastChange) {
                    lastNotifiedChange = lastChange;
                    changeListener.stateChanged(new ChangeEvent(text));
                }
            });
        }
    };
    text.addPropertyChangeListener("document", (PropertyChangeEvent e) -> {
        Document d1 = (Document)e.getOldValue();
        Document d2 = (Document)e.getNewValue();
        if (d1 != null) d1.removeDocumentListener(dl);
        if (d2 != null) d2.addDocumentListener(dl);
        dl.changedUpdate(null);
    });
    Document d = text.getDocument();
    if (d != null) d.addDocumentListener(dl);
}

ซึ่งแตกต่างจากการเพิ่มฟังฟังโดยตรงไปยังเอกสารนี้จัดการกรณี (ผิดปกติ) ที่คุณติดตั้งวัตถุเอกสารใหม่ในองค์ประกอบข้อความ นอกจากนี้ยังสามารถแก้ไขปัญหาที่กล่าวถึงในคำตอบของ Jean-Marc Astesanaซึ่งบางครั้งเอกสารจะเริ่มเหตุการณ์มากกว่าที่จำเป็น

อย่างไรก็ตามวิธีนี้ช่วยให้คุณสามารถแทนที่รหัสที่น่ารำคาญซึ่งมีลักษณะดังนี้:

someTextBox.getDocument().addDocumentListener(new DocumentListener() {
    @Override
    public void insertUpdate(DocumentEvent e) {
        doSomething();
    }

    @Override
    public void removeUpdate(DocumentEvent e) {
        doSomething();
    }

    @Override
    public void changedUpdate(DocumentEvent e) {
        doSomething();
    }
});

ด้วย:

addChangeListener(someTextBox, e -> doSomething());

รหัสเผยแพร่สู่สาธารณสมบัติ มีความสุข!


5
วิธีแก้ปัญหาที่คล้ายกัน: สร้างabstract class DocumentChangeListener implements DocumentListenerวิธีการแบบนามธรรมพิเศษchange(DocumentEvent e)ที่คุณเรียกใช้จากวิธีอื่นทั้ง 3 วิธี ดูเหมือนว่าฉันจะชัดเจนขึ้นเพราะใช้ตรรกะเดียวกันกับabstract *Adapterผู้ฟังมากหรือน้อย
geronimo

+1 ตามchangedUpdateวิธีการจะถูกเรียกอย่างชัดเจนผ่านการโทรภายในแต่ละรายการinsertUpdateและremoveUpdateเพื่อให้ทำงานได้ ..
Kais

16

เพียงแค่สร้างอินเตอร์เฟสที่ขยาย DocumentListener และใช้เมธอด DocumentListener ทั้งหมด:

@FunctionalInterface
public interface SimpleDocumentListener extends DocumentListener {
    void update(DocumentEvent e);

    @Override
    default void insertUpdate(DocumentEvent e) {
        update(e);
    }
    @Override
    default void removeUpdate(DocumentEvent e) {
        update(e);
    }
    @Override
    default void changedUpdate(DocumentEvent e) {
        update(e);
    }
}

แล้ว:

jTextField.getDocument().addDocumentListener(new SimpleDocumentListener() {
    @Override
    public void update(DocumentEvent e) {
        // Your code here
    }
});

หรือคุณสามารถใช้แลมบ์ดานิพจน์:

jTextField.getDocument().addDocumentListener((SimpleDocumentListener) e -> {
    // Your code here
});

1
อย่าลืมว่าโซลูชันนี้ต้องใช้คลาสนามธรรมแทนอินเทอร์เฟซในทุกรุ่นก่อนหน้าจาวา 8
klaar

15

โปรดระวังว่าเมื่อผู้ใช้ปรับเปลี่ยนฟิลด์ DocumentListener สามารถรับเหตุการณ์สองครั้ง ตัวอย่างเช่นหากผู้ใช้เลือกเนื้อหาฟิลด์ทั้งหมดจากนั้นกดปุ่มคุณจะได้รับ removeUpdate (เนื้อหาทั้งหมดจะถูกลบ) และ insertUpdate ในกรณีของคุณฉันไม่คิดว่ามันจะเป็นปัญหา แต่โดยทั่วไปแล้วมันคือ น่าเสียดายที่ดูเหมือนว่าจะไม่มีวิธีในการติดตามเนื้อหาของ textField โดยไม่มีการแบ่งคลาสย่อย JTextField นี่คือรหัสของคลาสที่มีคุณสมบัติ "ข้อความ":

package net.yapbam.gui.widget;

import javax.swing.JTextField;
import javax.swing.text.AttributeSet;
import javax.swing.text.BadLocationException;
import javax.swing.text.PlainDocument;

/** A JTextField with a property that maps its text.
 * <br>I've found no way to track efficiently the modifications of the text of a JTextField ... so I developed this widget.
 * <br>DocumentListeners are intended to do it, unfortunately, when a text is replace in a field, the listener receive two events:<ol>
 * <li>One when the replaced text is removed.</li>
 * <li>One when the replacing text is inserted</li>
 * </ul>
 * The first event is ... simply absolutely misleading, it corresponds to a value that the text never had.
 * <br>Anoter problem with DocumentListener is that you can't modify the text into it (it throws IllegalStateException).
 * <br><br>Another way was to use KeyListeners ... but some key events are throw a long time (probably the key auto-repeat interval)
 * after the key was released. And others events (for example a click on an OK button) may occurs before the listener is informed of the change.
 * <br><br>This widget guarantees that no "ghost" property change is thrown !
 * @author Jean-Marc Astesana
 * <BR>License : GPL v3
 */

public class CoolJTextField extends JTextField {
    private static final long serialVersionUID = 1L;

    public static final String TEXT_PROPERTY = "text";

    public CoolJTextField() {
        this(0);
    }

    public CoolJTextField(int nbColumns) {
        super("", nbColumns);
        this.setDocument(new MyDocument());
    }

    @SuppressWarnings("serial")
    private class MyDocument extends PlainDocument {
        private boolean ignoreEvents = false;

        @Override
        public void replace(int offset, int length, String text, AttributeSet attrs) throws BadLocationException {
            String oldValue = CoolJTextField.this.getText();
            this.ignoreEvents = true;
            super.replace(offset, length, text, attrs);
            this.ignoreEvents = false;
            String newValue = CoolJTextField.this.getText();
            if (!oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
        }

        @Override
        public void remove(int offs, int len) throws BadLocationException {
            String oldValue = CoolJTextField.this.getText();
            super.remove(offs, len);
            String newValue = CoolJTextField.this.getText();
            if (!ignoreEvents && !oldValue.equals(newValue)) CoolJTextField.this.firePropertyChange(TEXT_PROPERTY, oldValue, newValue);
        }
    }

3
Swing มีประเภทของ textField ที่แมปเอกสารการเปลี่ยนแปลงคุณสมบัติ - มันเรียกว่า JFormattedTextField :-)
kleopatra

11

ฉันรู้ว่าสิ่งนี้เกี่ยวข้องกับปัญหาเก่าจริง ๆ แต่มันทำให้ฉันมีปัญหาด้วย ในฐานะที่เป็นkleopatraJFormattedTextFieldตอบสนองในความคิดเห็นข้างต้นผมแก้ปัญหาด้วย อย่างไรก็ตามวิธีการแก้ปัญหาต้องใช้งานได้อีกเล็กน้อย แต่เป็น neater

JFormattedTextFieldไม่ได้โดยทริกเกอร์เริ่มต้นการเปลี่ยนแปลงสถานที่ให้บริการหลังการเปลี่ยนแปลงข้อความในทุกสนาม ตัวสร้างเริ่มต้นของJFormattedTextFieldไม่สร้างตัวจัดรูปแบบ

อย่างไรก็ตามในการทำสิ่งที่ OP แนะนำคุณจำเป็นต้องใช้ตัวจัดรูปแบบซึ่งจะเรียกใช้commitEdit()เมธอดหลังจากการแก้ไขแต่ละฟิลด์ที่ถูกต้อง commitEdit()วิธีคือสิ่งที่ก่อให้เกิดการเปลี่ยนแปลงสถานที่ให้บริการจากสิ่งที่ฉันสามารถดูและไม่มีการจัดรูปแบบนี้ถูกเรียกโดยเริ่มต้นในการเปลี่ยนแปลงโฟกัสหรือเมื่อป้อนคีย์ถูกกด

ดูhttp://docs.oracle.com/javase/tutorial/uiswing/components/formattedtextfield.html#valueสำหรับรายละเอียดเพิ่มเติม

สร้างDefaultFormatterวัตถุตัวจัดรูปแบบเริ่มต้น ( ) ที่จะส่งผ่านไปยังJFormattedTextFieldทั้งทางตัวสร้างหรือวิธีตัวตั้งค่า เมธอดหนึ่งของตัวจัดรูปแบบดีฟอลต์คือsetCommitsOnValidEdit(boolean commit)ซึ่งตั้งค่าตัวจัดรูปแบบเพื่อทริกเกอร์commitEdit()เมธอดทุกครั้งที่ข้อความถูกเปลี่ยน สิ่งนี้สามารถรับได้โดยใช้PropertyChangeListenerและpropertyChange()วิธีการ


2
textBoxName.getDocument().addDocumentListener(new DocumentListener() {
   @Override
   public void insertUpdate(DocumentEvent e) {
       onChange();
   }

   @Override
   public void removeUpdate(DocumentEvent e) {
      onChange();
   }

   @Override
   public void changedUpdate(DocumentEvent e) {
      onChange();
   } 
});

แต่ผมจะไม่ได้เป็นเพียงการแยกวิเคราะห์สิ่งที่ผู้ใช้ (อาจอุบัติเหตุ) Integerสัมผัสบนแป้นพิมพ์ของเขาเป็น คุณควรจะจับExceptionโยนและตรวจสอบให้แน่ใจว่าJTextFieldไม่ว่างเปล่า


2

หากเราใช้เมธอด runnable SwingUtilities.invokeLater () ในขณะที่การใช้แอปพลิเคชันฟังเอกสารกำลังติดขัดในบางครั้งและใช้เวลาในการอัปเดตผลลัพธ์ (ตามการทดสอบของฉัน) แต่การที่เรายังสามารถใช้เหตุการณ์ keyReleased สำหรับการเปลี่ยนแปลงช่องข้อความฟังเป็นที่กล่าวถึงที่นี่

usernameTextField.addKeyListener(new KeyAdapter() {
    public void keyReleased(KeyEvent e) {
        JTextField textField = (JTextField) e.getSource();
        String text = textField.getText();
        textField.setText(text.toUpperCase());
    }
});

1

มันเป็นเวอร์ชั่นอัปเดตของ Codemwnci รหัสของเขาค่อนข้างดีและใช้งานได้ดียกเว้นข้อผิดพลาด เพื่อหลีกเลี่ยงข้อผิดพลาดคุณต้องเปลี่ยนคำสั่งเงื่อนไข

  // Listen for changes in the text
textField.getDocument().addDocumentListener(new DocumentListener() {
  public void changedUpdate(DocumentEvent e) {
    warn();
  }
  public void removeUpdate(DocumentEvent e) {
    warn();
  }
  public void insertUpdate(DocumentEvent e) {
    warn();
  }

  public void warn() {
     if (textField.getText().length()>0){
       JOptionPane.showMessageDialog(null,
          "Error: Please enter number bigger than 0", "Error Massage",
          JOptionPane.ERROR_MESSAGE);
     }
  }
});

การปรับตัวของคุณจะทำให้กล่องโต้ตอบข้อความแสดงข้อผิดพลาดเกิดขึ้นทุกครั้งที่มีการป้อนสตริงใด ๆ ที่ยาวกว่า = 0 ในฟิลด์ข้อความ นั่นก็คือสตริงใด ๆ ที่ไม่ใช่สตริงว่าง นั่นไม่ใช่ทางออกที่ร้องขอ
klaar

0

คุณสามารถใช้ "MouseExited" เพื่อควบคุมได้ ตัวอย่าง:

 private void jtSoMauMouseExited(java.awt.event.MouseEvent evt) {                                    
        // TODO add your handling code here:
        try {
            if (Integer.parseInt(jtSoMau.getText()) > 1) {
                //auto update field
                SoMau = Integer.parseInt(jtSoMau.getText());
                int result = SoMau / 5;

                jtSoBlockQuan.setText(String.valueOf(result));
            }
        } catch (Exception e) {

        }

    }   

6
ไม่ได้จริงๆ: ความต้องการทำอะไรบางอย่างเมื่อข้อความมีการเปลี่ยนแปลง - ที่ไม่เกี่ยวข้องกับ mouseEvents ;-)
kleopatra

0

ฉันเป็นแบรนด์ใหม่สำหรับ WindowBuilder และในความเป็นจริงเพิ่งกลับสู่ Java หลังจากไม่กี่ปี แต่ฉันใช้ "บางอย่าง" แล้วคิดว่าฉันจะค้นหามันและเจอกระทู้นี้

ฉันอยู่ในระหว่างการทดสอบนี้ดังนั้นตามที่ใหม่ทั้งหมดนี้ฉันแน่ใจว่าฉันต้องหายไปบางสิ่งบางอย่าง

นี่คือสิ่งที่ฉันทำโดยที่ "runTxt" เป็นกล่องข้อความและ "runName" เป็นสมาชิกข้อมูลของคลาส:

public void focusGained(FocusEvent e) {
    if (e.getSource() == runTxt) {
        System.out.println("runTxt got focus");
        runTxt.selectAll();
    }
}

public void focusLost(FocusEvent e) {
    if (e.getSource() == runTxt) {
        System.out.println("runTxt lost focus");
        if(!runTxt.getText().equals(runName))runName= runTxt.getText();
        System.out.println("runText.getText()= " + runTxt.getText() + "; runName= " + runName);
    }
}

ดูเหมือนจะง่ายกว่าที่นี่มากและดูเหมือนว่าจะใช้งานได้ แต่เนื่องจากฉันอยู่ระหว่างการเขียนสิ่งนี้ฉันจึงรู้สึกซาบซึ้งที่ได้ยิน gotchas ที่ถูกมองข้ามไป เป็นปัญหาที่ผู้ใช้สามารถป้อน & ออกจากกล่องข้อความโดยไม่มีการเปลี่ยนแปลงหรือไม่? ฉันคิดว่าสิ่งที่คุณทำคือการมอบหมายที่ไม่จำเป็น


-1

ใช้ KeyListener (ซึ่งเรียกใช้กับคีย์ใด ๆ ) แทน ActionListener (ซึ่งเปิดใช้งานการป้อน)


สิ่งนี้ไม่ทำงานเนื่องจากค่าของฟิลด์ไม่ถูกจับอย่างเหมาะสมfield.getText()ส่งคืนค่าเริ่มต้น และเหตุการณ์ ( arg0.getKeyChar()) คืนค่าการตรวจสอบข้อผิดพลาดที่กดคีย์จำเป็นต้องมีเพื่อตรวจสอบว่าคุณควรเชื่อมต่อกับฟิลด์ข้อความหรือไม่
ยกย่อง

@glend คุณสามารถใช้เหตุการณ์ keyReleased แทนเหตุการณ์ keyTyped มันใช้งานได้สำหรับฉันและได้รับมูลค่าที่สมบูรณ์
Kakumanu siva krishna

-1

DocumentFilter ? มันทำให้คุณมีความสามารถในการจัดการ

[ http://www.java2s.com/Tutorial/Java/0240__Swing/FormatJTextFieldstexttouppercase.htm ]

ขอโทษ ฉันกำลังใช้ Jython (Python ใน Java) - แต่เข้าใจง่าย

# python style
# upper chars [ text.upper() ]

class myComboBoxEditorDocumentFilter( DocumentFilter ):
def __init__(self,jtext):
    self._jtext = jtext

def insertString(self,FilterBypass_fb, offset, text, AttributeSet_attrs):
    txt = self._jtext.getText()
    print('DocumentFilter-insertString:',offset,text,'old:',txt)
    FilterBypass_fb.insertString(offset, text.upper(), AttributeSet_attrs)

def replace(self,FilterBypass_fb, offset, length, text, AttributeSet_attrs):
    txt = self._jtext.getText()
    print('DocumentFilter-replace:',offset, length, text,'old:',txt)
    FilterBypass_fb.replace(offset, length, text.upper(), AttributeSet_attrs)

def remove(self,FilterBypass_fb, offset, length):
    txt = self._jtext.getText()
    print('DocumentFilter-remove:',offset, length, 'old:',txt)
    FilterBypass_fb.remove(offset, length)

// (java style ~example for ComboBox-jTextField)
cb = new ComboBox();
cb.setEditable( true );
cbEditor = cb.getEditor();
cbEditorComp = cbEditor.getEditorComponent();
cbEditorComp.getDocument().setDocumentFilter(new myComboBoxEditorDocumentFilter(cbEditorComp));
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.