|
หน้าตาเมื่อรันโปรแกรม ...
รายละเอียดของรายการครุภัณฑ์
เวลาผ่านมาอย่างยาวนานถึง 8 ปีแล้ว กับการที่แอดมินได้เริ่มต้นในการแจกโค้ด Visual Basic 6 แบบฟรีๆ โปรเจคระบบฐานข้อมูลครุภัณฑ์ ซึ่งแอดมินใช้ Component มาตรฐานของ Microsoft หากเป็นงานจริงๆจะหรูเริ่ดกว่านี้เยอะครับ 5555+ ... ก่อนอื่นต้องขอชี้แจงเกี่ยวกับการออกแบบในโปรเจคตัวนี้กันสักเล็กน้อย เพราะเมื่อหลายท่านได้ดาวน์โหลดโค้ดโปรแกรมไป ก็คงจะต้องมีคำถามเกิดขึ้นในใจว่า ทำไมแอดมินถึงได้ออกแบบตารางข้อมูลในลักษณะเยี่ยงนี้???
ข้อมูลครุภัณฑ์ในส่วนงานภาครัฐ โดยเฉพาะสถานศึกษา เขามักจะมี Detail หรือรายละเอียดต่างๆของวัสดุครุภัณฑ์ที่ซ้ำซ้อนกันมาก สิ่งที่แตกต่างกันก็คือ รหัสครุภัณฑ์ เท่านั้นเอง เช่น ชื่อครุภัณฑ์ว่า คอมพิวเตอร์แบบตั้งโต๊ะ ซึ่งจัดวางอยู่ใน ห้องปฏิบัติการหมายเลขนี้ มันมีจำนวนอยู่ 40 ชุดที่เหมือนกันเลย เพราะเวลาจัดซื้อจัดจ้างในแต่ละครั้ง ก็มักจะเหมารวมกันมาเป็นแบบเข่งๆ ทีนี้ในส่วนของผู้พัฒนาโปรแกรมต้องคิดอย่างแรกเลยว่า จะทำอย่างไรให้ผู้ใช้งานเขาสะดวกในการคีย์ข้อมูล??? เอ้า แค่นี้ยังไม่พอ เนื่องจากข้อมูลมันเกิดความซ้ำซ้อน แล้วเราจะทำอย่างไรจึงจะสามารถลดความซ้ำซ้อนนี้ลงไปได้ ... ก็เลยเกิดการเชื่อมโยงความสัมพันธ์ของตารางข้อมูลต่างๆตามภาพที่ปรากฏ และนำมาใช้ในงานนี้ ...
แอดมินจะขอตัดบางส่วนที่คิดว่าเป็นจุดสำคัญๆ มาเน้นย้ำเพื่ออธิบายให้เท่านั้นนะครับ ...
การโหลดค่าจากตารางย่อย (Detail) คือพวกกลุ่มตารางข้อมูลต่างๆที่อยู่รายรอบตารางหลัก tblAsset
จากฟอร์ม 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 ...
- ' =========================================================
- ' โปรแกรมย่อยในการค้นหาคำในรายการของ 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 tblAsset WHERE 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 Function
คัดลอกไปที่คลิปบอร์ด Conclusion: แอดมินก็คาดหวังเล็กๆว่า คงพอที่จะทำให้พี่น้องหลายท่านได้แนวคิด ได้มุมมองแปลกๆใหม่ๆเอาไว้ในอ้อมกอด อ้อมใจ กันบ้างพอสมควรนะครับ ... สวัสดี
ดาวน์โหลดโค้ด VB6 (SP6) ต้นฉบับแบบเต็มๆได้ที่นี่
|
ขออภัย! โพสต์นี้มีไฟล์แนบหรือรูปภาพที่ไม่ได้รับอนุญาตให้คุณเข้าถึง
คุณจำเป็นต้อง ลงชื่อเข้าใช้ เพื่อดาวน์โหลดหรือดูไฟล์แนบนี้ คุณยังไม่มีบัญชีใช่ไหม? ลงทะเบียน
x
|