thongkorn โพสต์ 2017-11-8 12:51:38

แจกฟรี Source Code VB6+Access โปรแกรมระบบฐานข้อมูลครุภัณฑ์ ภาคเขียนโปรแกรม

http://www.g2gnet.com/webboard/images/vb6/AssetMain.png
หน้าตาเมื่อรันโปรแกรม ...
http://www.g2gnet.com/webboard/images/vb6/AssetDetail.png
รายละเอียดของรายการครุภัณฑ์
http://www.g2gnet.com/webboard/images/vb6/AssetDB.png
เวลาผ่านมาอย่างยาวนานถึง 8 ปีแล้ว กับการที่แอดมินได้เริ่มต้นในการแจกโค้ด Visual Basic 6 แบบฟรีๆ โปรเจคระบบฐานข้อมูลครุภัณฑ์ ซึ่งแอดมินใช้ Component มาตรฐานของ Microsoft หากเป็นงานจริงๆจะหรูเริ่ดกว่านี้เยอะครับ 5555+ ... ก่อนอื่นต้องขอชี้แจงเกี่ยวกับการออกแบบในโปรเจคตัวนี้กันสักเล็กน้อย เพราะเมื่อหลายท่านได้ดาวน์โหลดโค้ดโปรแกรมไป ก็คงจะต้องมีคำถามเกิดขึ้นในใจว่า ทำไมแอดมินถึงได้ออกแบบตารางข้อมูลในลักษณะเยี่ยงนี้???

ข้อมูลครุภัณฑ์ในส่วนงานภาครัฐ โดยเฉพาะสถานศึกษา เขามักจะมี Detail หรือรายละเอียดต่างๆของวัสดุครุภัณฑ์ที่ซ้ำซ้อนกันมาก สิ่งที่แตกต่างกันก็คือ รหัสครุภัณฑ์ เท่านั้นเอง เช่น ชื่อครุภัณฑ์ว่า คอมพิวเตอร์แบบตั้งโต๊ะ ซึ่งจัดวางอยู่ใน ห้องปฏิบัติการหมายเลขนี้ มันมีจำนวนอยู่ 40 ชุดที่เหมือนกันเลย เพราะเวลาจัดซื้อจัดจ้างในแต่ละครั้ง ก็มักจะเหมารวมกันมาเป็นแบบเข่งๆ ทีนี้ในส่วนของผู้พัฒนาโปรแกรมต้องคิดอย่างแรกเลยว่า จะทำอย่างไรให้ผู้ใช้งานเขาสะดวกในการคีย์ข้อมูล??? เอ้า แค่นี้ยังไม่พอ เนื่องจากข้อมูลมันเกิดความซ้ำซ้อน แล้วเราจะทำอย่างไรจึงจะสามารถลดความซ้ำซ้อนนี้ลงไปได้ ... ก็เลยเกิดการเชื่อมโยงความสัมพันธ์ของตารางข้อมูลต่างๆตามภาพที่ปรากฏ และนำมาใช้ในงานนี้ ...

แอดมินจะขอตัดบางส่วนที่คิดว่าเป็นจุดสำคัญๆ มาเน้นย้ำเพื่ออธิบายให้เท่านั้นนะครับ ...

การโหลดค่าจากตารางย่อย (Detail) คือพวกกลุ่มตารางข้อมูลต่างๆที่อยู่รายรอบตารางหลัก tblAsset
http://www.g2gnet.com/webboard/images/vb6/Asset-DBLoadComboBox.gif

จากฟอร์ม frmAssetDetail ให้ไปดูโปรแกรมย่อย RecordToScreen ...
      ' ชื่อหลักของครุภัณฑ์ ...
      ' ไปโปรแกรมย่อยในการโหลดรายการต่างๆของตารางย่อย (Detail) เข้าสู่ ComboBox ค่าที่ส่งไปมี
      ' ชื่อ ComboBox, ชื่อตาราง, ชื่อ Field ที่เป็น Primary Key, ชื่อฟิลด์ที่เป็นรายการ
      Call LoadComboBox( _
                cmbAssetName, _
                "tblAssetName", _
                "AssetNamePK", _
                "AssetName" _
                )การเรียกไปยังโปรแกรมย่อยที่ชื่อ LoadComboBox โดยที่มีพารามิเตอร์ต่างๆที่ส่งไปยัง Sub Program
' Load รายการเข้าสู่ ComboBox ค่าที่ต้องส่งมา 4 ชุด คือ
' ชื่อ ComboBox, ชื่อตาราง, ชื่อฟิลด์ Primary Key และ ชื่อฟิลด์รายการ
Sub LoadComboBox( _
    cmb As ComboBox, _
    tblName As String, _
    FieldPK As String, _
    FieldName As String _
    )
   
    Set DS = New ADODB.Recordset
    SQLStmt = "SELECT * FROM " & tblName & " ORDER BY " & FieldName
    Set DS = ConnDB.Execute(SQLStmt, , adCmdText)
   
    cmb.Clear
    Do Until DS.EOF
      cmb.AddItem "" & DS(FieldName)
      DS.MoveNext
    Loop
    DS.Close:    Set DS = Nothing
End Sub
การค้นหาคำ และการจำกัดความยาวในการป้อนข้อมูลลงใน ComboBox ...
http://www.g2gnet.com/webboard/images/vb6/Asset-DBKeyPressCombo.gif
' =========================================================
' โปรแกรมย่อยในการค้นหาคำในรายการของ ComboBox
Private Sub SearchComboBox(cmb As ComboBox, KeyAscii As Integer)
' =========================================================
Dim strKey As String, iRet As Long, LenKey As Long
    cmb.SelText = ""
    strKey = cmb.Text & Chr$(KeyAscii)
    iRet = SendMessage(cmb.hwnd, CB_FINDSTRING, -1, ByVal strKey)
    If iRet <> CB_ERR Then
      LenKey = Len(strKey)
      cmb.Text = cmb.List(iRet)
      cmb.ListIndex = iRet
      KeyAscii = 0
      cmb.SelStart = LenKey
      cmb.SelLength = Len(cmb.Text) - LenKey
    End If

End Sub
' =========================================================การค้นหาคำใน ComboBox ... ในระดับขั้นสูง เราหลีกเลี่ยงไม่พ้นที่จะต้องใช้งาน Win API ... (อ่านรายละเอียดได้ที่นี่)

' =========================================================
' ฟังค์ชั่นที่ช่วยจำกัดความยาวข้อมูลสำหรับ ComboBox
Private Sub MaxComboBox(cmb As ComboBox, MaxChar As Integer, KeyAscii As Integer)
' =========================================================
   If Len(cmb.Text) >= MaxChar Then ' ถ้าหากมีความยาวมากกว่า หรือ เท่ากับที่ได้ตั้งไว้
         If KeyAscii <> vbKeyBack Then ' เป็นการกดคีย์ Back Space หรือไม่
            KeyAscii = 0 ' ไม่ใช่ให้ถือว่าไม่ได้กดคีย์ใดๆเลย
         End If
    End If
End Sub
' =========================================================การจำกัดความยาวที่จะพิมพ์ลงใน ComboBox

การบันทึกข้อมูล แบ่งออกได้ 2 ลักษณะ คือ
1. การเพิ่มข้อมูลใหม่ ส่วนนี้สาระสำคัญ คือ การหา Primary Key ตัวใหม่ และ AssetID ต้องไม่ไปซ้ำกับของเดิม
2. การแก้ไขข้อมูล สาระสำคัญต้องไม่ให้การแก้ไขแล้ว AssetID มีค่าซ้ำกับของเดิม
Private Sub cmdSave_Click()
    ' AssetID
    If Trim(txtAssetID.Text) = "" Or Len(Trim(txtAssetID.Text)) = 0 Then
      MsgBox "กรุณาป้อนทะเบียนครุภัณฑ์ให้เรียบร้อยก่อนด้วย.", vbOKOnly + vbExclamation, "รายงานสถานะ"
      txtAssetID.SetFocus
      Exit Sub
    End If
    If Trim(cmbAssetName.Text) = "" Or Len(Trim(cmbAssetName.Text)) = 0 Then
      MsgBox "กรุณาป้อนชื่อรายการครุภัณฑ์ให้เรียบร้อยก่อนด้วย.", vbOKOnly + vbExclamation, "รายงานสถานะ"
      cmbAssetName.SetFocus
      Exit Sub
    End If
    '
' ตรวจสอบการซ้ำกันของรหัสทะเบียนครุภัณฑ์
' =================================================================
' มันมีโอกาสเป็นได้ 2 กรณี คือ
' เพิ่มข้อมูลใหม่ - ทำให้ txtAssetID.Text จะไม่ตรงกันกับ txtAssetID.Tag (ค่านี้จะต้องว่าง)
' แก้ไขข้อมูล - มีโอกาสได้ 2 ทาง คือ
'      1. ไม่มีการแก้ไขค่าใน txtAssetdID.Text จะทำให้ txtAssetID.Text = txtAssetID.Tag
'         ดังนั้นไม่ต้องไปเสียเวลาทำการเปรียบเทียบค่าเดิมในฐานข้อมูล
'      2. มีการแก้ไขค่าใน txtAssetID.Text ดังนั้น txtAssetID.Text <> txtAssetID.Tag ทำให้
'          ต้องนำค่าไปตรวจสอบว่ามีค่า txtAssetID.Text (ที่เปลี่ยนไป) ไปซ้ำกับค่าเดิมในฐานข้อมูลหรือไม่
' เขียน VB มานับ 10 ปี ... เทคนิคง่ายๆนี้ ผมก็ยังใช้งานได้ไม่เปลี่ยนแปลงทั้ง VB6 หรือ VB.Net
' =================================================================
If txtAssetID.Text <> txtAssetID.Tag Then
    If CheckNewCode > 0 Then
      MsgBox "มีทะเบียนครุภัณฑ์: " & Trim(txtAssetID.Text) & " เรียบร้อยแล้ว กรุณาแก้ไขใหม่ด้วย.", _
                            vbOKOnly + vbExclamation, "รายงานสถานะ"
      txtAssetID.SetFocus
      Exit Sub
    End If
End If
' ================================
' ไปบันทึกข้อมูลได้เลย
Call SaveData
' ================================

End Subก่อนจะทำการบันทึกข้อมูล เราต้องทำการ Validate Data ให้เรียบร้อยก่อน

' ฟังค์ชั่นตรวจสอบการซ้ำกันของทะเบียนครุภัณฑ์ (หรืออื่นๆ) กรณีข้อมูลเป็น Text
' จากนั้นส่งค่ากลับ หากเป็น 0 แสดงว่าไปไม่เกิดการซ้ำกันของข้อมูล
' ค่าส่งกลับมากกว่า 0 ... เกิดการซ้ำกัน จะต้องบังคับไม่สามารถเพิ่ม หรือ แก้ไขข้อมูลได้
Function CheckNewCode() As Long
    Set DS = New Recordset
    SQLStmt = "SELECT * FROM tblAssetWHERE AssetID = " & "'" & Trim(txtAssetID.Text) & "'" & _
                            " ORDER BY AssetPK "
   
    ' หากไม่ระบุเป็น adUseClient จะใช้ค่าเดิมที่ตั้งต้น (Default) เป็นแบบ adUseServer
    ' การใช้แบบ adUseClient เพื่อต้องการให้ใช้เมธอดของการนับ Record ได้ นั่นคือ
    ' DS.RecordCount
    DS.CursorLocation = adUseClient
    DS.Open SQLStmt, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
    CheckNewCode = DS.RecordCount
    DS.Close:    Set DS = Nothing
End Functionค่าทะเบียนครุภัณฑ์จะมีค่าที่ซ้ำกันไม่ได้

การบันทึกข้อมูล ... (หลักการที่แอดมินใช้ใน VB6 จึงถูกนำไปใช้กับ VB.NET ได้ต่อทันที)
' ==================== การบันทึกข้อมูล =======================
Private Sub SaveData()
Set RS = New Recordset
    If NewData Then
      ' ค้นหาค่า PK ก่อน
      Call SetupNewData
      '
      Statement = "SELECT * FROM tblAsset ORDER BY AssetPK"
      RS.Open Statement, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
      RS.AddNew
      RS("AssetPK") = PK
      RS("DateAdded") = FormatDateTime(Now(), vbShortDate)
      RS("DateModified") = FormatDateTime(Now, vbShortDate)
    '========== แก้ไขข้อมูล
    Else
      '
      Statement = "SELECT * FROM tblAsset WHERE AssetPK = " & PK
      RS.Open Statement, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
    End If
    '
    RS("AssetID") = "" & Trim(txtAssetID.Text)
    RS("SerialNumber") = "" & Trim(txtSerialNumber.Text)
    RS("Model") = "" & Trim(txtModel.Text)
    RS("Class") = "" & Trim(txtClass.Text)
    RS("DateReceived") = FormatDateTime(dtpDateReceived.Value, vbShortDate)
    '
    ' ตรวจสอบค่าใน ComboBox
    ' ชื่อครุภัณฑ์ โดยการส่งค่าไปตรวจสอบหาค่า Primary Key ของตารางย่อย (Detail) ค่าที่ส่งไป มี
    ' ชื่อ ComboBox, ชื่อตาราง, Field ที่เป็น PK, Field ที่เป็นรายการ (ค่าที่ต้องทดสอบหา Primary Key)
    RS("AssetNameFK") = VerifyComboBox( _
                                                            cmbAssetName, _
                                                            "tblAssetName", _
                                                            "AssetNamePK", _
                                                            "AssetName" _
                                                            )
   
    ' ยี่ห้อ - BrandName
    ' ชื่อครุภัณฑ์ โดยการส่งค่าไปตรวจสอบหาค่า Primary Key ของตารางย่อย (Detail) ค่าที่ส่งไป มี
    ' ชื่อ ComboBox, ชื่อตาราง, Field ที่เป็น PK, Field ที่เป็นรายการ (ค่าที่ต้องทดสอบหา Primary Key)
    RS("BrandNameFK") = VerifyComboBox( _
                                                      cmbBrandName, _
                                                      "tblBrandName", _
                                                      "BrandNamePK", _
                                                      "BrandName" _
                                                      )
    ' กลุ่ม
    RS("GroupNameFK") = VerifyComboBox(cmbGroupName, "tblGroup", "GroupNamePK", "GroupName")
    ' หน่วยนับ
    RS("UnitFK") = VerifyComboBox(cmbUnitName, "tblUnit", "UnitPK", "UnitName")
    ' แหล่งเงิน
    RS("SourceFK") = VerifyComboBox(cmbSourceName, "tblSource", "SourcePK", "SourceName")
    ' สถานะของครุภัณฑ์
    RS("StatusFK") = VerifyComboBox(cmbStatusName, "tblStatus", "StatusPK", "StatusName")
    ' สถานที่ใช้งาน หรือ เก็บ
    RS("LocationFK") = VerifyComboBox(cmbLocationName, "tblLocation", "LocationPK", "LocationName")
   
    If txtUnitPrice.Text <> "" And Val(txtUnitPrice.Text) >= 0 Then
      RS("UnitPrice") = CCur(txtUnitPrice.Text)
    Else
      RS("UnitPrice") = 0#
    End If
    '
    RS("Reference") = "" & Trim(txtReference.Text)
    RS("Memo") = "" & Trim(txtMemo.Text)
    RS("DateModified") = FormatDateTime(Now(), vbShortDate)
    RS.Update
    '
    RS.Close: Set RS = Nothing
    '
    NewData = False
    ' ส่งค่าไปบอกฟอร์มหลักให้ Refresh
    FormUpdate = True
    MsgBox "บันทึกเข้าสู่ระบบงานฐานข้อมูลเรียบร้อย.", vbOKOnly + vbInformation, "รายงานสถานะ"
    Unload Me
   
End Sub
โปรแกรมย่อยในการสร้าง Primary Key ให้กับรายการครุภัณฑ์
' ===================== สร้าง Record ใหม่ ==========================
' ต้องคำนวณหาค่า Primary Key ให้เรียบร้อยก่อน
Sub SetupNewData()
Dim Rec As Long
Set DS = New Recordset
    ' นำข้อมูลจากตารางมาคำนวณหาค่าสูงสุด
    SQLStmt = "SELECT Max(tblAsset.AssetPK) As MaxPK FROM tblAsset "
    DS.Open SQLStmt, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
    ' หากไม่พบข้อมูลจะทำให้เกิดค่า Null ดังนั้นต้องดัก Error เอาไว้ก่อนด้วย
    If IsNull(DS("MaxPK")) Then
      PK = 1
    Else
      PK = DS("MaxPK") + 1
    End If
    DS.Close: Set DS = Nothing

End Sub
จะมีฟังค์ชั่นที่สำคัญอยู่ตัวหนึ่ง VerifyComboBox ซึ่งต้องใช้งานกับ ComboBox หลายๆตัวก่อนทำการบันทึกข้อมูล (แอดมินถึงต้องสร้างฟังค์ชั่นขึ้นมาใหม่)
' ฟังค์ชั่นที่ใช้ในการตรวจสอบค่าที่อยู่ใน ComboBox เพื่อค้นหาค่า Primary Key ในตารางย่อย
Function VerifyComboBox( _
    cmb As ComboBox, _
    tblName As String, _
    FieldPK As String, _
    FieldName As String _
    ) As Integer

    Dim CountRec As Integer    ' ไว้นับจำนวนของตารางย่อย
    ' ตรวจสอบว่ามีการป้อนข้อมูลหรือไม่ หากไม่มีให้กำหนดค่า Default เป็น 0
    ' จากนั้น Return ค่ากลับ และออกจากฟังค์ชั่นไปเลยครับพี่น้อง ... เพื่อเป็นการไม่เสียเวลา
    If cmb.Text = "" Or Len(cmb.Text) = 0 Or cmb.Text = "-" Then
      VerifyComboBox = 0
      Exit Function
    End If
    '// เป็นการตัดการเชื่อมต่อ RecordSet ของเดิมทิ้ง
    Set DS = New Recordset
    SQLStmt = "SELECT * FROM " & tblName & " WHERE " & FieldName & " = " _
                        & "'" & Trim(cmb.Text) & "'" & _
                        " ORDER BY " & FieldPK
    '/ ======================================================================
    '/ หลายคนมักทำผิด และมองข้ามมันไป สำหรับการเขียน SQL Statement
    '/ SQL Statement ... การค้นหาค่าโดยการเปรียบเทียบกับข้อมูลชนิดข้อความ Text หรือ String
    '/ SELECT * FROM ... WHERE ฟิลด์แบบข้อความ = '1020' ... (อ่านว่า หนึ่ง ศูนย์ สอง ศูนย์)
    '/ เวลาเขียน Statement จะต้องเขียนค่าที่นำมาเปรียบเทียบให้อยู่ภายใต้เครื่องหมาย Single Quote (') เช่น
    '/ "SELECT * FROM ... WHERE AssetID = " & "'" & txtAssetID.Text & "'" ... จดจำรูปแบบนี้ให้ดี
    '/ ส่วนกรณีของตัวเลขไม่ต้องมีเครื่องหมาย Single Quote เช่น
    '/ SELECT * FROM ... WHERE AssetPK = 1020 (อ่านว่า หนึ่งพันยี่สิบ) เช่น
    '/ "SELECT * FROM ... WHERE AssetPK = " & txtAssetPK.Text
    '/ ======================================================================
   
    DS.CursorLocation = adUseClient
    DS.Open SQLStmt, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
    CountRec = DS.RecordCount
   
    ' แสดงว่าไม่มีในรายการ ดังนั้นเราต้องเพิ่มรายการเข้าไปใหม่ในตารางย่อย
    If CountRec <= 0 Then
      Set DS = New Recordset
      SQLStmt = "SELECT Max(" & tblName & "." & FieldPK & ") As MaxPK " & " FROM " & tblName
      DS.CursorLocation = adUseClient
      DS.Open SQLStmt, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
      ' เพิ่มค่า Primary Key ของตารางย่อย (Detail) ขึ้นอีก 1
      CountRec = DS("MaxPK") + 1
      
      ' การที่ผมไม่สั่งปิดตารางข้อมูล DS.Close ก็เพราะคำสั่ง Set DS = New Recordset
      ' มันจะตัดการชื่อมต่อเดิมออกไปในทันทีได้เลยครับ ... ไม่ต้องห่วง
      Set DS = New Recordset
      SQLStmt = "SELECT * FROM " & tblName & " ORDER BY " & FieldPK
      DS.Open SQLStmt, ConnDB, adOpenKeyset, adLockOptimistic, adCmdText
      DS.AddNew
      DS(FieldPK) = CountRec
      DS(FieldName) = cmb.Text
      DS.Update
      ' ส่งค่า PK กลับไปเพื่อบันทึกข้อมูล
      VerifyComboBox = CountRec
      
    ' มีข้อมูลเดิมอยู่แล้ว
    Else
      ' ส่งค่า PK กลับไปเพื่อบันทึกข้อมูล
      VerifyComboBox = DS(FieldPK)
    End If
    DS.Close:    Set DS = Nothing
End FunctionConclusion: แอดมินก็คาดหวังเล็กๆว่า คงพอที่จะทำให้พี่น้องหลายท่านได้แนวคิด ได้มุมมองแปลกๆใหม่ๆเอาไว้ในอ้อมกอด อ้อมใจ กันบ้างพอสมควรนะครับ ... สวัสดี
ดาวน์โหลดโค้ด VB6 (SP6) ต้นฉบับแบบเต็มๆได้ที่นี่

Mrealda911 โพสต์ 2019-1-23 11:31:13

ขอบคุณมากมายครับ

phalakon โพสต์ 2019-5-14 14:18:42

ขอบคุณครับ:D:D

Artkummool โพสต์ 2019-9-16 14:21:50

อาจารย์ครับผมอยากได้ การรับค่าจากเครื่องยิง บาร์โค๊ดพอจะมีไหมครับ

thongkorn โพสต์ 2019-9-17 10:57:55

Artkummool ตอบกลับเมื่อ 2019-9-16 14:21
อาจารย์ครับผมอยากได้ การรับค่าจากเครื่องยิง บาร์โค๊ดพอจะมีไหมครับ

ปกติเครื่องอ่านบาร์โค้ดแบบแท่ง มันสามารถอ่านรหัสแท่ง แล้วแปลงเป็นชุดรหัสตัวอักษร ตัวเลขได้ทันทีเลยครับ

jakapong โพสต์ 2020-2-21 10:44:54

ขอบคุณครับ

g2gsoftuser โพสต์ 2022-10-25 14:48:51

ขอบคุณครับ
หน้า: [1]
ดูในรูปแบบกติ: แจกฟรี Source Code VB6+Access โปรแกรมระบบฐานข้อมูลครุภัณฑ์ ภาคเขียนโปรแกรม