Thừa kế Textbox để đánh chữ Việt Unicode  
 

(Post 05/05/2006) dotNET cho phép ta tha hồ thừa kế, và bài này sẽ minh họa cách chúng ta sẽ 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.

Hình 1

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ư Hình 1

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ữ đÐ ta dùng d9D9. 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á^ảãạầ-ằ-, 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á^ảãạầ-ằ^. Điều nầy nhắc ta biết là user đánh a22, nên ta sẽ hiển thị a2.

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 the keystroke alone

            If Pos > 0 Then

              ' Work out the offset of the new character on the line

               Offset = ToneCharPos + 1

               If Offset <= ChList(Pos).Length Then

                 ' Extract the new character

                  NewCh = ChList(Pos).Substring(Offset - 1, 1)

                 ' Console.WriteLine("ChList:{0} Pos:{1} Offset-1:{2}", ChList(Pos), Pos, Offset - 1)

                  If NewCh = "^" Then ' roll back last character

                    ' eg: letter a followed by 11 => á1 to become a1

                    ' Select the character just on the left of the cursor

                     Me.SelectionStart -= 1

                     Me.SelectionLength = 1

                    ' Copy leftmost character on the line to clipboard

                     Clipboard.SetDataObject(ChList(Pos).Substring(0, 1))

                    ' Paste it to replace the character on the left

                     Me.Paste()

                     ConcatCharacterIfFailed(ChList(Pos).Substring(0, 1))

                    ' Leave keystroke alone to let it fall through and be displayed

                    ' Forget it if the new character is "-", let the keystroke displayed

                  ElseIf NewCh <> "-" Then

                    ' Select the character just on the left of the cursor

                     Me.SelectionStart -= 1

                     Me.SelectionLength = 1

                    ' Copy the new (modified) character to clipboard

                     ToClipboard(NewCh)

                    ' Paste it to replace the character on the left

                     Me.Paste()

                     ConcatCharacterIfFailed(NewCh)

                     Return True ' Swallow the actual keystroke

                  End If

               End If

            End If

         End If

      End If

   End If

   EscapeFlag = False ' Reset the Escape Flag

End Function

Để vnTextbox hỗ trợ hai lối bỏ dấu, VNI và VIQR, ta cho nó Property TypingStyle như sau:

Property TypingStyle() As String

   Get

      Return mTypingStyle

   End Get

   Set ( ByVal Value As String)

     ' Chỉ chấp nhận "VNI" hay "VIQR" mà thôi

      If Value = "VNI" Or Value = "VIQR" Then

         mTypingStyle = Value

      End If

   End Set

End Property

Lưu ý cách copy một character vào Clipboard:

' Copy it to clipboard

Clipboard.SetDataObject(Ch)

Việc lấy Unicode text string từ Clipboard gồm có hai bước:

Tạo một DataObject Interface với Clipboard.GetDataObject()

Dùng Interface ấy để lấy data từ Clipboard dưới dạng Unicode (DataFormats.UnicodeText), rồi đổi nó ra string

' Create a new instance of the DataObject interface.

Dim data As IDataObject = Clipboard.GetDataObject()

' Retrieve LastCh the character in Unicode Text format from clipboard

LastCh = data.GetData(DataFormats.UnicodeText).ToString()

Chắc bạn đã để ý, chữ Việt ta dùng ở đây là Unicode, mặc dầu ta không nhắc gì đến encoding của nó hay code point của mỗi character. Nếu bạn chưa quen với Unicode cho chữ Việt thì hãy đọc bài Dùng Unicode chữ Việt trong .NET

Rất tiếc vì method Paste bên trong vnTextbox không reliable (bất chừng), nhiều khi nó không Paste được nên LastCh vẫn còn bị highlighted. Trong trường hợp đó Sub ConcatCharacterIfFailed sẽ dùng string concatenation (ghép strings) để khắc phục khó khăn. Nhưng cách ghép strings không hiệu năng bằng Paste.

Control vnTextbox có hỗ trợ Escape character \. Con số theo sau Escape character \ sẽ không bị dùng vào việc bỏ dấu cho nguyên âm ngay trước đó.

Xin lưu ý: Ðể program nầy chạy bỏ dấu đuợc bạn phải tạm thời ngưng các programs như VPSkey hay VietKey, UniKey .v.v.. Lý do là các programs kia sẽ giựt trước các keystrokes của những con số 1 đến 9, sau khi kiểm tra rồi không chịu buông ra cho program nầy thấy.

Bạn có thể tải về mã nguồn của control vnTextboxchương trình thử. Hai Zip files nầy là cho 2 projects. Bạn load vnTextbox và compile trước để dùng WindowsControlLibrary2.dll trong bin folder làm reference cho project TestvnTextbox. Sau khi load TestvnTextbox, bạn hãy remove WindowsControlLibrary2 từ References của nó rồi dùng Menu Command Project |Add Reference để refernce library WindowsControlLibrary2.dll vừa mới compile.

Nếu bao giờ IDE than phiền về Licence bạn chỉ cần delete file licences.licx trong Project Folder.

Dưới đây là hình form chính của program TestvnTextbox.

(theo Vovisoft.com)


 
 

 
     
 
Công nghệ khác:


Triggers And ViewsSử dụng hàm API trong C#
Thiết kế giao diện cho phần mềmUSB Flashdrive nhận dạng vân tay
Cách dùng Google hiệu quảCryptography – Mật mã học
  Xem tiếp    
 
Lịch khai giảng của hệ thống
 
Ngày
Giờ
T.Tâm
TP Hồ Chí Minh
Hà Nội
 
   
New ADSE - Nhấn vào để xem chi tiết
Mừng Sinh Nhật Lần Thứ 20 FPT-APTECH
Nhấn vào để xem chi tiết
Bảng Vàng Thành Tích Sinh Viên FPT APTECH - Nhấn vào để xem chi tiết
Cập nhật công nghệ miễn phí cho tất cả cựu sinh viên APTECH toàn quốc
Tiết Thực Vì Cộng Đồng
Hội Thảo CNTT
Những khoảnh khắc không phai của Thầy Trò FPT-APTECH Ngày 20-11