หากมีคำถาม ขอให้ไปโพสต์ลง เว็บบอร์ดจีทูจีเน็ตดอตคอม ตัวใหม่แทนน่ะครับ

หรือติดต่อเข้ามาทาง Inbox ที่ เฟซบุ๊ค ผมครับ

หน้าหลัก
ข่าวสาร - บทความ ทั้งหมด
VB 6/VB.Net
ASP/ASP.Net
จับฉ่ายคอมพิวเตอร์
เรียนรู้ผ่าน Flash Movie
บทความที่มีผู้ตอบล่าสุด  
 RSS Feeds
 ดาวน์โหลดโปรแกรม RSS Reader ได้ที่นี่ ...   Download โปรแกรม RSS Reader

Forum - www.g2gnet.com
Webmaster - www.g2gnet.com
Visitors - Session views
 5 5 9 1 7 6 7

7 ธันวาคม พ.ศ.2549
128 Users On-Line.
Visitors - Page views
 8 9 4 6 8 1 7
1 กุมภาพันธ์ พ.ศ.2551

Google   
เว็บ g2gnet.com
ขนาดตัวอักษร:  

VB6 กับการใช้งาน Data Control ภาค 5 (ฉบับตราเด็กสมบูรณ์)

Category »  VB 6/VB.Net
โดย : Webmaster เมื่อ 12/11/2553   เวลา: 12:20
(อ่าน : 39132) 
จากภาค 1 มายังภาคจบ (ฉบับตราเด็กสมบูรณ์ 55555+) ... แม้ว่าเราไม่ได้ผูกข้อมูลให้กับ Control ในขณะทำการออกแบบ (Design Time) ก็ตามที แต่ในการเขียนโค้ดลักษณะแบบนี้เราเรียกว่า Bound Control หมายความว่าเราทำ Query ให้กับ ADO Data Control และผูกข้อมูลเข้าไปด้วยโค้ด โดยการกำหนดผ่านทาง RecordSource มันก็จะแสดงผลข้อมูลในตารางกริดออกมาทั้งหมด แบบไม่มีเงื่อนไข (เขียนง่าย) โดยเรียงตามลำดับจากชื่อฟิลด์แรก ไปยังฟิลด์สุดท้าย (สังเกตว่าผมจะตัด ProvinceFK ออกไป ไม่อย่างนั้นมันจะแสดงผลอกมาด้วย) แต่อีกแบบเราเรียกว่า Unbound Control ซึ่งจะไม่มีการผูกข้อมูลเข้ากับ ADO Data Control แต่จะอาศัยคำสั่ง For, Do While หรือ Do Until เพื่อวนรอบการแสดงผลข้อมูลแทน ดังนั้นเราจึงสามารถดึงเฉพาะฟิลด์ที่ต้องการมาแสดงผลได้ (แม้ว่าใน SQL Statement จะมีฟิลด์ที่เราไม่ต้องการติดเข้ามาด้วยก็ตาม) ...
ดาวน์โหลด Source Code สำหรับผู้ใช้งาน Visual Basic 6
ดาวน์โหลด 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 กับการใช้งาน Data Control ภาค 1 ปฐมบท (Design Time ก็ทำงานได้)
VB6 กับการใช้งาน Data Control ภาค 2 (ลูกผสม Design และ Run Time)
VB6 กับการใช้งาน Data Control ภาค 3 (การแก้ไขข้อมูล))
VB6 กับการใช้งาน Data Control ภาค 4 (โค้ด Run Time)
วิดีโอสอนขั้นตอนการ Debug Program ด้วย MS Visual Basic 6.0
วิดีโอสอนขั้นตอนการออกแบบความสัมพันธ์ระหว่างตารางแบบ One To One
หมายเหตุ:
พวก CursorLocation = adUseClient หรือ CommandType = adCmdText เราสามารถกำหนดได้ครั้งเดียวในขณะ Design Time ไปเลยก็ได้ เพราะก็ใช้แบบเดิมๆอยู่แล้วหมดทั้งท้องเรื่องครับ ... แหะๆๆๆ

โค้ดจากโมดูล Module1.bas

Option Explicit

' การประกาศตัวแปรที่สามารถมองเห็นกันได้ทั้งหมดในโปรเจคของ VB6

' #####################################################
' สำหรับการใช้งาน SQL Statement (Query)
Global Statement As String
' #####################################################

' #####################################################
' ค่าตัวแปรตัวนี้จะส่งไปให้กับฟอร์มย่อย เพื่อระบุว่าเป็นการเพิ่ม หรือ แก้ไขข้อมูล
Global blnNewdata As Boolean
' #####################################################

' #####################################################
' เปิดการเชื่อมต่อ (Connect) กับไฟล์ฐานข้อมูล
' โดยมีการส่งชื่อ ADO Data Control เข้ามายังโปรแกรมย่อย
' #####################################################
Public Sub OpenDataBase(AdoControl As Adodc)
    With AdoControl
        .ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; " & _
                    " Data Source=" & App.Path & "\MyDB.MDB; " & _
                    " Persist Security Info=False"
    End With
End Sub

' #####################################################
' ปิดการเชื่อมต่อ (Disconnect) กับไฟล์ฐานข้อมูล
' โดยมีการส่งชื่อ ADO Data Control เข้ามายังโปรแกรมย่อย
' #####################################################
Public Sub CloseDataBase(AdoControl As Adodc)
    
    If AdoControl.Recordset.ActiveConnection.State = adStateOpen Then
        AdoControl.Recordset.ActiveConnection.Close
        Set AdoControl.Recordset.ActiveConnection = Nothing
    End If

End Sub
โค้ดจากฟอร์มหลัก frmMain.frm

Option Explicit

' #####################################################
' Visual Basic ทุกรุ่น จะต้องมาเริ่มต้นที่เหตุการณ์ (หรือโปรแกรมย่อย) Form_Load เสมอ
' #####################################################
Private Sub Form_Load()
    
    ' ตั้งตำหน่งกึ่งกลางจอภาพ ... การใช้ \ หรือ การหารตัดเศษ จะทำงานได้เร็วกว่าการหาร /
    Me.Move (Screen.Width - Me.Width) \ 2, (Screen.Height - Me.Height) \ 2
    
    ' เคลียร์ค่า TextBox ที่ใช้ในการค้นหาข้อมูล
    txtSearch.Text = ""
    
    ' การทำงานซ้ำๆกัน ก็ควรแยกให้เป็นโปรแกรมย่อย จะลดบรรทัดคำสั่งลง เพื่อสะดวกและแก้ไขได้ง่าย
    'Adodc1.ConnectionString = "Provider=Microsoft.Jet.OLEDB.4.0; " & _
                " Data Source=" & App.Path & "\MyDB.MDB; " & _
                " Persist Security Info=False"
    
    ' เริ่มต้นทำการเปิดไฟล์ฐานข้อมูล โดยส่งชื่อ ADO Data Control ไปด้วย
    Call OpenDataBase(Adodc1)
    
    ' เนื่องจากเราต้องใช้งานโค้ดเดิมซ้ำๆ จึงควรนำไปสร้างเป็นโปรแกรมย่อยแทน ...
    ' ทำให้เป็นระบบ จะทำให้อ่านโค้ดได้ง่าย รวมไปถึงเข้าไปแก้ไข ปรับปรุงได้สะดวก
    Call RecordToDataGrid
       
End Sub

' #####################################################
' โปรแกรมย่อยในการแสดงผลข้อมูลลงในตารางกริด
' #####################################################
Sub RecordToDataGrid()

    ' สังเกตด้วยว่า ผมจะไม่เอาฟิลด์ ProvincePK หรือ ProvinceFK มาด้วย เพราะการแสดงผลจะผิดพลาด
    ' หรือหากอยากจะเอามาก็ได้น่ะครับ แต่ต้องซ่อน Column มันเอาไว้ด้วย
    Statement = "SELECT tblCustomer.CustomerPK, tblCustomer.CustomerCode, " & _
                    " tblCustomer.CustomerName, tblCustomer.Address, tblCustomer.Amphur, " & _
                    " tblProvince.ProvinceName, tblCustomer.PostCode " & _
                    " FROM tblCustomer INNER JOIN tblProvince ON " & _
                    " tblCustomer.ProvinceFK = tblProvince.ProvincePK " & _
                    " ORDER BY CustomerPK "
    
    ' Data Control กับกำหนดการเชื่อมต่อไฟล์ฐานข้อมูลแบบ Run Time
    With Adodc1
        
        .CursorLocation = adUseClient
        ' เราสั่งให้อ่าน SQL Statement (Query)
        .CommandType = adCmdText
        ' แสดงผลข้อมูลอย่างเดียว ใช้ LockType แบบ Read Only
        .LockType = adLockReadOnly
        .RecordSource = Statement
        
        ' สั่งให้ประมวลผมตาม RecordSource หรือ SQL Statement ตัวใหม่
        .Refresh
        
    End With
    
    ' เนื่องจากเราผูกฐานข้อมูลเข้าสู่ Adodc1 เรียบร้อยแล้ว ... สามารถนับจำนวน Record ได้เลย
    lblCount.Caption = "[จำนวน : " & Adodc1.Recordset.RecordCount & " รายการ.]"

    ' การ Bound Control ระหว่าง Adodc1 และ DataGrid
    Set DataGrid1.DataSource = Adodc1
    ' จากคำสั่งด้านบนนี้ จะทำให้ DataGrid มี Column ทั้งหมด 7 หลัก (ตามจำนวนฟิลด์)
    
    ' เรียกไปยังโปรแกรมย่อยในการปรับแต่งรูปแบบของ DataGrid
    Call SetupDataGrid

End Sub

' #####################################################
' โปรแกรมย่อยในการเพิ่มข้อมูล - ADD NEW
' #####################################################
Private Sub cmdNew_Click()
    ' แจ้งไปยังฟอร์มย่อย ระบุการเพิ่มข้อมูล คือ True หากเป็นการแก้ไขข้อมูลค่อยกำหนดเป็น False
    blnNewdata = True
    
    ' เปิดฟอร์มของการเพิ่มข้อมูล
    frmCustomer.Show vbModal
    
    ' เมื่อทำงานเสร็จกลับมา ก็สั่งให้ Refresh การแสดงผลข้อมูลใน DataGrid ใหม่
    Adodc1.Refresh
    
    Call SetupDataGrid
    
    lblCount.Caption = "[จำนวน : " & Adodc1.Recordset.RecordCount & " รายการ.]"

End Sub

' #####################################################
' โปรแกรมย่อยในการแก้ไขข้อมูล - EDIT
' #####################################################
Private Sub cmdEdit_Click()
    
    ' ไปอ่านค่าที่ได้ในหลักแรก (หลัก 0) ... เพราะตัวนี้เราเก็บค่า Primary Key เอาไว้
    DataGrid1.Col = 0
    
    'MsgBox DataGrid1.Text
    
    ' แจ้งไปยังฟอร์มย่อย ระบุการแก้ไขข้อมูล คือ False หากเป็นการเพิ่มข้อมูลค่อยกำหนดเป็น True
    blnNewdata = False
    
    ' เปิดฟอร์มของการแก้ไข ไม่ต้องส่งค่าไป แต่จะใช้วิธีการอ้างอิง Control ผ่านฟอร์มเลยก็ได้ครับ
    frmCustomer.Show vbModal
    
    ' เมื่อทำงานเสร็จกลับมา ก็สั่งให้ Refresh การแสดงผลข้อมูลใน DataGrid ใหม่
    Adodc1.Refresh
    
    Call SetupDataGrid

    lblCount.Caption = "[จำนวน : " & Adodc1.Recordset.RecordCount & " รายการ.]"

End Sub

' #####################################################
' โปรแกรมย่อยในการลบข้อมูล - DELETE
' #####################################################
Private Sub cmdDelete_Click()
    
    ' อ่านค่า Customer Primary Key จากหลัก 0 ใน DataGrid
    DataGrid1.Col = 0
    
    If DataGrid1.Text <= 0 Or IsNull(DataGrid1.Text) Then Exit Sub
    
    ' สร้างเงื่อนไข เพื่อทำการยืนยันการลบข้อมูลก่อน
    If MsgBox("คุณแน่ใจว่าต้องการลบข้อมูลลูกค้านี้?", _
                    vbOKCancel + vbQuestion + vbDefaultButton2, "ยืนยันการลบข้อมูล") = vbCancel Then
        Exit Sub
    End If
    
    ' SQL ในการเลือกรายการที่ต้องการลบออกไป
    Statement = "SELECT * FROM tblCustomer WHERE [CustomerPK] = " & DataGrid1.Text
    
    With Adodc1
        .RecordSource = Statement
        .CommandType = adCmdText
        .CursorLocation = adUseClient
        ' ต้องระบุ LockType ด้วย มิฉะนั้นจะไม่สามารถลบออกได้ ... แหง่มๆๆๆๆ
        .LockType = adLockOptimistic
        .Refresh
        ' สั่งลบข้อมูลตามค่า Primary Key
        .Recordset.Delete
        
        .Recordset.Close
    End With
    
    ' เราจะสั่งให้ Adodc1.Refresh ทันทีไม่ได้ เพราะ SQL Statement มันเปลี่ยนไป ... ดังนั้นต้องมาเริ่มใหม่
    ' ไปที่โปรแกรมย่อย RecordToDataGrid
    Call RecordToDataGrid
    
    MsgBox "ลบข้อมูลลูกค้าออกจากระบบเรียบร้อยแล้ว.", vbOKOnly + vbInformation, "รายงานสถานะ"
    
End Sub

' #####################################################
' เกิดเหตุการณ์ (Events) ในการกดปุ่ม Enter จากช่อง TextBox (txtSearch)
' Driven ก็คือ ทำ Query ตามเงื่อนไขที่ใช้ในการค้นหา ... ผลลัพธ์ส่งออกไปที่ DataGrid
' #####################################################
Sub SearchData()
    
    ' หากไม่มีการป้อนข้อมูลในการค้นหา ให้ออกจากโปรแกรมย่อยทันที
    If Trim(txtSearch.Text) = "" Or Len(Trim(txtSearch.Text)) = 0 Then
        
        ' SetFocus คือ ให้ Cursor ไปอยู่ในช่องของ TextBox ที่เราต้องการ
        ' ต้องระวังการใช้ SetFocus ไม่สามารถนำไปใช้ในโปรแกรมย่อยที่เราสร้างขึ้นมาเองได้
        txtSearch.SetFocus
        
        ' หากเงื่อนไขเป็น True ก็ให้ออกจากโปรแกรมย่อย หรือ เหตุการณ์นี้ทันที
        Exit Sub
        
    End If
    
    ' ทำ Query เพื่อทำการค้นหาข้อมูล
    ' สังเกตด้วยว่า ผมจะไม่เอาฟิลด์ ProvincePK หรือ ProvinceFK มาด้วย เพราะการแสดงผลจะผิดพลาด
    ' หรือหากอยากจะเอามาก็ได้น่ะครับ แต่ต้องซ่อน Column มันเอาไว้ด้วย
    Statement = "SELECT tblCustomer.CustomerPK, tblCustomer.CustomerCode, " & _
                    " tblCustomer.CustomerName, tblCustomer.Address, tblCustomer.Amphur, " & _
                    " tblProvince.ProvinceName, tblCustomer.PostCode " & _
                    " FROM tblCustomer INNER JOIN tblProvince ON " & _
                    " tblCustomer.ProvinceFK = tblProvince.ProvincePK " & _
                    " WHERE " & _
                    " [CustomerCode] " & " Like '%" & Trim(txtSearch.Text) & "%'" & " OR " & _
                    " [CustomerName] " & " Like '%" & Trim(txtSearch.Text) & "%'" & " OR " & _
                    " [Address] " & " Like '%" & Trim(txtSearch.Text) & "%'" & " OR " & _
                    " [Amphur] " & " Like '%" & Trim(txtSearch.Text) & "%'" & " OR " & _
                    " [ProvinceName] " & " Like '%" & Trim(txtSearch.Text) & "%'" & _
                    " ORDER BY CustomerPK "
    
    With Adodc1
    
        ' ผูกข้อมูลตาม Query ที่กำหนดให้กับ ADO Data Control ใหม่อีกครั้ง
        ' จำได้ว่าจุดตรงนี้นี่เอง ที่ทำให้ผมรู้ว่าตอน Design Time เราแค่ทดสอบการ Connect ก็พอ
        ' จากนั้นก็ปลด SQL Statement ออกไป แล้วค่อยมาเขียนโค้ดใหม่ตอน Form_Load ก็ได้ อิๆๆๆๆ
        .RecordSource = Statement
        
        ' สั่งทำงานตามคำสั่ง SQL (Query) ... กรณีที่ Design Time ไม่ได้ตั้งค่าเอาไว้ล่วงหน้า
        .CommandType = adCmdText
        
        ' สามารถอ่านจำนวน Record ได้ ... ไปกำหนดตอน Design Time เอาก็ได้ครั้งเดียว
        .CursorLocation = adUseClient
        
        ' อ่านเดินหน้าอย่างเดียว กรณีของการแสดงผล
        .LockType = adLockReadOnly
        
        ' ปรับข้อมูลใหม่ใน Adodc1
        .Refresh
        
    End With
    
    ' กรณีหาข้อมูลพบ ... จะทำให้ Adodc1.Recordset.EOF = False
    ' ก่อนมาถึงตรงนี้ได้ ต้องมีทักษะพื้นฐานเรื่องของตรรกศาสตร์มาก่อนล่ะครับ
    ' แล้วรู้ได้ยังไงล่ะว่ามันมีค่าอะไร ... ต้องลอง Debug ดูครับ
    ' ให้ Cursor ชี้ที่บรรทัดคำสั่งด้านล่าง (ตรง If) แล้วกด Ctrl + F8 ... เมื่อมันทำงานถึงก็จะหยุดรอก่อน
    ' เลื่อนเมาส์เข้าไปหาบริเวณ Adodc1.Recordset.EOF เราก็จะเห็นผลลัพธ์ทันที
    If Not Adodc1.Recordset.EOF Then
    ' หรือเขียนแบบธรรมดาที่มือใหม่จะเข้าใจได้ง่ายๆหน่อย
    'If Adodc1.Recordset.EOF = False Then
        
        ' ปรับการแสดงผลข้อมูลใหม่ ใน DataGrid ตามข้อมูลที่ค้นหาพบ
        DataGrid1.Refresh
        
        ' เรียกไปยังโปรแกรมย่อยในการปรับแต่งรูปแบบของ DataGrid
        Call SetupDataGrid
        
        ' ป้าย Label แสดงผลจำนวนข้อมูลที่ค้นหาพบ
        lblCount.Caption = "[จำนวน : " & Adodc1.Recordset.RecordCount & " รายการ.]"
    
    ' กรณีหาข้อมูลไม่พบ นั่นคือ Adodc1.Recordset.EOF = True
    ' EOF หรือ End Of File ... คือ สิ้นสุดของข้อมูลแล้ว แปลง่ายๆ ก็หาข้อมูลไม่พบสักรายการนั่นแหละครับ
    Else
    
        lblCount.Caption = "[จำนวน : 0 รายการ.]"
        
        ' ต้องเคลียร์การแสดงผลใหม่ด้วย
        Call SetupDataGrid
        
        ' แจ้งให้ผู้ใช้งานทราบด้วยก็ได้ว่าหาข้อมูลไม่เจอ
        MsgBox "ขออภัย ... ไม่พบข้อมูลที่ต้องการ.", vbOKOnly + vbInformation, "รายงานสถานะ"
    
    End If
    
    ' เคลียร์ค่าการค้นหา
    txtSearch.Text = ""
    
End Sub

' #####################################################
' โปรแกรมย่อยที่สร้างเอง (Sub Program) ในการปรับแต่ง DataGrid แบบ Run Time
' สำหรับมือใหม่ๆ ควรจะฝึกการเขียนเป็นโปรแกรมย่อย ในลักษณะแบบนี้เอาไว้ด้วยน่ะครับ
' เพราะเราจะแยกแต่ละส่วนออกจากกัน เพื่อจะได้เข้าใจ หรือ แก้ไขได้ง่ายๆต่อไป
' #####################################################
Sub SetupDataGrid()

    With DataGrid1
    
        ' #####################################################
        ' สูตรสำเร็จเลยครับ ... เรื่องของการซ่อน Primary Key เอาไว้ เพื่อนำไปใช้ประโยชน์ต่อไป
        ' ซ่อน Primary Key ไว้ในหลักแรกของตารางกริด (Index = 0) เอาไว้
        .Columns(0).Width = 0
        ' ไม่ให้ปรับระยะของหลักแรก (Index = 0) ให้ขยายออกมาได้
        .Columns(0).AllowSizing = False
        ' #####################################################
        
        ' #####################################################
        ' เริ่มการปรับแต่งค่า Column ของ DataGrid แบบ Run Time
        ' จะมีจำนวนทั้งสิ้น 7 หลัก แต่จะใช้ค่า Index จาก 0 - 6 (คอมพิวเตอร์ จะเริ่มนับจาก 0)
        ' ในงานจริงๆของแต่ละคน ก็ต้องดูด้วยว่าเรียกฟิลด์ไหนมาแสดงผลข้อมูลด้วยน่ะครับ
        With .Columns(1)
            .Caption = "รหัสลูกค้า"
            .Width = DataGrid1.Width \ 6
        End With
        
        With .Columns(2)
            .Caption = "ชื่อลูกค้า"
            .Width = DataGrid1.Width \ 6
        End With
        
        With .Columns(3)
            .Caption = "ที่อยู่ลูกค้า"
            .Width = DataGrid1.Width \ 6
        End With
        
        With .Columns(4)
            .Caption = "อำเภอ"
            .Width = DataGrid1.Width \ 6
        End With
        
        With .Columns(5)
            .Caption = "จังหวัด"
            .Width = DataGrid1.Width \ 6
            ' ทดสอบปรับการแสดงผลให้อยู่ชิดขวา
            .Alignment = dbgRight
        End With
        
        With .Columns(6)
            .Caption = "รหัสไปรษณีย์"
            .Width = .Width + 170
        End With
        
    End With

End Sub

' #####################################################
' ก่อนจบการทำงานของโปรแกรม ต้องปิดการเชื่อมต่อไฟล์ฐานข้อมูลด้วย
' #####################################################
Private Sub Form_Unload(Cancel As Integer)

    'If Adodc1.Recordset.ActiveConnection.State = adStateOpen Then
    '    Adodc1.Recordset.ActiveConnection.Close
    '    Set Adodc1.Recordset.ActiveConnection = Nothing
    'End If
    
    ' การทำงานซ้ำๆกัน ก็ควรแยกให้เป็นโปรแกรมย่อย จะลดบรรทัดคำสั่งลง เพื่อสะดวกและแก้ไขได้ง่าย
    ' ทำการปิดไฟล์ฐานข้อมูล โดยส่งชื่อ ADO Data Control ไปด้วย
    Call CloseDataBase(Adodc1)
    
    ' การคืนค่าหน่วยความจำกลับคืนให้กับระบบปฏิบัติการ - Operating System
    ' โดยการใช้ชื่อฟอร์มตัวมันเอง ... การทำแบบนี้ก็เพื่อให้แน่ใจเท่านั้นเองแหละครับ
    Set frmMain = Nothing
    
End Sub
โค้ดจากฟอร์มรายการลูกค้า frmCustomer.frm

Option Explicit

' #####################################################
' ตัวแปรแบบ Local เพื่อทำการรับค่าตัวแปร Global ที่ส่งมาจากฟอร์มหลัก (frmMain)
' เหตุที่ต้องใช้ตัวแปร Local มารับค่า ก็เพราะเราอาจจะต้องเปิดหลายๆฟอร์มพร้อมๆกัน
' จึงต้องใช้ตัวแปร blnNewData ซ้ำๆกัน ดังนั้นจึงต้องเพื่อป้องกันความผิดพลาดเอาไว้ก่อน
Dim NewData As Boolean

' ตัวแปรเพื่อรับค่า CustomerPK เข้ามาจากฟอร์มหลัก (DataGrid)
' และยังต้องใช้ในการคำนวณหาค่า Primary Key ตัวใหม่ด้วย
Dim CustomerPrimaryKey As Long

' ตัวแปรรับค่าชื่อจังหวัดจากการทำ Query ครั้งแรกเอาไว้ แล้วนำไปเปรียบเทียบค่ากับ DataCombo
Dim ProvinceName As String

' #####################################################
' เริ่มต้นการเลือกเงื่อนไขว่าเป็นการเพิ่มข้อมูล หรือ แก้ไขข้อมูล
' #####################################################
Private Sub Form_Load()
    
    ' #####################################################
    ' การทำงานซ้ำๆกัน ก็ควรแยกให้เป็นโปรแกรมย่อย จะลดบรรทัดคำสั่งลง เพื่อสะดวกและแก้ไขได้ง่าย
    ' ทำการเปิดไฟล์ฐานข้อมูล โดยส่งชื่อ ADO Data Control ไปด้วย (Module1.bas)
    Call OpenDataBase(Adodc1)
    ' #####################################################

    ' ให้ตัวแปรแบบ Local มารับค่าตัวแปรแบบ Global นั่นหมายความว่า ...
    ' ยกเลิกการใช้งานตัวแปร Global ไปเลย เพื่อป้องกันความผิดพลาด หากนำมาใช้ซ้ำ พร้อมๆกันหลายฟอร์ม
    NewData = blnNewdata
    
    ' #####################################################
    ' การเขียนแยกเป็นโปรแกรมย่อย จะทำให้เราประหยัดโค้ด และ อ่านโค้ดเข้าใจได้ง่ายขึ้น
    ' กรณีของการเพิ่มข้อมูล NewData = True
    If NewData Then
        
        ' เคลียร์หน้าจอ
        Call SetupScreen
        
    ' กรณีของการแก้ไขข้อมูล NewData = False
    Else
    
        ' ไปโปรแกรมย่อยในการนำข้อมูลเดิมมาแสดงผล
        Call RecordToScreen
        
    End If
    
    ' แยกออกไปเป็นโปรแกรมย่อย เพราะต้องใช้งานซ้ำถึง 2 ครั้ง ทั้งการเพิ่ม และ แก้ไขข้อมูล
    ' โหลดรายชื่อจังหวัดเข้ามาสู่ DataCombo
    Call GetProvinceName
    
End Sub

' #####################################################
' โปรแกรมย่อยเคลียร์หน้าจอของการแสดงผลใหม่ทั้งหมด ... สำหรับการเพิ่มข้อมูล
' #####################################################
Sub SetupScreen()
    txtCustomerCode.Text = ""
    txtCustomerName.Text = ""
    txtAddress.Text = ""
    txtAmphur.Text = ""
    txtPostCode.Text = ""
End Sub

' #####################################################
' โปรแกรมย่อยเพื่อทำการแสดงผล โดยนำข้อมูลที่ได้จาก CustomerPK มาเป็นเงื่อนไขทดสอบ
' #####################################################
Sub RecordToScreen()
    ' รับค่า CustomerPK ที่เราซ่อนเอาไว้ จากหลักแรก (Index = 0) มาจากฟอร์มหลัก
    ' ก่อนจะมาถึงฟอร์มนี้ เรากำหนดให้อ่านหลัก 0 ของ DataGrid มาก่อนแล้วล่วงหน้า
    ' หากเอาแบบให้แน่ใจก็กำหนดใหม่ได้ โดยการอ้างถึงฟอร์มหลักโดยตรง
    frmMain.DataGrid1.Col = 0
    CustomerPrimaryKey = frmMain.DataGrid1.Text
    
    ' ส่วนนี้จริงๆแล้ว ต้องแยกออกเป็นโปรแกรมย่อย เพราะต้องมีการทดสอบก่อนว่าเป็นการเพิ่ม หรือ แก้ไขข้อมูล
    ' หากเป็นการเพิ่มข้อมูลใหม่ ก็จะเคลียร์ค่า Control ต่างๆเพื่อเริ่มต้นการป้อนข้อมูลใหม่
    ' หากเป็นการแก้ไข ก็จะต้องทำการแสดงผลข้อมูลเดิมขึ้นมา ... นี่แหละคือการออกแบบเป็นขั้นเป็นตอน
    ' ทำ Query เพื่อทำการค้นหาข้อมูล
    Statement = "SELECT tblCustomer.CustomerPK, tblCustomer.CustomerCode, " & _
                    " tblCustomer.CustomerName, tblCustomer.Address, tblCustomer.Amphur, " & _
                    " tblCustomer.ProvinceFK, tblProvince.ProvinceName, tblCustomer.PostCode " & _
                    " FROM tblCustomer INNER JOIN tblProvince ON " & _
                    " tblCustomer.ProvinceFK = tblProvince.ProvincePK " & _
                    " WHERE " & _
                    " [CustomerPK] = " & CustomerPrimaryKey & _
                    " ORDER BY CustomerPK "
    With Adodc1
        ' ผูกข้อมูลตาม Query ที่กำหนดให้กับ ADO Data Control ใหม่อีกครั้ง
        .RecordSource = Statement
        
        ' สั่งทำงานตามคำสั่ง SQL (Query) ... กรณีที่ Design Time ไม่ได้ตั้งค่าเอาไว้ล่วงหน้า
        .CommandType = adCmdText
        
        ' สามารถอ่านจำนวน Record ได้ ... ไปกำหนดตอน Design Time เอาก็ได้ครั้งเดียว
        .CursorLocation = adUseClient
        
        ' อ่านเดินหน้าอย่างเดียว กรณีของการแสดงผล
        .LockType = adLockReadOnly
        
        ' ปรับข้อมูลใหม่ใน Adodc1
        .Refresh
        
        ' นำเอาข้อมูลจากการ Query เพื่อทำการแสดงผลข้อมูล
        txtCustomerCode.Text = "" & Adodc1.Recordset("CustomerCode")
        
        ' #####################################################
        ' เทคนิคลึกล้ำ ... พิศดาร ... 55555+
        ' เป็นการเก็บค่ารหัสลูกค้าเอาไว้ที่คุณสมบัติ Tag ของ TextBox เพื่อไม่ต้องประกาศตัวแปรใหม่
        ' จะใช้ทดสอบตอนที่ทำการบันทึกผล ตรวจสอบว่ารหัสลูกค้ามันไปซ้ำกับของเดิมหรือไม่
        txtCustomerCode.Tag = txtCustomerCode.Text
        ' หรือ txtCustomerCode.Tag = "" & Adodc1.Recordset("CustomerCode")
        ' #####################################################
        
        txtCustomerName.Text = Adodc1.Recordset("CustomerName")
        txtAddress.Text = Adodc1.Recordset("Address")
        txtAmphur.Text = Adodc1.Recordset("Amphur")
        txtPostCode.Text = Adodc1.Recordset("PostCode")
        
    End With
    
    ' ประกาศตัวแปรเพื่อรับค่ารายชื่อจังหวัดเอาไว้ก่อน แล้วค่อยนำไปเทียบรายการใน DataCombo
    ProvinceName = Adodc1.Recordset("ProvinceName")
    
    Adodc1.Recordset.Close
    
End Sub

Sub GetProvinceName()
    ' #####################################################
    ' หากในฟอร์มมี ComboBox นับ 10 ตัวล่ะ จะมี ADO Data Control ทั้ง 10 ตัวเลยเหรอ
    ' ส่วนนี้คือการนำ Adodc1 กลับมาใช้งานอีกรอบ แต่เปลี่ยน RecordeSource ใหม่
    ' เพื่อ Query หารายชื่อจังหวัด นำมาแสดงผลลงใน DataCombo เสียก่อน
    ' #####################################################
    Statement = "SELECT * FROM tblProvince ORDER BY ProvinceName "
    With Adodc1
        .CursorLocation = adUseClient
        .CommandType = adCmdText
        .RecordSource = Statement
        .Refresh
    End With
    
    Set DataCombo1.RowSource = Adodc1
    DataCombo1.ListField = "ProvinceName"
    
    ' หากเป็นการแก้ไขข้อมูล ... จะต้องนำข้อมูลเดิมมาแสดงผล
    ' นำค่า ProvinceName จากการ Query เดิมมาเทียบใส่กับ DataCombo
    If Not NewData Then DataCombo1.Text = ProvinceName

End Sub

' #####################################################
' เกิดเหตุการณ์กดปุ่ม (Event) ... สั่งทำ Driven คือ
' - ตรวจสอบการป้อนข้อมูล
' - บันทึกผลเข้าสู่ตารางข้อมูล
' - กลับไปรายการหลัก
' #####################################################
Private Sub cmdSave_Click()

    ' การตรวจสอบค่าว่างในฟิลด์ที่สำคัญๆ และจำเป็น เช่น รหัสลูกค้า ชื่อลูกค้า
    If Trim(txtCustomerCode.Text) = "" Then
        MsgBox "กรุณาป้อนรหัสลูกค้าให้เรียบร้อยก่อนด้วย.", vbOKOnly + vbExclamation, "รายงานสถานะ"
        txtCustomerCode.SetFocus
        Exit Sub
    ElseIf Trim(txtCustomerName.Text) = "" Then
        MsgBox "กรุณาป้อนชื่อลูกค้าให้เรียบร้อยก่อนด้วย.", vbOKOnly + vbExclamation, "รายงานสถานะ"
        txtCustomerName.SetFocus
        Exit Sub
    
    End If
    
    ' #####################################################
    ' ตรวจสอบรหัสลูกค้าก่อนว่าซ้ำกันกับของเดิมหรือไม่
    ' หากมีการเปลี่ยนแปลงค่าข้อมูลของ txtCustomerCode
    ' เราพิจารณาได้จาก 2 เหตุการณ์ คือ
    ' 1. เพิ่มข้อมูลใหม่
    '     - กรณีนี้ค่าใน Tag และใน Text จะไม่ตรงกัน จึงต้องไปเช็คว่าไปซ้ำกับของเดิมที่มีอยู่หรือไม่
    ' 2. แก้ไขข้อมูล ประกอบด้วย
    '     - หากมีการแก้ไขรหัสลูกค้า จะต้องไปเช็คว่าไปซ้ำกับของตัวอื่นที่มีอยู่เดิมหรือไม่ (ตัวอื่นน่ะครับ ไม่ใช่ตัวมันเอง)
    '     - หากไม่มีการแก้ไขรหัสลูกค้า ก็จะข้ามไปยังการบันทึกข้อมูลเลยทันที
    '
    ' หากเงื่อนไขเป็นจริง นั่นคือไม่ได้เปลี่ยนแปลงค่ารหัสลูกค้าเดิม ให้ข้าม IF ออกไปทำงานที่ SaveData เลย
    If txtCustomerCode.Text <> txtCustomerCode.Tag Then
        ' หากฟังค์ชั่น CheckExistCode มันส่งค่ากลับมามากกว่า 0 แสดงว่ารหัสลูกค้าซ้ำ ให้ออกไปเลย
        If CheckExistCode > 0 Then
            MsgBox "มีรหัสลูกค้า: " & Trim(txtCustomerCode.Text) & " เรียบร้อยแล้ว กรุณาแก้ไขใหม่ด้วย.", _
                            vbOKOnly + vbExclamation, "รายงานสถานะ"
            Exit Sub
        End If
    End If
    ' #####################################################
    
    
    ' #####################################################
    ' สั่งบันทึกข้อมูลที่โปรแกรมย่อย
    Call SaveData
    ' #####################################################
    
End Sub

' #####################################################
' ฟังค์ชั่นที่ใช้ในการตรวจสอบรหัสลูกค้าว่าซ้ำกันหรือไม่
' ไม่มีการรับค่าเข้ามา แต่จะมีการส่งค่าคืนกลับไป ... ดังนั้นเราเลยจึงต้องใช้ฟังค์ชั่น
' #####################################################
Function CheckExistCode() As Long
    
    Statement = "SELECT * FROM tblCustomer " & _
                    " WHERE CustomerCode = " & "'" & Trim(txtCustomerCode.Text) & "'" & _
                    " ORDER BY CustomerPK "
    
    With Adodc1
        .RecordSource = Statement
        .CommandType = adCmdText
        .CursorLocation = adUseClient
        .LockType = adLockReadOnly
        .Refresh
        
        ' หากมีค่าซ้ำกัน แสดงว่า RecordCount ต้องมีค่ามากกว่า 0
        CheckExistCode = .Recordset.RecordCount
        
        .Recordset.Close
        
    End With
    
End Function

' #####################################################
' โปรแกรมย่อยที่ทำการบันทึกข้อมูล - SAVE
' #####################################################
Sub SaveData()
    
    ' เนื่องเราใช้ ADO Data Control เพียงแค่ตัวเดียว ดังนั้นจะต้องไปหาค่า Primary Key
    ' ของรายชื่อจังหวัดที่เลือกเข้ามาก่อน ... นั่นคือการจัดเรียงลำดับเหตุการณ์เพื่อไม่ให้เกิดข้อผิดพลาด
    Dim ProvincePrimaryKey As Integer
    ProvincePrimaryKey = CompareProvinceName
    
    ' #####################################################
    ' ลักษณะของการเขียนแบบนี้ทำให้เราประหยัดบรรทัดคำสั่งลงได้มากมาย
    ' ทดสอบก่อนว่าเป็นการเพิ่มข้อมูลเข้ามาใหม่ หรือ แก้ไขข้อมูล
    ' NewData = True คือการเพิ่มข้อมูล ดังนั้นต้องหาค่า CustomerPK ใหม่เสียก่อน
    If NewData Then
         
        ' นำข้อมูลจากตารางมาคำนวณหาจำนวนทั้งหมดแล้วบวก 1 ... เพื่อหาค่า Primary Key ตัวใหม่
        ' จริงๆควรแยกไปทำเป็นโปรแกรมย่อย น่าจะดีกว่า
        Statement = "SELECT * FROM tblCustomer ORDER BY CustomerPK "
        With Adodc1
            .RecordSource = Statement
            .CursorType = adOpenKeyset
            .LockType = adLockReadOnly
            .Refresh
            
            ' โน่นไปรายการสุดท้ายเลยครับ เพราะจะแน่นอนกว่าการนับด้วย Method RecordCount
            .Recordset.MoveLast
            ' ไชโย ... ได้ค่า CustomerPK ใหม่ที่ไม่ซ้ำกับใครแล้ว ... เย้ๆๆๆๆๆ
            CustomerPrimaryKey = .Recordset.Fields("CustomerPK") + 1
            .Recordset.Close
            
        End With
        
        ' ให้สังเกตความแตกต่างของ SQL Statement ระหว่างการเพิ่ม และ การแก้ไข
        Statement = "SELECT * FROM tblCustomer " & _
                        " ORDER BY CustomerPK "
        With Adodc1
            .RecordSource = Statement
            .CursorType = adOpenKeyset
            .LockType = adLockOptimistic
            .Refresh
            
            ' กำหนด Method AddNew เพื่อทำการเพิ่มข้อมูล ไม่ใช่แก้ไขข้อมูล
            .Recordset.AddNew
            
            ' ส่วนของ Primary Key จะ Update เฉพาะการเพิ่มข้อมูลเท่านั้น การแก้ไขเราจะไม่เปลี่ยนค่า
            .Recordset.Fields("CustomerPK") = CustomerPrimaryKey
        
        End With
        
    ' NewData = False คือ การแก้ไขข้อมูล เราเก็บค่า CustomerPK เอาไว้แล้ว ก็ Query ค่าเดิมมา
    Else
        
        Statement = "SELECT * FROM tblCustomer " & _
                        " WHERE " & _
                        " [CustomerPK] = " & CustomerPrimaryKey & _
                        " ORDER BY CustomerPK "
        With Adodc1
            .RecordSource = Statement
            .CursorType = adOpenKeyset
            .LockType = adLockOptimistic
            .Refresh
        End With
    
    End If
    
    ' ไม่ว่าจะเป็นการเพิ่ม หรือ แก้ไข ... ส่วนนี้มันจะต้องมาทำงานเช่นเดียวกัน
    With Adodc1
        
        ' บันทึกผลข้อมูลใหม่อีกครั้ง
        .Recordset.Fields("CustomerCode") = "" & txtCustomerCode.Text
        .Recordset.Fields("CustomerName") = "" & txtCustomerName.Text
        .Recordset.Fields("Address") = "" & txtAddress.Text
        .Recordset.Fields("Amphur") = "" & txtAmphur.Text
    
        .Recordset.Fields("ProvinceFK") = ProvincePrimaryKey
    
        .Recordset.Fields("PostCode") = "" & txtPostCode.Text
    
        ' ปรับปรุงข้อมูล
        .Recordset.Update
        
        ' ปิดการเชื่อมต่อกับตารางข้อมูล
        .Recordset.Close
    
    End With
    ' #####################################################
    
    MsgBox "บันทึกข้อมูลเรียบร้อย.", vbOKOnly + vbInformation, "รายงานสถานะ"
    
    ' ปิดฟอร์มกลับไปที่โปรแกรมรายการหลัก
    Unload Me
    
End Sub

' #####################################################
' เนื่องจากต้องมีการส่งค่า Primary Key ของจังหวัดนั้นๆ กลับคืนไปเราจึงต้องใช้ฟังค์ชั่นแทน
' แต่ไม่มีการรับค่าเข้ามา นำค่า Text ที่อยู่ใน DataCombo มาทดสอบหาค่า Primary Key
' #####################################################
Function CompareProvinceName() As Integer
    
    Statement = "SELECT tblProvince.ProvincePK, tblProvince.ProvinceName " & _
                    " FROM tblProvince " & _
                    " WHERE ProvinceName = " & "'" & DataCombo1.Text & "'"
                            
    With Adodc1
        .RecordSource = Statement
        .CommandType = adCmdText
        .CursorLocation = adUseClient
        .LockType = adLockReadOnly
        .Refresh
        
        ' หากค้นหาข้อมูลพบ RecordSet.EOF จะเป็น True
        If Not .Recordset.EOF Then
        
            ' ส่งค่าคืนกลับ (แบบเลขจำนวนเต็ม) โดยใช้ชื่อฟังค์ชั่นของตัวมันเอง
            CompareProvinceName = Adodc1.Recordset("ProvincePK")
        
        ' ค้นหาไม่เจอ แสดงว่าผู้ใช้งานไม่ได้เลือกรายการใน DataCombo ต้องกำหนดค่า 0 กลับไปแทน
        ' เห็นประโยชน์ของค่า Primary Key ที่เป็น 0 มั้ยครับ ... อิอิอิอิอิ
        Else
        
            CompareProvinceName = 0
            
        End If
        
        .Recordset.Close
    
    End With
    
End Function

' #####################################################
' ก่อนปิดหน้าจอ ต้องตัดการการเชื่อมต่อไฟล์ฐานข้อมูลด้วย
' สังเกตด้วยว่า Adodc1 ตัวนี้ มันไม่เกี่ยวข้องกับ Adodc1 ที่อยู่ในฟอร์มหลัก
' ดังนั้นคุณปิดการเชื่อมต่อ Adodc1 ที่นี่ จึงไม่มีผลต่อ Adodc1 ที่อยู่ในฟอร์มหลัก ...
' #####################################################
Private Sub Form_Unload(Cancel As Integer)
    
    'If Adodc1.Recordset.ActiveConnection.State = adStateOpen Then
    '    Adodc1.Recordset.ActiveConnection.Close
    '    Set Adodc1.Recordset.ActiveConnection = Nothing
    'End If
    
    ' การทำงานซ้ำๆกัน ก็ควรแยกให้เป็นโปรแกรมย่อย จะลดบรรทัดคำสั่งลง เพื่อสะดวกและแก้ไขได้ง่าย
    ' ทำการปิดไฟล์ฐานข้อมูล โดยส่งชื่อ ADO Data Control ไปด้วย
    Call CloseDataBase(Adodc1)

    ' การคืนค่าหน่วยความจำกลับคืนให้กับระบบปฏิบัติการ - Operating System
    ' โดยการใช้ชื่อฟอร์มตัวมันเอง ... การทำแบบนี้ก็เพื่อให้แน่ใจเท่านั้นเองแหละครับ
    Set frmCustomer = Nothing
    
End Sub
Conclusion:
เทคนิคในการเขียนโปรแกรม Visual Basic กับฐานข้อมูล อยู่ที่การเรียงลำดับเหตุการณ์ที่เกิดขึ้น (มันก็โปรแกรมย่อยนั่นแหละครับ) จากไหน ไปไหน ไปแล้วให้มันทำอะไร หากสิ่งที่ให้ทำ (Driven) มันซ้ำซ้อนกัน ก็ควรแยกเขียนออกมาเป็นโปรแกรมย่อย (อีกแล้ว) แต่บางอย่างสิ่งที่ทำนั้นมันมีโค้ดยาวเฟื้อย หรือเข้าใจยาก ก็ควรทำเป็นโปรแกรมย่อยเหมือนกัน เพื่อให้สะดวกต่อการปรับปรุง แก้ไขในภายหลังได้ง่าย อีกทั้งยังเป็นการเขียนโค้ดอย่างเป็นระบบ ระเบียบ (รัด) ... โค้ดตัวอย่าง (ใช้งานได้จริง) นี้ จะนำทางให้พี่น้องเลื่อนระดับไปเขียนโค้ดแบบ Run Time โดยใช้ Object แทน Control

จี ทู จี เน็ต ดอต คอม - g2gNet Dot Com
เลขทะเบียนพาณิชย์อิเล็กทรอนิกส์ 0407314800231
CopyLeft © 2004 - 2099 g2gNet.Com All rights reserved.
Email: [email protected] หรือ โทร. 08-6862-6560