java 文本框输入事件_让绘制的Java文本框响应输入法事件

本文介绍如何使用Java的Canvas组件创建一个自定义文本框,并实现输入事件和输入法支持。通过实现InputMethodListener、InputMethodRequests接口,使得任何Component都能支持输入法,而不仅仅局限于TextComponent或JTextComponent。示例代码展示了如何在Canvas上绘制文本框并处理输入事件。
摘要由CSDN通过智能技术生成

在任何一款桌面应用中,都难免会遇到让用户输入文字或者特殊字符的情况发生,所以输入法的支持与文本框组件的存在就变得必不可少。

由于Java具有桌面应用开发能力,它的图形组件中也当然配备有文本框,因而无论是继承自TextComponent的Text系组件抑或继承自JTextComponent的JText系组件都提供了让用户输入数据的功能。

现在的疑问是,虽然TextComponent与JTextComponent相类似,但两者的父类却并不同级。TextComponent直接继承自Component,但Component已经是所有Java图形组件的公共父类,JTextComponent的父类JComponent却继承自Container,而Container的父类才是Component。

为什么会这样呢?如果JTextComponent直接继承TextComponent难道不好吗?没错,不好,或者说不能。除了Swing与AWT运行原理造成的差异与组件关系的统一性需求外,造成这样情况的理由中还有一点至关重要,那就是不光JTextComponent不能,即便我们想在java.awt包外重载TextComponent也不能。原因在于,虽然TextComponent类并非final,但它的唯一构造函数却是default的,这意味着即便不同包中的类继承了它,也不能构造,根本无法重载。

更何况,就算可以重载的JTextComponent,也与TextComponent一样存在着一些很麻烦的默认配置问题(就更不要说重载JTextField抑或TextField了)。最主要的是,用它们制作标准文本框固然游刃有余,但如果我们需要的文本框不那么标准,甚至需要某些“奇形怪状”到只要求输入文字,但根本就算不上文本框的组件时,那么它们势必更加捉襟见肘。

那么,我们要怎样才能满足这种近乎于“变态”的要求呢?

很简单,自己“画”个文本框出来就好了,因为是“画”的,所以想它怎样,便是怎样,因为是凭空绘制,也没有利用现成Swing组件绘制时的不便。

所以能这样做,就在于Java获得输入法支持的关键点不在TextComponent与JTextComponent,而是java.awt.im包下的相关组件,更具体地说,只要你实现了InputMethodListener与InputMethodRequests两尊大神,那么所有Component都可以支持输入法,又何必专情于TextComponent与JTextComponent?

闲话少说,现在我就直接用Canvas来“画”个文本框,给大家瞧瞧。

TextCanvas.java

package org.test;

import java.awt.Canvas;

import java.awt.Color;

import java.awt.Dimension;

import java.awt.Font;

import java.awt.FontMetrics;

import java.awt.Frame;

import java.awt.Graphics;

import java.awt.Graphics2D;

import java.awt.Point;

import java.awt.Rectangle;

import java.awt.Toolkit;

import java.awt.event.FocusEvent;

import java.awt.event.FocusListener;

import java.awt.event.InputMethodEvent;

import java.awt.event.InputMethodListener;

import java.awt.event.KeyEvent;

import java.awt.event.KeyListener;

import java.awt.event.WindowAdapter;

import java.awt.event.WindowEvent;

import java.awt.font.FontRenderContext;

import java.awt.font.TextAttribute;

import java.awt.font.TextHitInfo;

import java.awt.font.TextLayout;

import java.awt.im.InputMethodRequests;

import java.text.AttributedCharacterIterator;

import java.text.AttributedString;

import java.text.AttributedCharacterIterator.Attribute;

import sun.awt.InputMethodSupport;

/**

*

* Copyright 2009

*

* Licensed under the Apache License, Version 2.0 (the "License"); you may not

* use this file except in compliance with the License. You may obtain a copy of

* the License at

*

* http://www.apache.org/licenses/LICENSE-2.0

*

* Unless required by applicable law or agreed to in writing, software

* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT

* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the

* License for the specific language governing permissions and limitations under

* the License.

*

* @project loonframework

* @author chenpeng

* @email:ceponline@yahoo.com.cn

* @version 0.1

*/

public class TextCanvas extends Canvas implements KeyListener, FocusListener,

InputMethodListener, InputMethodRequests {

/**

*

*/

private static final long serialVersionUID = 1L;

// 空的字符信息迭代器

private static final AttributedCharacterIterator EMPTY_TEXT = (new AttributedString(

"")).getIterator();

// 空的输入法信息

private static final Attribute[] IM_ATTRIBUTES = { TextAttribute.INPUT_METHOD_HIGHLIGHT };

// 判定当前组件是否具有焦点

private transient boolean haveFocus;

// 用户已输入数据缓存

private StringBuffer messageText = new StringBuffer();

// 文本布局器,用以限定输入后显示的具体位置

private transient TextLayout textLayout = null;

// 判定是否验证输入法布局器

private transient boolean validTextLayout = false;

// 文本显示位置的坐标修正值

private static final int textOffset = 5;

// 已输入文本定位器

private Point textOrigin = new Point(0, 0);

// 已输入信息的字符串构成数据

private AttributedString composedTextString;

// 已输入信息的字符构成数据

private AttributedCharacterIterator composedText;

// 插入符定位器,用以记载游标所在位置

private TextHitInfo caret = null;

/**

* 构造一个TextCanvas,用以通过纯绘制的方式输入文本信息

*

*/

public TextCanvas() {

super();

this.setForeground(Color.black);

this.setBackground(Color.white);

this.setFontSize(12);

this.setVisible(true);

this.setEnabled(true);

this.addInputMethodListener(this);

this.addKeyListener(this);

this.addFocusListener(this);

try {

Toolkit toolkit = Toolkit.getDefaultToolkit();

boolean shouldEnable = false;

// 验证当前环境是否支持输入法调用

if (toolkit instanceof InputMethodSupport) {

shouldEnable = ((InputMethodSupport) toolkit)

.enableInputMethodsForTextComponent();

}

enableInputMethods(shouldEnable);

} catch (Exception e) {

}

}

/**

* 重载Component的输入法调用接口,因为此类为InputMethodRequests的实现,所以返回this即可。

*/

public InputMethodRequests getInputMethodRequests() {

return this;

}

/**

* 调正当前显示的字体大小

*

* @param size

*/

public void setFontSize(int size) {

setFont(new Font("Dialog", Font.PLAIN, size));

textOrigin.x = 5;

textOrigin.y = (textOffset + size);

if (composedTextString != null) {

composedTextString.addAttribute(TextAttribute.FONT, getFont());

}

}

/**

* 返回用于显示的输入信息集

*

* @return

*/

public AttributedCharacterIterator getDisplayText() {

if (composedText == null) {

return getDisplayTextToAttributedCharacterIterator();

} else {

return EMPTY_TEXT;

}

}

/**

* 返回当前插入符所在位置

*

* @return

*/

public TextHitInfo getCaret() {

if (composedText == null) {

return TextHitInfo.trailing(messageText.length() - 1);

} else if (caret == null) {

return null;

} else {

return caret.getOffsetHit(getCommittedTextLength());

}

}

/**

* 触发输入法变更事件

*/

public void inputMethodTextChanged(InputMethodEvent e) {

int committedCharacterCount = e.getCommittedCharacterCount();

AttributedCharacterIterator text = e.getText();

composedText = null;

char c;

if (text != null) {

// 需要复制的字符长度

int toCopy = committedCharacterCount;

c = text.first();

while (toCopy-- > 0) {

insertCharacter(c);

c = text.next();

}

if (text.getEndIndex()

- (text.getBeginIndex() + committedCharacterCount) > 0) {

composedTextString = new AttributedString(text, text

.getBeginIndex()

+ committedCharacterCount, text.getEndIndex(),

IM_ATTRIBUTES);

composedTextString.addAttribute(TextAttribute.FONT, getFont());

composedText = composedTextString.getIterator();

}

}

e.consume();

invalidateTextLayout();

caret = e.getCaret();

repaint();

}

/**

* 修改插入符所在位置

*/

public void caretPositionChanged(InputMethodEvent event) {

caret = event.getCaret();

event.consume();

repaint();

}

/**

* 获得指定定位符对应的文本显示位置

*/

public Rectangle getTextLocation(TextHitInfo offset) {

Rectangle rectangle;

if (offset == null) {

rectangle = getCaretRectangle();

} else {

TextHitInfo globalOffset = offset

.getOffsetHit(getCommittedTextLength());

rectangle = getCaretRectangle(globalOffset);

}

Point location = getLocationOnScreen();

rectangle.translate(location.x, location.y);

return rectangle;

}

/**

* 获得偏移指定坐标的插入符信息

*/

public TextHitInfo getLocationOffset(int x, int y) {

Point location = getLocationOnScreen();

Point textOrigin = getTextOrigin();

x -= location.x + textOrigin.x;

y -= location.y + textOrigin.y;

TextLayout textLayout = getTextLayout();

if (textLayout != null && textLayout.getBounds().contains(x, y)) {

return textLayout.hitTestChar(x, y).getOffsetHit(

-getCommittedTextLength());

} else {

return null;

}

}

public int getInsertPositionOffset() {

return getCommittedTextLength();

}

/**

* 返回指定范围内的字符信息迭代器

*/

public AttributedCharacterIterator getCommittedText(int beginIndex,

int endIndex, Attribute[] attributes) {

return getMessageText(beginIndex, endIndex);

}

public AttributedCharacterIterator cancelLatestCommittedText(

Attribute[] attributes) {

return null;

}

public AttributedCharacterIterator getSelectedText(Attribute[] attributes) {

return EMPTY_TEXT;

}

public synchronized void update(Graphics g) {

paint(g);

}

/**

* 绘制目标界面

*/

public synchronized void paint(Graphics g) {

g.setColor(getBackground());

Dimension size = getSize();

g.fillRect(0, 0, size.width, size.height);

g.setColor(Color.black);

g.drawRect(0, 0, size.width - 1, size.height - 1);

if (haveFocus) {

g.drawRect(1, 1, size.width - 3, size.height - 3);

}

g.setColor(getForeground());

TextLayout textLayout = getTextLayout();

if (textLayout != null) {

textLayout.draw((Graphics2D) g, textOrigin.x, textOrigin.y);

}

Rectangle rectangle = getCaretRectangle();

if (haveFocus && rectangle != null) {

g.setXORMode(getBackground());

g.fillRect(rectangle.x, rectangle.y, 1, rectangle.height);

g.setPaintMode();

}

}

/**

* 将messageText转化为指定范围内的字符信息迭代器

*

* @param beginIndex

* @param endIndex

* @return

*/

public AttributedCharacterIterator getMessageText(int beginIndex,

int endIndex) {

AttributedString string = new AttributedString(messageText.toString());

return string.getIterator(null, beginIndex, endIndex);

}

/**

* 返回已输入的字符串信息长度

*/

public int getCommittedTextLength() {

return messageText.length();

}

public AttributedCharacterIterator getDisplayTextToAttributedCharacterIterator() {

AttributedString string = new AttributedString(messageText.toString());

if (messageText.length() > 0) {

string.addAttribute(TextAttribute.FONT, getFont());

}

return string.getIterator();

}

/**

* 返回当前文本布局器

*

* @return

*/

public synchronized TextLayout getTextLayout() {

if (!validTextLayout) {

textLayout = null;

AttributedCharacterIterator text = getDisplayText();

if (text.getEndIndex() > text.getBeginIndex()) {

FontRenderContext context = ((Graphics2D) getGraphics())

.getFontRenderContext();

textLayout = new TextLayout(text, context);

}

}

validTextLayout = true;

return textLayout;

}

/**

* 强制文本布局器验证无效化

*

*/

public synchronized void invalidateTextLayout() {

validTextLayout = false;

}

/**

* 返回文本绘制点

*

* @return

*/

public Point getTextOrigin() {

return textOrigin;

}

/**

* 返回对应插入点的矩形选框

*

* @return

*/

public Rectangle getCaretRectangle() {

TextHitInfo caret = getCaret();

if (caret == null) {

return null;

}

return getCaretRectangle(caret);

}

/**

* 返回对应插入点的矩形选框

*

* @param caret

* @return

*/

public Rectangle getCaretRectangle(TextHitInfo caret) {

TextLayout textLayout = getTextLayout();

int caretLocation;

if (textLayout != null) {

caretLocation = Math.round(textLayout.getCaretInfo(caret)[0]);

} else {

caretLocation = 0;

}

FontMetrics metrics = getGraphics().getFontMetrics();

return new Rectangle(textOrigin.x + caretLocation, textOrigin.y

- metrics.getAscent(), 0, metrics.getAscent()

+ metrics.getDescent());

}

/**

* 插入指定字符串

*

* @param c

*/

public void insertCharacter(char c) {

messageText.append(c);

invalidateTextLayout();

}

/**

* 用户输入

*/

public void keyTyped(KeyEvent event) {

char keyChar = event.getKeyChar();

// 处理文字删除

if (keyChar == '/b') {

int len = messageText.length();

if (len > 0) {

messageText.setLength(len - 1);

invalidateTextLayout();

}

} else {

insertCharacter(keyChar);

}

event.consume();

repaint();

}

public void keyPressed(KeyEvent event) {

}

public void keyReleased(KeyEvent event) {

}

public void focusGained(FocusEvent event) {

haveFocus = true;

repaint();

}

public void focusLost(FocusEvent event) {

haveFocus = false;

repaint();

}

public static void main(String[] args) {

Frame frame = new Frame("绘制一个输入框");

TextCanvas text = new TextCanvas();

frame.add(text);

frame.addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent e) {

System.exit(0);

}

});

frame.pack();

frame.setSize(300, 300);

frame.setLocationRelativeTo(null);

frame.setVisible(true);

}

}

运行效果如下图:

20091118_canvas_00.jpg

怎么样?这时你在TextCanvas中进行输入操作,是不是与JTextField或TextField里相差无几呢?——什么?你说就算“重复发明轮子”也应该有个限度,已经有JTextField与TextField了,你再写一个有什么用?

嗯,您很聪明,单纯的绘制文本框确实没有任何意义,但是,如果有一系列直接通过AWT绘制的组件与其相呼应呢?——比如,偶在LGame-Simple中制作的那一系列UI组件……

那么事情,就会变成如下这个样子。

20091118_canvas_02.jpg

怎么样呢?如上图所示,这是一个纯绘制的界面,无论文本框的字体,大小,颜色乃至透明度,贴图都可以随性切换(甚至逆天的将两个文本框叠在一起也可以), 而这样一个纯绘制出的文本框能够获得输入法支持,意味着什么呢?这意味着,一个相对于Swing能耗更少,效率更高的类Swing体系已经搭建成型了!(当然,相对的功能也更少,不过事无两利嘛……)

PS:如上所述,LGame-Simple-0.2.5版Text系组件将获得输入法支持,中文或其它语言的输入已经没有任何问题。(此版预计同Android版LGame一道于12月中下旬发布……不过,那是理想状态,事实上偶欠着的事情挺多,尽力看看……)

嗯,其实PS中的话才是最主要的……

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值