限制用户在JTextField中input时间的最佳方法
在我的一个Java应用程序中,有一个用户应该进入的领域。 我知道,我可以简单地保持它作为一个正常的JTextField
并在validation时,我可以检查值等…
但是我觉得有比这更好的方法..
例如,如果这是一个date的问题,那么可以非常方便地使用JXDatePicker
(它带有swingx
)。 最终,用户select保证是一个date。
同样,也必须有更好的时间select方式。 如果你知道更好的解决scheme,有人会分享你的知识吗?
任何想法是赞赏。 谢谢!
你可以使用JFormattedTextField
看看下面的例子,这将创build一个JFormattedTextField
,它将只接受数字,并将它们放在窗体XXhXXminXXs中,然后向JFormattedTextField
添加一个ActionListener
,并在那个尝试parsing为一个有效的时间对象时ENTER现在我只显示使用JFormattedTextField
和ActionListener
我没有尝试转换或任何转换和parsing的有效时间:
import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.ParseException; import javax.swing.JFormattedTextField; import javax.swing.JFrame; import javax.swing.SwingUtilities; import javax.swing.text.MaskFormatter; public class FormattedTextFieldExample { public FormattedTextFieldExample() { initComponents(); } private void initComponents() { JFrame frame = new JFrame("JFormattedTextField Example"); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); MaskFormatter mask = null; try { mask = new MaskFormatter("##h##min##s");//the # is for numeric values mask.setPlaceholderCharacter('#'); } catch (ParseException e) { e.printStackTrace(); } // // Create a formatted text field that accept a valid time. // final JFormattedTextField timeField = new JFormattedTextField(mask); //Add ActionListener for when enter is pressed timeField.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent ae) { Object source = ae.getSource(); if (source == timeField) { //parse to a valid time here System.out.println(timeField.getText()); } } }); frame.add(timeField); frame.pack(); frame.setVisible(true); } public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { @Override public void run() { new FormattedTextFieldExample(); } }); } }
- 多个
JSpinner
实例允许select小时,分钟和秒钟 - 使用
JFormattedTextField
进行一些改进,以提供即时的用户反馈(例如,一旦input变为无效,就将背景变为红色)
前段时间我做了一个TimeField
的原型,还是需要一点工作,但是这个概念是非常基本的。
它基本上呈现两个JTextFields
,一个用于一个小时,另一个用于分钟,并使它们看起来是单个字段。
通过使用一些DocumentFilter
,它限制了用户的input。
我前段时间做了这个,需要工作,但是基本的想法就在那里……如果你有问题,你需要自己弄清楚;)
/** * * @author MadProgrammer */ public class TimeField extends javax.swing.JPanel { // The time of day... public enum TimeOfDay { AM, PM } private HourDocumentFilter hourDocumentFilter; private MinuteDocumentFilter minDocumentFilter; private HourKeyHandler hourKeyHandler; private MinuteKeyHandler minuteKeyHandler; private HourFocusHandler hourFocusHandler; private MinuteFocusHandler minuteFocusHandler; private boolean use24HourClock; private ActionHandler actionHandler; /** * Creates new form TimeField */ public TimeField() { initComponents(); pnlFields.setBorder(new CompoundBorder(UIManager.getBorder("TextField.border"),new EmptyBorder(0, 2, 0, 2))); set24HourClock(false); setTime(new Date()); fldHour.addKeyListener(new HourKeyHandler()); } @Override public void addNotify() { super.addNotify(); // Add all the required functionality to make this thing work... ((AbstractDocument) fldHour.getDocument()).setDocumentFilter(getHourDocumentFilter()); ((AbstractDocument) fldMin.getDocument()).setDocumentFilter(getMinuteDocumentFilter()); fldHour.addFocusListener(getHourFocusHandler()); fldMin.addFocusListener(getMinuteFocusHandler()); fldHour.addKeyListener(getHourKeyHandler()); fldMin.addKeyListener(getMinuteKeyHandler()); fldHour.addActionListener(getActionHandler()); fldMin.addActionListener(getActionHandler()); cmbTimeOfDay.addActionListener(getActionHandler()); } @Override public void removeNotify() { // Clean up our listeners... ((AbstractDocument) fldHour.getDocument()).setDocumentFilter(null); ((AbstractDocument) fldMin.getDocument()).setDocumentFilter(null); fldHour.removeFocusListener(getHourFocusHandler()); fldMin.removeFocusListener(getMinuteFocusHandler()); fldHour.removeKeyListener(getHourKeyHandler()); fldMin.removeKeyListener(getMinuteKeyHandler()); fldHour.removeActionListener(getActionHandler()); fldMin.removeActionListener(getActionHandler()); cmbTimeOfDay.removeActionListener(getActionHandler()); super.removeNotify(); } /** * Adds an action listener to the component. Actions are fired when the user * presses the enter key * * @param listener */ public void addActionListener(ActionListener listener) { listenerList.add(ActionListener.class, listener); } public void removeActionListener(ActionListener listener) { listenerList.remove(ActionListener.class, listener); } /** * Returns the field that is acting as the hour editor * * @return */ public JTextField getHourEditor() { return fldHour; } /** * Returns the field that is acting as the minute editor * * @return */ public JTextField getMinuteEditor() { return fldMin; } /** * Returns the combo box that provides the time of day selection * * @return */ public JComboBox getTimeOfDayEditor() { return cmbTimeOfDay; } /** * Returns the internal action handler. This handler monitors actions on the * individual components and merges them into one. * * @return */ protected ActionHandler getActionHandler() { if (actionHandler == null) { actionHandler = new ActionHandler(); } return actionHandler; } /** * Returns the hour key listener * * @return */ protected HourKeyHandler getHourKeyHandler() { if (hourKeyHandler == null) { hourKeyHandler = new HourKeyHandler(); } return hourKeyHandler; } /** * Returns the minute key listener * * @return */ protected MinuteKeyHandler getMinuteKeyHandler() { if (minuteKeyHandler == null) { minuteKeyHandler = new MinuteKeyHandler(); } return minuteKeyHandler; } /** * Returns the document filter used to filter the hour field * * @return */ protected HourDocumentFilter getHourDocumentFilter() { if (hourDocumentFilter == null) { hourDocumentFilter = new HourDocumentFilter(); } return hourDocumentFilter; } /** * Returns the document filter user to filter the minute field * * @return */ protected MinuteDocumentFilter getMinuteDocumentFilter() { if (minDocumentFilter == null) { minDocumentFilter = new MinuteDocumentFilter(); } return minDocumentFilter; } /** * Returns the focus listener used to monitor the hour field * * @return */ protected HourFocusHandler getHourFocusHandler() { if (hourFocusHandler == null) { hourFocusHandler = new HourFocusHandler(); } return hourFocusHandler; } /** * Used the focus listener used to monitor the minute field * * @return */ protected MinuteFocusHandler getMinuteFocusHandler() { if (minuteFocusHandler == null) { minuteFocusHandler = new MinuteFocusHandler(); } return minuteFocusHandler; } /** * Sets the time based on the supplied date * * @param date */ public void setTime(Date date) { Calendar cal = Calendar.getInstance(); cal.setTime(date); int hour = cal.get(Calendar.HOUR); int min = cal.get(Calendar.MINUTE); int dayPart = cal.get(Calendar.AM_PM); TimeOfDay timeOfDay = TimeOfDay.AM; switch (dayPart) { case Calendar.PM: timeOfDay = TimeOfDay.PM; break; } setTime(hour, min, timeOfDay); } /** * Sets the time based on a 24 hour clock. The field does not need to be in 24 * hour mode to use this method, the method will automatically correct the * hour appropriately. * * @param hour * @param min */ public void setTime(int hour, int min) { hour = correctHour(hour); min = correctMinute(min); TimeOfDay timeOfDay = TimeOfDay.AM; if (hour >= 12) { timeOfDay = TimeOfDay.PM; } setTime(hour, min, timeOfDay); } /** * Corrects the minute value to make sure it is within allowable ranges. * * For example, if you pass in 90 the method, it will automatically correct * the value to 30, discard the overflow. * * This will not effect the hour value...although this might be worth * consideration in the future * * @param min * @return */ protected int correctMinute(int min) { // Make sure the value is positive. // If we were interested in altering the hour value as well, we wouldn't // want to do this... if (min < 0) { min += (min * -2); } // Correct the minute value.... if (min > 59) { // How many hours fit into this value float part = min / 60f; part = (float) (part - Math.floor(part)); // Get remainder min = (int) (60 * part); // Calculate the number of minutes... } return min; } /** * Basically, this method will attempt to correct the hour value and bring the * it into range of a single day. * * We are basically going to try and figure out how many parts of the day that * the hour falls in and make it equal to a single day... * * That is, if the hour is 35, it's actually 1.458... days, which is roughly 1 * day and 11 hours. We are only interested in the 11 hours, cause the date is * irrelevant to us * * @param hour * @return */ protected int correctHour(int hour) { if (hour < 0) { hour += (hour * -2); } if (hour > 23) { float part = hour / 24f; part = (float) (part - Math.floor(part)); hour = (int) (24 * part); } return hour; } /** * Sets the time value for this field... * * @param hour * @param min * @param timeOfDay */ public void setTime(int hour, int min, TimeOfDay timeOfDay) { hour = correctHour(hour); min = correctMinute(min); // Now that we have a correct hour value, we need to know if it will // actually fit within the correct part of the day... switch (timeOfDay) { case AM: cmbTimeOfDay.setSelectedIndex(0); break; case PM: cmbTimeOfDay.setSelectedIndex(1); break; } if (!is24HourClock()) { if (hour > 12) { hour -= 12; } } else { if (hour < 12 && timeOfDay.equals(TimeOfDay.PM)) { hour += 12; } } fldHour.setText(pad(Integer.toString(hour), 2)); fldMin.setText(pad(Integer.toString(min), 2)); } public int getHour() { return Integer.parseInt(getHourEditor().getText()); } public int getMinute() { return Integer.parseInt(getMinuteEditor().getText()); } public TimeOfDay getTimeOfDay() { TimeOfDay tod = null; switch (cmbTimeOfDay.getSelectedIndex()) { case 0: tod = TimeOfDay.AM; break; case 1: tod = TimeOfDay.PM; break; } return tod; } /** * Sets if we should be using 24 or 12 hour clock. This basically configures * the time of day field and the validation ranges of the various fields * * @param value */ public void set24HourClock(boolean value) { if (value != use24HourClock) { use24HourClock = value; cmbTimeOfDay.setVisible(!use24HourClock); if (cmbTimeOfDay.getSelectedIndex() == 1) { setTime(getHour() + 12, getMinute(), getTimeOfDay()); } invalidate(); firePropertyChange("24HourClock", !use24HourClock, value); } } /** * Returns if this is using a 24 or 12 hour clock * * @return */ public boolean is24HourClock() { return use24HourClock; } /** * This method is called from within the constructor to initialize the form. * WARNING: Do NOT modify this code. The content of this method is always * regenerated by the Form Editor. */ @SuppressWarnings("unchecked") // <editor-fold defaultstate="collapsed" desc="Generated Code"> private void initComponents() { java.awt.GridBagConstraints gridBagConstraints; cmbTimeOfDay = new javax.swing.JComboBox(); pnlFields = new javax.swing.JPanel(); lblSeperator = new javax.swing.JLabel(); fldHour = new javax.swing.JTextField(); fldMin = new javax.swing.JTextField(); addFocusListener(new java.awt.event.FocusAdapter() { public void focusGained(java.awt.event.FocusEvent evt) { doFocusGained(evt); } }); setLayout(new java.awt.GridBagLayout()); cmbTimeOfDay.setModel(new javax.swing.DefaultComboBoxModel(new String[]{"am", "pm"})); cmbTimeOfDay.setBorder(null); cmbTimeOfDay.setEditor(null); cmbTimeOfDay.setOpaque(false); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.insets = new java.awt.Insets(0, 4, 0, 0); add(cmbTimeOfDay, gridBagConstraints); pnlFields.setBackground(new java.awt.Color(255, 255, 255)); pnlFields.setLayout(new java.awt.GridBagLayout()); lblSeperator.setText(":"); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 1; gridBagConstraints.gridy = 0; gridBagConstraints.insets = new java.awt.Insets(0, 2, 0, 2); pnlFields.add(lblSeperator, gridBagConstraints); fldHour.setBorder(null); fldHour.setColumns(2); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; pnlFields.add(fldHour, gridBagConstraints); fldMin.setBorder(null); fldMin.setColumns(2); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 2; gridBagConstraints.gridy = 0; pnlFields.add(fldMin, gridBagConstraints); gridBagConstraints = new java.awt.GridBagConstraints(); gridBagConstraints.gridx = 0; gridBagConstraints.gridy = 0; add(pnlFields, gridBagConstraints); }// </editor-fold> private void doFocusGained(java.awt.event.FocusEvent evt) { fldHour.requestFocus(); } // Variables declaration - do not modify private javax.swing.JComboBox cmbTimeOfDay; private javax.swing.JTextField fldHour; private javax.swing.JTextField fldMin; private javax.swing.JLabel lblSeperator; private javax.swing.JPanel pnlFields; // End of variables declaration /** * Moves the focus forward to the next field. * * This is used to provide "automatic" focus movement */ protected void moveFocusForward() { if (fldHour.hasFocus()) { fldMin.requestFocus(); } else if (fldMin.hasFocus()) { cmbTimeOfDay.requestFocus(); } } /** * Moves the focus backwards to the previous field. * * This is used to provide "automatic" focus movement */ protected void moveFocusBackward() { if (fldMin.hasFocus()) { fldHour.requestFocus(); } else if (cmbTimeOfDay.hasFocus()) { fldMin.requestFocus(); } } /** * Fires the action performed event to all registered listeners * * @param evt */ protected void fireActionPerformed(ActionEvent evt) { List<ActionListener> lstListeners = Arrays.asList(listenerList.getListeners(ActionListener.class)); if (!lstListeners.isEmpty()) { Collections.reverse(lstListeners); for (ActionListener listener : lstListeners) { listener.actionPerformed(evt); } } } /** * Hour key handler, used to monitor "special" keys for the hour field. * * This looks for the user pressing the ":" key and the right arrow key in * order to perform special navigation */ protected class HourKeyHandler extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { boolean numLock = false; try { // Get the state of the nums lock numLock = Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_NUM_LOCK); } catch (Exception exp) { } // Move focus forward if the user presses the ":" if (e.getKeyCode() == KeyEvent.VK_SEMICOLON && e.isShiftDown()) { moveFocusForward(); // Move focus forward if the user pressed the left arrow key } else if ((e.getKeyCode() == KeyEvent.VK_NUMPAD6 && !numLock) || e.getKeyCode() == KeyEvent.VK_RIGHT) { // If we are in the last edit position if (fldHour.getCaretPosition() >= 2) { moveFocusForward(); // Or we are in the first edit position and the field only contains a single character } else if (fldHour.getText().trim().length() == 1 && fldHour.getCaretPosition() == 1) { moveFocusForward(); } } } } /** * Minute key handler, used to monitor "special" keys for the hour field. * * This looks for the user pressing the left arrow key in order to perform * special navigation */ protected class MinuteKeyHandler extends KeyAdapter { @Override public void keyPressed(KeyEvent e) { boolean numLock = false; try { numLock = Toolkit.getDefaultToolkit().getLockingKeyState(KeyEvent.VK_NUM_LOCK); } catch (Exception exp) { } if ((e.getKeyCode() == KeyEvent.VK_NUMPAD4 && !numLock) || e.getKeyCode() == KeyEvent.VK_LEFT) { // Only want to move backwards if we are at the first edit position if (fldMin.getCaretPosition() == 0) { moveFocusBackward(); } } } } /** * Hour field focus handler. This watches for focus lost events a * automatically pads the field with a leading "0" if the field is only 1 * character in length */ protected class HourFocusHandler extends FocusAdapter { @Override public void focusLost(FocusEvent e) { String text = fldHour.getText(); if (text.length() < 2) { text = pad(text, 2); fldHour.setText(text); } } } /** * Minute field focus handler, watches for focus lost events and automatically * adds a "0" to the end of the field if it is only 1 character in length */ protected class MinuteFocusHandler extends FocusAdapter { @Override public void focusLost(FocusEvent e) { String text = fldMin.getText(); if (text.length() < 2) { fldMin.setText(text + "0"); } } } /** * The document filter used to filter the hour field. */ protected class HourDocumentFilter extends DocumentFilter { @Override public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException { System.out.println("insert: offset = " + offset + "; text = " + text); super.insertString(fb, offset, text, attr); } @Override public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { try { boolean isAcceptable = false; boolean passOnFocus = false; int strLength = text.length(); // We convert the value here to make sure it's a number... int value = Integer.parseInt(text); // If the length of the string been replaced is only 1 character if (strLength == 1) { // If we are at the start of the editing position if (offset == 0) { // What clock type are we using... if (!is24HourClock()) { // only accept 0 or 1... if (value <= 1) { isAcceptable = true; } } else if (value <= 2) { isAcceptable = true; } // If we are at the second editing position } else if (offset == 1) { // Get the preceeding value, should be 0, 1 or 2 String upperPart = fb.getDocument().getText(0, 1); // Convert the value to an int int upperValue = Integer.parseInt(upperPart); // The acceptable range of values for the given position int lowerRange = 0; int upperRange = 9; // Which clock are we using if (is24HourClock()) { // If the first value is 2, we can only accept values from 0-3 (20-23) if (upperValue == 2) { upperRange = 3; } } else { // 12 hour clock // If the first value is 1, we can only accept values from 0-2 (10-12) if (upperValue == 1) { upperRange = 2; } } // Is the value within accpetable range... if (value >= lowerRange && value <= upperRange) { isAcceptable = true; } // Pass on focus (only if the value is accepted) passOnFocus = true; } } else { // First, we need to trim the value down to a maximum of 2 characters // Need to know at what offest... // 2 - offset.. // offset == 0, length = 2 - offset = 2 // offset == 1, length = 2 - offset = 1 strLength = 2 - offset; String timeText = text.substring(offset, strLength); value = Integer.parseInt(timeText); // this will only work if we are using a 24 hour clock if (value >= 0 && value <= 23) { while (value > 12 && is24HourClock()) { value -= 12; } // Pad out the text if required text = pad(value, 2); isAcceptable = true; } } if (isAcceptable) { super.replace(fb, offset, length, text, attrs); if (passOnFocus) { moveFocusForward(); } } } catch (NumberFormatException exp) { } } } /** * The document filter used to filter the minute field. */ protected class MinuteDocumentFilter extends DocumentFilter { @Override public void insertString(FilterBypass fb, int offset, String text, AttributeSet attr) throws BadLocationException { System.out.println("insert: offset = " + offset + "; text = " + text); super.insertString(fb, offset, text, attr); } @Override public void replace(FilterBypass fb, int offset, int length, String text, AttributeSet attrs) throws BadLocationException { try { boolean isAcceptable = false; boolean passOnFocus = false; // How long is the text been added int strLength = text.length(); // Convert the value to an integer now and save us the hassel int value = Integer.parseInt(text); // If the length is only 1, probably a new character has been added if (strLength == 1) { // The valid range of values we can accept int upperRange = 9; int lowerRange = 0; if (offset == 0) { // If we are at the first edit position, we can only accept values // from 0-5 (50 minutes that is) upperRange = 5; } else if (offset == 1) { // Second edit position... // Every thing is valid here... // We want to pass on focus if the clock is in 12 hour mode passOnFocus = !is24HourClock(); } // Is the value acceptable.. if (value >= lowerRange && value <= upperRange) { isAcceptable = true; } } else { // Basically, we are going to trim the value down to at max 2 characters // Need to know at what offest... // 2 - offset.. // offset == 0, length = 2 - offset = 2 // offset == 1, length = 2 - offset = 1 strLength = 2 - offset; String timeText = text.substring(offset, strLength); value = Integer.parseInt(timeText); if (value >= 0 && value <= 59) { // Pad out the value as required text = pad(value, 2); isAcceptable = true; } } if (isAcceptable) { super.replace(fb, offset, length, text, attrs); if (passOnFocus) { moveFocusForward(); } } } catch (NumberFormatException exp) { } } } /** * This is a simple "pass" on action handler... */ protected class ActionHandler implements ActionListener { @Override public void actionPerformed(ActionEvent e) { ActionEvent evt = new ActionEvent(TimeField.this, e.getID(), e.getActionCommand(), e.getModifiers()); fireActionPerformed(evt); } } public static String pad(long lValue, int iMinLength) { return pad(Long.toString(lValue), 2); } public static String pad(int iValue, int iMinLength) { return pad(Integer.toString(iValue), iMinLength); } public static String pad(String sValue, int iMinLength) { StringBuilder sb = new StringBuilder(iMinLength); sb.append(sValue); while (sb.length() < iMinLength) { sb.insert(0, "0"); } return sb.toString(); } }
为什么有人在DavidKroukamp简单的解决scheme中select了这个庞大的实现。 另外,手动input数字时会出现一些错误。 我更喜欢DavidKroukamp的方式
简单的答案,validation。 JFormattedTextField
不能有效的值,但只限制了你可以input数字的事实,你可以很容易地input一个JFormattedTextField
99:99
。 这个实现的目的是提供实时validation,同时提供简单的数据input要求
我没有看到任何JSpinner
或JFormattedTextField
多重播放的原因,这不会让我有任何意义,这些概念不是用户友好的 ,请看:
-
使用Java日历
-
在SpinnerDateModel中使用简单的JSpinner