VB6 กับการทำหน้าจอ Login ระดับมืออาชีพ ภาค 1 - ปฐมบท

Category »  VB 6/VB.Net
โดย : Webmaster เมื่อ 18/11/2552   เวลา: 15:15
(อ่าน : 54007) 
ปฐมบท ... มหากาพย์ล็อคอินเข้าสู่ระบบ ... หลายคนก็คงสงสัยว่าผมหมดมุขแล้วหรือยังไง ถึงได้เอาเรื่องง่ายๆหมูๆแบบนี้มานำเสนอ ... แต่เรื่องง่ายของเรา มันอาจจะเป็นเรื่องยากสำหรับคนอื่นก็ได้น่ะครับ ดังนั้นการนำเสนอบทความนี้ขึ้นมา ย่อมจะไม่ธรรมดาและไม่ซ้ำกับผู้ใดอยู่แล้ว 55555+ ... ดังนั้นเพื่อความเข้าใจที่ดี ผมเลยแยกการอธิบายออกเป็นชุด เป็นตอนๆไปก่อน แล้วค่อยเสริมเติมแต่งสิ่งต่างๆเข้าไปตามมาอีกในภายหลัง เพราะว่าจุดเริ่มต้นของการทำ Log in เข้าสู่ระบบ มันยังซุกซ่อนเหตุการณ์อื่นๆที่จำเป็นต้องใช้ ต้องทำอะไรได้อีกบ้าง ... ไม่เชื่อก็ลองติดตามกันดูครับ ...
    ทำความเข้าใจกับชุดโครงสร้างข้อมูล สำหรับ Visual Basic 6.0
    การกำหนดชนิดของข้อมูลเป็นชุดๆเอาไว้ ในลักษณะเป็นแบบโครงสร้าง (Structure) โดยที่ทำการกำหนดรูปแบบต่างๆเอาเอง หรือ เรียกว่า User-Defined Types (UDTs) จะประกอบไปด้วยประเภทของข้อมูลหลากหลายชนิด เช่น String, Long หรือ Boolean มาประกอบรวมกันเป็นชุดข้อมูล แต่การกำหนด หรือ ประกาศจะถือว่าเป็นเพียงต้นแบบ หรือ Template เท่านั้นน่ะครับ ... ยังเรียกใช้งานโดยตรง หรือ เก็บข้อมูลเข้าไปไม่ได้
    มีรูปแบบดังต่อไปนี้ คือ ... แอ่นแอ้นนนนนน
  • [Public | Private] Type TypeName
    • Variable1 As DataType
    • ...
    • ...
    • VariableN As DataType
  • End Type
    ตัวอย่าง
  • Public Type MyProduct
    • ProductPK As Long
    • ProductID As String
    • ProductName As String
  • End Type
    อย่างที่ได้กราบเรียนไปแล้ว ... โครงสร้างดังกล่าว มันยังไม่สามารถจัดเก็บข้อมูลเข้าไปได้ เราจึงต้องกำหนดตัวแปรให้กับชุดข้อมูล (UDTs) อีกทีก่อนดังนี้ คือ
    Public ProductRecord As MyProduct
    เพื่อให้เรียกใช้งานชุดข้อมูล MyProduct ผ่านทางตัวแปร ProductRecord อีกที
  • เช่น
    • ProductRecord.ProductPK เพื่อแสดงค่า Primary Key
    • ProductRecord.ProductName เพื่อแสดงค่าชื่อสินค้า
    กล่าวถึงทำไมล่ะ... กับการกำหนดตัวแปรแบบโครงสร้าง เอิ๊กๆๆๆๆ (บอกแล้วว่าเป็น Log in System ในระดับมืออาชีพ) ...สำหรับบทความของผม จะนำมาใช้เพื่อจัดเก็บข้อมูลเข้าสู่หน่วยความจำ ในแต่ละครั้งที่ผู้ใช้งานเข้าสู่ระบบมาครับ ... พี่น้อง ... ยังไม่ได้เก็บลงฐานข้อมูลน่ะครับ เอาไว้ตอนต่อไปที่จะกล่าวถึงอีกที ... สำหรับตอนนี้ ผมอธิบายตามโค้ดทีละ Step อยู่แล้ว ... คิดว่าคงเข้าใจได้ไม่ยากนักหรอกน่ะครับ ...
ดาวน์โหลด
ดาวน์โหลด Source Code สำหรับ MS Visual Basic 6.0 - Service Pack 6
 ดาวน์โหลด Visual Basic 6.0 SP5: Run-Time Redistribution Pack
 ดาวน์โหลด Microsoft Data Access Object (MDAC) และ Jet 4.0 Update
 ดาวน์โหลด Microsoft Visual Basic Service Pack 6
ข้อมูลเพิ่มเติม
ตรวจสอบการกดแป้นคีย์บอร์ดเพื่อให้รับค่าเฉพาะตัวเลข และ จุดทศนิยมเท่านั้น (VB6)
ตรวจสอบการกดแป้นคีย์บอร์ดเพื่อให้รับค่าเฉพาะตัวเลข (VB.Net)
การเข้ารหัส และ ถอดรหัส ด้วยตรรกศาสตร์ XOR (eXclusive OR) - ภาค 1
การเข้ารหัส และ ถอดรหัส ด้วยตรรกศาสตร์ XOR (eXclusive OR) - ภาค 2


Design Time


Run Time

modDataBase.bas - โมดูลทำมาหากิน

' Connection เพื่อเชื่อมต่อเข้ากับฐานข้อมูล
Global ConnDB As New ADODB.Connection
' ใช้ RecordSet เพียงแค่ 2 หรือ 3 ตัวก็เหลือเฟือแล้วครับ ... จะประกาศอะไรกันนักกันหนาหลายๆตัว ... 55555+
Global RS As New ADODB.Recordset
Global DS As New ADODB.Recordset
' นี่ก็เหมือนกันครับ ใช้ SQL Statement หรือ Query นั่นแหละ ... แค่ 2 ตัวก็พอแล้ว
Global Statement As String
Global SQLStmt As String

' ตัวแปรแบบที่มองเห็นกันหมดทั้ง Project มีไว้เพื่อส่งค่า Primary Key ข้ามฟอร์ม
Global gPK As Long
'
' กำหนดว่าเป็นการเพิ่ม หรือ แก้ไขข้อมูล
Global blnNewData As Boolean

' เปิดไฟล์ฐานข้อมูลเพื่อทำการเชื่อมต่อ - Connection
Public Sub OpenDataBase()
On Error GoTo Err_Handler
Dim DB_File As String
    DB_File = App.Path
    If Right$(DB_File, 1) <> "\" Then DB_File = DB_File & "\"
    DB_File = DB_File & "UserLog.MDB"
    ' เชื่อมต่อไฟล์ฐานข้อมูล MS Access
    Set ConnDB = New ADODB.Connection
    ConnDB.ConnectionString = _
        "Provider=Microsoft.Jet.OLEDB.4.0;" & _
        "Data Source=" & DB_File & ";" & _
        "Persist Security Info=False"
    ConnDB.Open
    Exit Sub
    
Err_Handler:
    MsgBox "Error : " & Err.Number & " " & Err.Description
    End
End Sub

' ปิดการเชื่อมต่อ
Public Sub CloseDataBase()
    ' ตรวจสอบว่ามีการเชื่อมโยง - Connect ข้อมูลหรือไม่
    If ConnDB.State = adStateOpen Then
        ConnDB.Close
        Set ConnDB = Nothing
    End If
End Sub
modFunction.bas - แยกมาเก็บพวกโปรแกรมย่อยต่างๆทั้งหลายที่ต้องใช้งานประจำ
การเลือกใช้ Select Case ... จะเหมาะกับเงื่อนไขที่เป็น Range หรือ ช่วงข้อมูลครับ ... พี่น้อง

Option Explicit

' =================================================
' ตรวจสอบค่าเฉพาะค่าตัวเลข 0 - 9 เท่านั้น ... รวมทั้งการกด Back Space (8) และ Enter (13) ด้วย
Function CheckDigitOnly(Index As Integer) As Integer
' =================================================
    Select Case Index
        ' เมื่อ ASCII Code 48 - 57 คือตัวอักขระ 0 - 9
        Case 48 To 57
        Case 8           ' Back Space
        Case 13         ' Enter
        
        Case Else
            Index = 0
    End Select
    
    ' Return ค่ากลับ
    CheckDigitOnly = Index

End Function

' =================================================
' ตรวจสอบค่าเฉพาะ a - z  และ A - Z เท่านั้น ... รวมทั้งการกด Back Space (8) และ Enter (13) ด้วย
Function CheckAlphaOnly(Index As Integer) As Integer
' =================================================
    Select Case Index
        ' เมื่อ ASCII Code 65 - 90 คือตัวอักขระ A - Z
        ' เมื่อ ASCII Code 97 - 122 คือตัวอักขระ a - z
        Case 65 To 90, 97 To 122
        Case 8           ' Back Space
        Case 13         ' Enter
        Case Else
            Index = 0
    End Select
    
    ' Return ค่ากลับ
    CheckAlphaOnly = Index

End Function

' =================================================
' ตรวจสอบค่าเฉพาะ 0 - 9, a - z  และ A - Z เท่านั้น ... รวมทั้งการกด Back Space (8) และ Enter (13) ด้วย
Function CheckAlphaNumeric(Index As Integer) As Integer
' =================================================
    Select Case Index
        ' เมื่อ ASCII Code 48 - 57 คือตัวอักขระ 0 - 9
        ' เมื่อ ASCII Code 65 - 90 คือตัวอักขระ A - Z
        ' เมื่อ ASCII Code 97 - 122 คือตัวอักขระ a - z
        Case 48 To 57, 65 To 90, 97 To 122
        Case 8           ' Back Space
        Case 13         ' Enter
        Case Else
            Index = 0
    End Select
    
    ' Return ค่ากลับ
    CheckAlphaNumeric = Index

End Function

' =================================================
' โปรแกรมย่อยในการทำ High Light ใน TextBox ... ใช้ร่วมกับ GotFocus
Public Sub HLText(ByRef sText)
' =================================================
    On Error Resume Next
    With sText
        .SelStart = 0
        .SelLength = Len(sText.Text)
    End With
End Sub
modVarType.bas - เก็บตัวแปรชนิดโครงสร้าง

Option Explicit

' ========================================================
' การกำหนดชนิดของข้อมูลเป็นชุดเอาไว้ (Structure) หรือ User-defined Types (UDTs)
' โดยจะประกอบไปด้วยประเภทของข้อมูลหลายชนิด เช่น String, Long หรือ Boolean
' การประกาศ USER_INFO และประกอบด้วยชนิดของข้อมูลหลากหลาย ... แต่ขณะนี้
' มันเป็นเพียงต้นแบบ หรือ Template เท่านั้นน่ะครับ ... ยังเรียกใช้งานโดยตรงไม่ได้
' ========================================================
' *** จะต้องกำหนดการใช้งานที่ Module ด้วยเท่านั้นน่ะครับ ***
Public Type USER_INFO

    ' Primary Key ของ User
    USER_USERPK As Integer
    
    ' ชื่อผู้ใช้ที่เข้ามาสู่ระบบ
    USER_USERID As String
    
    ' รหัสผ่าน
    USER_PASSWORD As String
    
    ' เป็น Administrator หรือ ผู้ดูแลระบบหรือไม่
    USER_ISADMIN As Boolean
    
    ' เก็บชื่อเต็มของผู้ใช้ เช่น ผู้ดูแลระบบ หรือ พนักงาน 1
    USER_COMPLETENAME As String
    
    ' วันเวลาที่เข้าสู่ระบบ
    USER_TIMELOGIN As Date

End Type
' ========================================================

' อย่างไรก็ตามการกำหนด UDTs เอาไว้มันยังไม่เพียงพอ เพราะ USER_INFO เปรียบเสมือนเป็นเพียง Template
' ซึ่งมันยังไม่สามารถจัดเก็บข้อมูลเข้าไปได้ เราจึงต้องกำหนดตัวแปรให้กับชุดข้อมูล (UDTs) ก่อนดังนี้ คือ
Public CurrUser As USER_INFO
' เพื่อให้เรียกใช้งานชุดข้อมูล USER_INFO ผ่านทางตัวแปรโครงสร้าง CurrUser อีกที

' การเรียกใช้งาน เช่น ....
' CurrUser.USER_ID ... ก็จะแสดงค่าของชื่อผู้ใช้ เช่น ADMIN หรือ USER
' CurrUser.USER_ISADMIN ... เก็บสถานะว่าเป็น True หรือ False ...

' =========================== เพิ่มเติม ========================
' ในกรณีที่เราต้องการกำหนดระดับ User ให้มีหลายๆระดับ
' - กำหนดฟิลด์ในตารางข้อมูล UserLevel แบบ Byte ก็พอ (เหลือเฟือ)
' - กำหนด Data Type คือ USER_LEVEL As Byte
' เช่น 0 = Admin, 1 = Manager, 2 = User
' =========================================================
frmLogin.frm - เป็นฟอร์มเริ่มต้นการทำงาน และ ให้ไล่เรียงโค้ดจากที่นี่เลยน่ะครับ

Option Explicit

Private Sub Form_Load()
    Me.Move (Screen.Width - Me.Width) \ 2, (Screen.Height - Me.Height) \ 2
    txtUserID.Text = ""
    txtPassword.Text = ""
    
    Call OpenDataBase
    
End Sub

' ======================================================
' เริมต้นทำการตรวสอบข้อมูลของผู้ใช้งาน
Private Sub cmdOK_Click()
' ======================================================

' ตัวแปรเก็บค่ารหัสผ่านจากตารางข้อมูล
Dim strPass As String

    ' ตรวจสอบค่าว่างหรือไม่ ... หากใช่ให้กลับไปป้อนชื่อผู้ใช้ หรือ รหัสผ่านใหม่
    If Trim(txtUserID.Text) = "" Or Len(Trim(txtUserID.Text)) = 0 Then txtUserID.SetFocus: Exit Sub
    If Trim(txtPassword.Text) = "" Or Len(Trim(txtPassword.Text)) = 0 Then txtPassword.SetFocus: Exit Sub

    ' เริ่มตรวจสอบชื่อผู้ใช้งานก่อนว่ามีในระบบหรือไม่
    ' การกำหนด Set RS = New Recordset หมายถึงการปิด RecordSet เดิมลง (กรณีเปิดค้างไว้)
    Set RS = New Recordset
    Statement = "SELECT * FROM tblUser WHERE UserID = " & "'" & Trim(txtUserID) & "'"
    RS.CursorLocation = adUseClient
    RS.Open Statement, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
    
    ' การใช้ RecordCount ได้ เราต้องตั้งการอ่านแบบ RS.CursorLocation = adUseClient
    ' หากพบข้อมูล ให้คัดลอกรหัสผ่านไว้ในตัวแปรก่อน ... อันนี้ผมเผื่อเอาไว้ในกรณีที่มีการเข้ารหัสข้อมูล (Encrypt Data)
    If RS.RecordCount > 0 Then
        
        strPass = "" & RS("Password")
        
        ' กรณีที่เราเข้ารหัสข้อมูล (Encrypt) ก็ต้องถอดรหัส (Decrypt) ก่อน จึงจะเปรียบเทียบค่าได้
        ' นี่แหละคือสาเหตุที่ผมต้องแยกการตรวจสอบชื่อผู้ใช้  และ รหัสผ่าน ออกจากกัน
        ' strPass = DecryptData(RS("Password"))
        
    ' ไม่พบข้อมูลให้กลับไปแก้ไขใหม่
    Else
        MsgBox "ชื่อผู้ใช้งานไม่ถูกต้อง กรุณาลองใหม่อีกครั้ง.", vbOKOnly + vbExclamation, "รายงานสถานะ"
        txtUserID.SetFocus
        RS.Close:   Set RS = Nothing
        Exit Sub
    End If
    
    ' เมื่อชื่อผู้ใช้ถูกต้องแล้ว ก็ไปตรวจสอบรหัสผ่านในลำดับต่อไป
    ' ตรวจสอบรหัสผ่านของผู้ใช้งานก่อนว่าตรงกันกับในระบบหรือไม่ โดยปรับตัวอักขระทุกตัวให้เป็นตัวเล็ก (LCase)
    If LCase(txtPassword.Text) <> LCase(strPass) Then
        MsgBox "รหัสผ่านไม่ถูกต้อง กรุณาลองใหม่อีกครั้ง.", vbOKOnly + vbExclamation, "รายงานสถานะ"
        txtPassword.SetFocus
        RS.Close:   Set RS = Nothing
        Exit Sub
    End If
    
    ' คัดลอกค่าต่างๆที่จำเป็นเอาไว้ในชุดข้อมูล CurrUser เพื่อนำไปใช้ประโยชน์ได้ในภายหลัง
    ' เช่น การเปลี่ยนรหัสผ่าน หรือ การตรวจสอบสถานะว่า User นี้ เข้าถึงข้อมูลได้ในระดับไหน
    ' จากส่วนของโมดูล ... modVarType.bas
    With CurrUser
    
        ' ตัวนี้สำคัญเลย เมื่อเวลาการบันทึกข้อมูลลงไปในตารางอื่นๆ จะได้รู้ว่า User ใดเป็นผู้กระทำ
        ' ผมเก็บเฉพาะค่า Primary Key หรือ UserPK น่ะครับ ... UserID ไม่ได้เอาไปเก็บด้วย
        ' ต้องแยกความแตกต่างระหว่าง Primary Key กับพวกเหล่า ID ทั้งหลายเช่น รหัสผู้ใช้
        ' รหัสสินค้า รหัสลูกค้า เหล่านี้ ... ในหนังสือน่ะมันสอนกันมาผิดๆ ... แทบจะทุกเล่ม
        .USER_USERPK = RS("UserPK")
        
        .USER_USERID = "" & RS("UserID")
        
        
        ' เพื่อตรวจสอบได้ว่า User ใดเป็นผู้ที่ทำรายการเพิ่ม (AddedByFK) หรือ แก้ไขรายการ (LastUserFK)
        ' เช่น UserPK = 1, UserID = ADMIN
        ' ไม่ว่าคุณจะเปลี่ยนชื่อ UserID จาก ADMIN มาเป็น THONGKORN หรือ SURAPON ก็ตาม
        ' มันก็ยังชี้ชัดๆว่าเป็น User คนเดียวกันอยู่วันยังค่ำ โดยไม่ต้องไปเปลี่ยนแปลงข้อมูลใดๆในตารางอื่นๆเลย
        ' เพราะเราไม่ได้เปลี่ยนแปลงค่า Primary Key ที่จะต้องสร้างความสัมพันธ์ไปยังตารางอื่นๆ
        ' แบบนี้แหละที่เราเรียกว่า Primary Key ... ตอนต่อไปจะนำเสนออีกทีครับ
' นำไปใช้ในเวลาที่ผู้ใช้ต้องการเปลี่ยนรหัสผ่าน .USER_PASSWORD = LCase(strPass) .USER_COMPLETENAME = "" & RS("CompleteName") .USER_TIMELOGIN = Now() ' True คือ Admin ... หากเป็น False ก็คือระดับ User .USER_ISADMIN = RS("Admin") End With RS.Close: Set RS = Nothing ' เข้าสู่ระบบ ... ทดสอบเอาค่าชนิดข้อมูลที่ประกาศเอาไว้มาใช้งาน ' ทดสอบว่าเป็นระดับ Admin หรือไม่ ... If CurrUser.USER_ISADMIN Then MsgBox "ยินดีต้อนรับคุณ " & CurrUser.USER_COMPLETENAME & " เข้าสู่ระบบ" & vbCrLf & _ "คุณอยู่ในระดับ Administrator.", vbOKOnly + vbInformation, "รายงานสถานะ" Else MsgBox "ยินดีต้อนรับคุณ " & CurrUser.USER_COMPLETENAME & " เข้าสู่ระบบ" & vbCrLf & _ "คุณอยู่ในระดับ User เท่านั้น.", vbOKOnly + vbInformation, "รายงานสถานะ" End If ' ปกติก็ต้อง Unload แล้วเรียกฟอร์มหลักขึ้นใช้งาน End End Sub
Conclusion:

อยากจะกล่าวซ้ำๆเรื่องของ Primary Key ครับ ... ในอดีตกาลนานนับสิบกว่าปีมาแล้ว ตอนที่เริ่มจับงานฐานข้อมูลใหม่ๆ ผมพบว่าเดินหลงทางผิดๆไปกับเรื่องนี้พอสมควร กว่าจะหันกลับมาทำความเข้าใจใหม่ (ด้วยตัวเอง) ก็ต้องรื้อระบบงาน "โปรแกรมจัดเก็บข้อมูลการซ่อมบำรุงอุปกรณ์" ขึ้นมาทำใหม่ทั้งหมด ซึ่งก็ใช้เวลาไปไม่ใช่น้อยๆเลย ... แต่เมื่อเวลาผันผ่านเข้าสู่ยุค 3G กลับกลายเป็นว่า ยังมีผู้คนอีกเป็นจำนวนไม่น้อย ที่ยังติดหล่ม จมดิ่ง อยู่กับเรื่องราวความไม่เข้าใจ ของคำว่า Primary Key ...