您的位置:首页 > 编程语言 > Java开发

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

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

由于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);
	}
}





运行效果如下图:







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



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



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







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



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



嗯,其实PS中的话才是最主要的……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: