中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 程序开发 > 编程语言 > Java > 测试
JMaskField @ JDJ
作者:未知 时间:2005-08-10 22:36 出处:Java频道 责编:chinaitpower
              摘要:JMaskField @ JDJ
When you write user interfaces, you inevitably have to collect information from text fields and validate the data before you use it. There are several ways of handling validation. You can verify the text as the user exits the field by watching for lost focus events, or you can wait for the user to dismiss a window or dialog box by pressing a button, thereby validating all the fields at once. Both approaches are useful, but they can also lead to complex scenarios. Providing appropriate feedback and cursor positioning when invalid data is entered can often become complicated. Often, a better approach is to validate the information as it's being entered by the user - a technique known as keystroke validation.

This month's JMaskField goes much further than restricting the user to valid keystrokes by supporting the following advanced features:

  • Customizable Rules: We use a regular expression-style syntax to define rules that determine whether a given character is acceptable for each position in the field. A mask can be defined with a mix of literal characters and validation rules.
  • Macro Characters: We can make it possible to associate any character with an expression in order to support a more compact notation. For example, "#" might be assigned to an expression like "[0-9]" to represent numerical values.
  • Cursor Positioning: The cursor is positioned intelligently after keystrokes, skipping literal characters that don't need to be typed in by the user.
  • Template Character: You can define any character as a visual cue to indicate positions where the user hasn't yet typed or where a character was deleted. The default template character is the underscore ("_").

    Figure 1 shows the classes we'll be developing and how they relate to each other. While this may seem like a lot, you can see by the diagram that most of the classes address tokenizing and parsing the mask. Once parsed, the validation is pretty straightforward and is handled by our extension to the PlainDocument class. JMaskField extends JTextField and merely adds some cursor movement code to make it more user-friendly.

    JMaskField provides the high-level interface you'll use in your applications. In practice, you can provide a mask and template character in the JMaskField constructor and check the field output for template characters to determine if they were fully entered by the user at runtime.

    Tokenizing Masks
    To process the field mask we need to tokenize the text and build a parse tree from the token list. The parse tree elements are used to match the characters as they're being typed by the user. The Java class library includes two out-of-the-box tokenizers: StringTokenizer and StreamTokenizer. While these are useful, they just don't provide sufficient information when telling the user where the problems have occured. So we'll create a more flexible solution.

    The MaskTokenizer produces a list of MaskToken elements. And, this MaskToken class, as seen in Listing 1, has two member variables: pos, an integer value that stores the offset from the beginning of the tokenized text, and text, a string value that stores the actual token. Because many tokens are single characters, we overload the equals method to handle both string and char inputs. This makes parsing easier when we need to determine what type of token we're dealing with.

    Listing 2 shows the MaskTokenizer class, which has a single constructor that requires two arguments: an include string that identifies all delimiter characters which should be returned as tokens, and an exclude string that identifies all delimiter characters which shouldn't be returned as tokens. A math tokenizer, for example, might use an include string like "+-*/"and an exclude string like " ". This would return any nonspace sequence and consider the math operators separate tokens.

    The MaskTokenizer provides hasMoreTokens and nextToken methods, just like the Java tokenizers, but nextToken returns a MaskToken instance. In addition, we provide an ignoreToken method to push back the current position after reading a token. This is useful with most parsers, which sometimes need to look ahead before determining whether the next token is relevant.

    Regular Expressions
    Regular expressions are used heavily in languages like Perl and AWK, and are often applied by programmers either through command line searching with the GREP utility or in flexible search/find capabilities exposed in modern user interfaces. While these are often considered too complicated for average computer users, they fit quite nicely into the savvy programmer's bag of tricks.

    Table 1 lists the characters we're interested in. Regular expressions have a couple of reserved characters that represent the beginning and the end of a line, as well as sequence modifiers. These aren't directly applicable to our solution, so they're not included in this table. If you're familiar with regular expressions, you'll notice that we've changed the character for NOT operators. This is nothing more than artistic license. Naturally, if you prefer something else in your application, you're free to change it to whatever you like.

    We'll take a look at the syntax in a moment. In the JMaskField widget, a mask is provided in the form of a string. The string can be a mix of literal characters - which may not be edited - and rules expressed using our regular expressions subset. To distinguish between rules and literals, we delimit rules with curly braces. Table 2 shows a few valid masks with a brief explanation for each.

    The last example shows how macros can be used to make this syntax more compact. Let's take a quick look at the parser.

    Parsing the Rules
    Once the text has been tokenized, we can organize it by constructing a parse tree. When errors are encountered, we throw a MaskException (as shown in Listing 3) to tell the caller what was expected and the text position where the error occurred. This makes it a lot easier to deal with syntax errors before they become problems. The text offset position is especially informative and makes addressing any occurring problems much easier.

    The rule parser uses several supporting classes to represent the resulting item list. Each of these implements the MaskElement interface from Listing 4, which enforces the use of a toString and a match method. The toString method is useful for debugging, so we can see the structure by just writing it out. The match method tests a character for validity and will get used in the document class we're implementing later.

    Here's a quick look at the syntax using the BNF format:

    ::= '{' '}' |

    ::= [ ] ::= '&' |
    '|'
    ::= '(' ')' |

    ::= '[' [ '!' ]
    ']'

    BNF allows us to represent the syntax in a manner which is very close to the way we need to program the parser. The productions above can be easily described in English. Each production involves an element on the left and options on the right. In BNF the options are separated by a "|" character and may include optional elements that are delimited by square brackets. Thus the first production means an element is either a condition (delimited by curly braces) or a literal.

    The second production means a condition is an expression, followed by an optional conjunction and another condition. The conjunctions are either the or ("|") character or the and ("&") character. The expression production is there primarily to allow parenthesis-delimited nesting - exactly the way mathematical expressions can be given precedence by wrapping them in parentheses. If no parenthesis is present, we expect a character set.

    We consider a character set to be a special case in our parser by expecting a set to be delimited by square brackets and to be optionally negated, using the "!" modifier. Character ranges aren't handled as tokenized elements. It's easier to consider anything tokenized as a single character sequence and to process sets with the parser. When we run into a set, we traverse the characters and handle dash ("-") delimited character pairs by dynamically expanding them so that the resulting set is explicit. This makes later matching more efficient.

    Listing 5 shows the MaskLiteral class, which represents literals and stores the character internally. The match method simply does a direct comparison with the test character. Macro characters are considered literals until they're interpreted at runtime. This allows us to change the macro definitions without requiring the mask to be parsed again, thereby increasing our flexibility.

    The MaskSet class is shown in Listing 6. A set of characters is made explicit by the parser, expanding hyphenated ranges into a string list. The parser automatically handles inverted ranges if the value of the rightmost character is less than the value of the leftmost character. The only additional information required in our MaskSet representation is the negation marker if a NOT operator was used and stored as a Boolean value.

    Listing 7 shows the MaskExpression class. Expressions are just wrappers designed to handle precedence. The match method calls the encapsulated MaskElement at comparison time. MaskCondition is more interesting. Listing 8 shows how it stores a Boolean value to indicate whether an AND or an OR conjunction is used. We keep a couple of constants around to make the parser code more readable. The match method uses the Java logical and ("&") and or ("|") operators to resolve the function. The left and right arguments are resolved by calling their own match methods.

    The MaskParser, shown in Listing 9, is implemented as a separate class so it can be used by both the MaskMacros and the MaskDocument classes. There isn't enough room here to say much about recursive descent parsers, other than the fact that they operate much as the name implies. They use recursion to build a tree structure and descend to parse any nested structures. As mentioned earlier, the structure of the methods in MaskParser closely resembles the structure of the BNF notation used to represent the syntax.

    Extending the Model
    One of the objectives I had when I decided to implement the JMaskField control was to make it both powerful and easy to use. This is one of those standard programming dichotomies that's difficult to resolve and requires some thought. The best option I was able to identify was to make the syntax for defining rules ultimately flexible. That makes it powerful, providing a mechanism for abstracting those rules in simple form. This mechanism is implemented in the form of character macros.

    Figure 2 shows how a simple mask gets expanded to a parsed expression, and finally to explicit character sets.

    Listing 10 shows how the MaskMacro class is really little more than a hashtable that stores an association between a given character and a MaskElement representing the rule(s) to be applied. When the MaskDocument in Listing 11 runs into a literal character, it checks to see if there's a rule associated with it in the MaskMacro model. While it's always possible to define masks using the curly brace syntax, you can see that it's much easier to define your own rules and assign them to macro characters.

    The Document Model
    The MaskDocument class extends the PlainDocument class in the JFC and can be assigned to any JTextComponent. The JMaskField class is presented in Listing 12 and extends JTextField, implementing additional behavior to handle intelligent cursor movement. Let's take a quick look at the MaskDocument class before we cover the JMaskField code.

    To provide visual feedback, we generate a template for the mask expression. To keep things simple for the user, we'll use an underscore as a placeholder for nonliteral characters. The underscore is the default template character, but you can easily change it if you prefer something else. Figure 3 shows how the mask presentation and user input parallel each other.

    The MaskDocument class implements supporting methods to handle the template and to make character matching easier, but it primarily implements the remove and insertString methods required by the JFC Document interface. The Document interface has two methods we have to override in order to get the behavior we need.

    The insertString method is called any time new data is entered in the text field, typically after every keystroke. The remove method is called whenever text is deleted.

    The insertString and remove methods can pass several characters at the same time, as would be the case in cut or paste operations. To support these, we break each string into single characters and handle them recursively. The net effect is that a parse operation may not complete if some of the characters don't match, but the field will still handle as many characters as possible. Each character, processed individually, is tested by the match method and allowed to replace template characters in the insertString method. A character is replaced by a template character when being deleted in the remove method.

    Summary
    Figure 4 shows JMaskField in action. The Phone and Postal Code fields have already been entered and the other fields demonstrate a mix of literal and template characters that provide visual cues to guide the user through a successful experience. When inappropriate characters are typed in, the user hears a beep and the character is rejected.

    JMaskField provides a flexible mechanism for constraining character entries in a text field. Because it uses standard Java strings, any valid character pattern can be applied to define the data mask. The simple, regular, expressionlike syntax lets you define arbitrary character rules. Extending this model to support character macros adds even more flexibility and the template view provides useful feedback for the user. Together, these elements provide you with yet another tool to make the user experience as pleasant as possible. Use it in good health.

  • 关闭本页
     
    首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
    Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有