Thừa kế Textbox để đánh chữ Việt Unicode
Lập trình dùng thừa kế
Hết rồi giai đoạn bực mình với VB6 thì sự giới hạn về lập trình theo hướng đối
tượng (Object Oriented), .NET cho phép ta tha hồ thừa kế. Do đó, một trong
những dự án nho nhỏ đầu tiên của chúng ta là thừa kế từ Textbox bình thường
để tạo một Textbox, tạm gọi là vnTextbox, hỗ trợ đánh chữ Việt Unicode theo lối
VNI hay VIQR. Dĩ nhiên, ta vẫn tiếp tục giữ các programs bỏ dấu chuyên nghiệp
ưng ý của mình như VietKey, UniKey, VPSKey, .v.v.., nhưng có thể sau nầy sẽ có
trường hợp ta cung cấp cho khách hàng một chương trình áp dụng tiếng Việt để
họ dùng cho nhu cầu chuyên môn mà không cần phải dùng thêm một program
bỏ dấu hỗ trợ.
Để tạo một Control thừa kế từ Textbox bạn khởi động một Project mới loại
Windows Control Library như sau:
Kế đó, khi mở code ra thay thế hai hàng:
Public Class UserControl1
Inherits System.Windows.Forms.UserControl
bằng hai hàng sau:
Public Class vnTextbox
Inherits System.Windows.Forms.TextBox
Đánh dấu theo lối VNI
Ðể đánh dấu cho các nguyên âm chữ Việt, trong vnTextbox ta tạm dùng phương
pháp VNI. Tức là ta đánh nguyên âm trước, kế đó ta đánh một con số từ 1 đến 9
để bỏ dấu.
Các con số 1..6 theo sau chữ a chẳng hạn, sẽ cho ta các chữ á à ả ã ạ â; số 7
theo sau chữ u sẽ cho ta ư; số 8 theo sau chữ a sẽ cho ta ă; số 9 theo sau chữ
d sẽ cho ta đ. Để bỏ hai dấu thì ta dùng hai con số, thí dụ a36 thì sẽ đuợc hiển
thị thành ẩ, còn u27 thì sẽ cho ừ. Ðể đánh các chữ đ và Ð ta dùng d9 và D9.
Chắc chắn bạn sẽ thấy program nầy đơn sơ quá, nhưng nó sẽ dễ hiểu, và sau
đó, nếu thích bạn có thể thêm thắt các chức năng.
Để bỏ dấu theo lối VIQR thì thay vì các con số 1,2,3,4,5,6,7,8,9 ta dùng ' ` ? ~ .
^ + (hay *) ( d (hay -). Ðặc biệt control vnTextbox nầy dùng gần như hoàn
toàn look-up table để tính ra các nguyên âm có dấu. Trước hết, mỗi khi user
đánh một con số từ 1 đến 9, thì program nhìn xem character phía trước cursor
(gọi là LastCh) là chữ gì. Kế đó nó tìm đến hàng chữ chứa toàn bộ những
nguyên âm có thể thay thế LastCh, tùy theo con số mà user vừa đánh vào. Ở
đây kể cả trường hợp user vừa đánh một Backspace.
Cái bảng chứa những hàng chữ ấy đuợc chứa trong một array-of-string tên
ChList và nó được initialised trong Constructor Sub New của vnTextbox như
dưới đây:
Private ChList(148) As String ' List of character groups like
"aáàảãạâ-ă"
ChList(0) =
"aáàảãạăắằẳẵặâấầẩẫậeéèẻẽẹêếềểễệiíìỉĩịoóòỏõọôốồổỗộơớờởỡợuúùủũụưứừửữựyýỳỷ
ỹỵdđAÁÀẢÃẠĂẮẰẲẴẶÂẤẦẨẪẬEÉÈẺẼẸÊẾỀỂỄỆIÍÌỈĨỊOÓÒỎÕỌÔỐỒỔỖỘƠỚỜỞỠỢUÚÙỦŨỤƯỨỪỬỮỰY
ÝỲỶỸỴD"
ChList(1) = "aáàảãạâ-ă"
ChList(2) = "a^àảãạấ-ắ"
ChList(3) = "aá^ảãạầ-ằ"
ChList(4) = "aáà^ãạẩ-ẳ"
ChList(5) = "aáàả^ạẫ-ẵ"
ChList(6) = "aáàảã^ậ-ặ"
ChList(7) = "aắằẳẵặâ-^"
ChList(8) = "ăắằẳẵặấ-ắ"
ChList(9) = "ăắằẳẵặầ-ằ"
ChList(10) = "ăắằẳẵặẩ-ẳ"
ChList(11) = "ăắằẳẵặẫ-ẵ"
ChList(12) = "ăắằẳẵặậ-ặ"
ChList(13) = "aấầẩẫậ^-ă"
ChList(14) = "âấầẩẫậấ-ắ"
ChList(15) = "âấầẩẫậầ-ằ"
ChList(16) = "âấầẩẫậẩ-ẳ"
ChList(17) = "âấầẩẫậẫ-ẵ"
ChList(18) = "âấầẩẫậậ-ặ"
ChList(19) = "eéèẻẽẹê"
ChList(20) = "e^èẻẽẹế"
ChList(21) = "eé^ẻẽẹề"
ChList(22) = "eéè^ẽẹể"
ChList(23) = "eéèẻ^ẹễ"
ChList(24) = "eéèẻẽ^ệ"
ChList(25) = "eếềểễệ^"
ChList(26) = "êếềểễệế"
ChList(27) = "êếềểễệề"
ChList(28) = "êếềểễệể"
ChList(29) = "êếềểễệễ"
ChList(30) = "êếềểễệệ"
. . .
ChList(0) chứa toàn bộ các nguyên âm. Tương ứng với mỗi nguyên âm (LastCh)
trong ChList(0) là một hàng chứa tất cả mọi chữ có thể đuợc dùng để thay thế
LastCh khi user đánh vào một con số 1..9 hay Backspace.
Thí dụ nếu LastCh là
à
, ta sẽ dùng ChList(3), nó chứa các chữ:
aá^ảãạầ-ằ
Kế đó nếu user đánh số 3 ta sẽ thay thế dấu sắc thành dấu hỏi để có chữ
ả
.
Còn nếu thay vì đánh số 3, user đánh số 8, thì ta sẽ có chữ
ằ
, tức là thêm dấu
ă
cho chữ
à
.
Nếu user đánh thêm một số 7 thì character tướng ứng với số 7 trong hàng
aá^ảãạầ-ằ là
-
, hể gặp character - thì ta làm ngơ.
Nếu user đánh thêm một số 2 sau chữ
à
bạn sẽ thấy character tướng ứng với số
2 trong hàng aá^ảãạầ-ằ là
^
. Điều nầy nhắc ta biết là user đánh
a
22, nên ta
sẽ hiển thị
a
2.
Nếu user đánh Backspace, thay vì một con số, ta sẽ dùng nguyên âm nằm ở đầu
dòng, tức là chữ
a
. Như thế nếu LastCh là
ẩ
, thì sau một Backspace ta có
ả
,
sau thêm một Backspace kế tiếp ta sẽ còn lại
a
.
Kỹ thuật Program dùng để thay thế LastCh là select (highlight) LastCh rồi Paste
nguyên âm mới.
Dưới đây là Listing của Function GetToneCharPos() để trả về một giá trị từ 1
đến 9 tượng trưng cho dấu:
Private Function GetToneCharPos( ByVal KeyChar As Integer) As Integer
' If Typing stype is VNI, see if user enters "1".."9" or "d"
' If so return 1..9 and also 9 for "d". Otherwise return -1
'
' If Typing stype is VIQR, return 1..9 for characters '`?~.^+(d .
Otherwise return -1
' We also allow for * and - to be same as + and d successively.
' i.e. u+ or u* and dd or d- are OK.
GetToneCharPos = -1
If mTypingStyle = "VNI" Then
If (KeyChar = 68) Or (KeyChar = 100) Then ' ie. "d" for dd or DD
GetToneCharPos = 9
ElseIf (KeyChar >= &H31) And (KeyChar <= &H39) Then
' it's a digit. KeyChar of "1" is &H31
GetToneCharPos = KeyChar - &H30
End If
ElseIf mTypingStyle = "VIQR" Then
Console.WriteLine("KeyChar:{0}", KeyChar)
Select Case KeyChar
Case 39 '
GetToneCharPos = 1 ' '
Case 96
GetToneCharPos = 2 ' `
Case 126
GetToneCharPos = 4 ' ~
Case 63
GetToneCharPos = 3 ' ?
Case 46
GetToneCharPos = 5 ' .
Case 94
GetToneCharPos = 6 ' ^
Case 43, 42 ' + or *
GetToneCharPos = 7
Case 40
GetToneCharPos = 8 ' (
Case 100, 68, 45 ' d D or -
GetToneCharPos = 9
End Select
End If
End Function
Trong Control vnTextbox ta không thể để code hỗ trợ đánh dấu chữ Việt trong
Sub vnTextbox_KeyDown hay Sub vnTextbox_KeyUp được vì một khi
KeyDown hay KeyDown Events đã được raised rồi ta không thể bỏ qua Keystroke
hay thay đổi trị giá của nó thành 0 như trong VB6. Do đó, ở đây ta Override
Function ProcessKeyMessage. Nếu giá trị trả về (Returned value) của hàm
ProcessKeyMessage là True thì ta ngăn cản không cho Keyboard Event xẩy ra.
Loại Event có thể xẩy ra sau đó tùy thuộc vào trị số của m.Msg. Trong hàm
ProcessKeyMessage, ta chỉ xử lý thông điệp m.Msg = KeyUp (có giá trị 258). Nếu
m.Msg là cho KeyDown hay KeyPress thì ta làm ngơ và cho ProcessKeyMessage
return False để KeyDown hay KeyPress events xẩy ra như bình thường.
Protected Overrides Function ProcessKeyMessage( ByRef m As
System.Windows.Forms.Message) As Boolean
' Get out if this is not a KeyUp message
If m.Msg <> 258 Then Return False
Const Delay As Integer = 100
' Obtain the Keystroke character
Dim KeyChar As Integer = m.WParam.ToInt32
' Process a keystroke
Dim Pos, ToneCharPos, Offset As Integer
Dim NewCh As String
If KeyChar = 8 Then ' It's a backspace character
If Me.SelectionStart = 0 Then Return True
' Obtain the position of the line containing all possible modified
characters
Pos = GetLastCharMapPos()
' Select the character just on the left of the cursor
Me.SelectionStart -= 1
Me.SelectionLength = 1
If Pos > 0 Then
If LastCh <> ChList(Pos).Substring(0, 1) Then
' Get here if backspace means removing ^ or ', ` etc..
' Copy the new (modified) character to clipboard, it's the
leftmost
' character on the line
Clipboard.SetDataObject(ChList(Pos).Substring(0, 1))
' Paste it to replace the character on the left
Me.Paste()
ConcatCharacterIfFailed(ChList(Pos).Substring(0, 1))
Else
' get here if it's a genuine backspace
Me.Cut()
End If
Else
' Select the character just on the left of the cursor
Me.Cut()
End If
' Swallow the actual keystroke
Return True
ElseIf KeyChar = 92 Then ' it's a back slash \ which is the Escape
character
If Not EscapeFlag Then
EscapeFlag = True ' Set the Escape flag
Return True ' Swallow the actual keystroke
End If
Else
' Map the key entered (i.e: "1".."9", "d", or "'", "`" , "?" ,
"~", ".", "+", "(" )
' to the position 1..9, to be used for selecting the new character
from a string like:
' eg: a a' a` a? a~ a. a^ - a(
ToneCharPos = GetToneCharPos(KeyChar)
If ToneCharPos > 0 Then
' Get here if a digit in range 1..9 has been entered
' Ignore the digit if Escape Flag is set
If Not EscapeFlag Then
' Obtain the position of the line containing all possible
modified characters
Pos = GetLastCharMapPos()
' If Pos = 0 then simply display the character, ie. leave