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

หรือติดต่อเข้ามาทาง 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
 4 7 6 3 6 0 1

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

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

แจกฟรีโค้ดโปรแกรม VB6 กับการพิมพ์ซองจดหมายด้วย Active Report

Category »  VB 6/VB.Net
โดย : Webmaster เมื่อ 19/9/2553   เวลา: 16:34
(อ่าน : 25872) 
ผมเคยได้กล่าวไปแล้ว เรื่องของการทำรายงาน หรือ ทำพวก Hard Copy ออกมา เพราะมันจะเป็นตัวชี้วัดว่าตารางข้อมูลที่ออกแบบมานั้น มันมีความถูกต้อง น่าเชื่อถือได้แค่ไหน ... บทความเรื่องนี้เป็นแค่การออกแบบลักษณะความสัมพันธ์แบบ 1 : 1 หรือ เรียกได้ว่าเป็นแค่ระดับพื้นฐานอนุบาลแมวเหมียวเท่านั้นเอง ... แต่หลายคนกลับมองว่ามันยากเกินไป สู้ทำแบบตารางเดี่ยวๆไม่ได้ นู้นล่ะ ... สุดท้ายก็จะพบทางตัน หรือ เจอปัญหานั่นแหละ ถึงต้องวนกลับมาออกแบบใหม่ ... ซึ่งเสียทั้งเวลา เสียทั้งเงิน ... ผมจะไม่กล่าวย้ำถึงเรื่องขีดความสามารถของเจ้า Active Reports หรอก เพราะมันสุดเจ๋งมากๆอยู่แล้ว 55555+ แต่ผมจะนำการออกแบบตารางข้อมูลสัก 2 ตัว ที่เห็นกันบ่อยๆ มาเปรียบเทียบให้ดูกันครับ ซึ่งพอเราจะนำไปออกงานพิมพ์ต่างๆ ผลที่ได้มันจะแตกต่างกันอย่างไรบ้าง (ทั้งในวันนี้ และ ในวันหน้า ที่อาจจะต้องมีการขยายความต้องการออกไปอีก)

ตัวอย่างเปรียบเทียบของการออกแบบตารางข้อมูล


ลักษณะนี้เป็นการจัดเก็บข้อมูลที่อยู่ของลูกค้าเอาไว้ในฟิลด์เดียว หากเราต้องการแยกข้อมูลลูกค้า ออกตามรายชื่อจังหวัดย่อมทำได้ยากมาก


ลักษณะนี้เป็นการจัดเก็บข้อมูลรายชื่อจังหวัดแยกออกมาต่างหาก แต่ในตัวโปรแกรมคงต้องให้ผู้ใช้ (Users) ป้อนข้อมูลเข้าสู่ TextBox เอาเอง โอกาสที่จะเกิดความผิดพลาดในการป้อนข้อมูลย่อมมีโอกาสสูง (Human Error) แล้วเวลาสั่งออกพิมพ์รายงาน (หรือจ่าหน้าซองจดหมาย) ก็จะเกิดปัญหากับชื่อ กทมฯ กรุงเทพ กรุงเทพมหานคร หรืออื่นๆ ซึ่งจะต้องใช้คำว่าเขตแทนอำเภอ และ ไม่มีคำว่าจังหวัดขึ้นนำหน้า (บางคนแก้ปัญหาโดยใช้ IF เพียบแหละครับ ... แถมบางคนก็ยังด่า User เอาครับ ไหงคุณโง่อย่างนี้ ก็คีย์แค่ กทม พอซิ 55555+)

การออกแบบด้วย 2 วิธีการข้างบน นอกจากจะไม่เป็นมิตรต่อผู้ใช้งานแล้ว (Friendly Use) เพราะต้องให้ผู้ใช้คีย์ข้อมูลเอาเอง ความผิดพลาดในการป้อนข้อมูลมันก็ย่อมมี นอกจากนั้นขนาดของไฟล์จะมีขนาดโตเพิ่มขึ้นเรื่อยๆ เพราะมันเกิดความซ้ำซ้อนของข้อมูล ยิ่งหากเป็นโปรเจคใหญ่ๆล่ะก็อ่วมอรทัยแน่ๆ (เหมือนที่หลายๆคนเจอมาแล้ว) แต่สิ่งที่สำคัญมากที่สุด คือ ความถูกต้องน่าเชื่อถือของข้อมูล ... ลองมาเปรียบเทียบกันกับการออกแบบของผม และ โปรดใช้วิจารญาณไตร่ตรองกันเอาเองน่ะครับผม ... หุๆๆๆๆ

การแยกย่อยตารางและสร้างความสัมพันธ์ขึ้นมาใหม่ (จากภาพผมพิมพ์ผิดต้องเป็น Foreign Key น่ะครับ)


การทำ Query เพื่อแสดงผลของความสัมพันธ์ระหว่าง 2 ตาราง (ดู Query ในไฟล์ MS Access ตัวอย่างเลยครับ)
ดาวน์โหลด 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
ข้อมูลเพิ่มเติม
ดาวน์โหลด และ แนะนำวิธีการติดตั้ง Active Report Professional 2.0
Visual Basic 6.0 กับการทำรายงานด้วย Active Report ตอนที่ 1
Visual Basic 6.0 กับการทำรายงานด้วย Active Report ตอนที่ 2
การพิมพ์บาร์โค้ดด้วย Active Report 2.0 ออกกระดาษ A4
การทำกราฟแสดงผลด้วย MSChart และ ออกรายงานได้ใน Active Report
การแทรกปุ่มคำสั่ง ToolBar ใน ARViewer ของ Active Report 2.0

เลือก ActiveReports Viewer Component มาใช้งาน เพื่อทำการ Preview ผลลัพธ์ก่อนพิมพ์


จากนั้นให้คลิ๊กเลือก ARViewer จาก ToolBox มาวาดลงบนฟอร์ม (การจัดระยะค่อยปรับที่ Form_Size ในแบบ Run Time)


การเพิ่ม Active Report Designer เข้ามาใหม่


การออกแบบการพิมพ์ซองจดหมายใน Active Report Designer (ซองยาวมาตรฐานขนาด 23 x 10.8 ซม.)


เมื่อสั่งโปรแกรมทำงาน โดยการกดปุ่ม ดูก่อนพิมพ์ (Preview) หรือ กดปุ่มฟังค์ชั่น F7

โมดูลหากินจริงๆเลย ... modDataBase.Bas เพื่อเก็บโค้ดการเชื่อมต่อไฟล์ข้อมูล

Option Explicit

' #####################################################
' ประหยัดตัวแปรในการใช้งานครับพี่น้อง ... ไม่จำเป็นต้องไปประกาศให้พร่ำเพรื่อ
' #####################################################
Global ConnDB As New ADODB.Connection
Global RS As New ADODB.Recordset
Global DS As New ADODB.Recordset
Global Statement As String
Global SQLStmt As String

' พวกนี้ไม่ได้ใช้งานในบทความนี้หรอกน่ะครับ ... เผื่อๆเอาไว้ให้ดูเท่านั้นเอง
' กำหนดว่าเป็นการเพิ่ม หรือ แก้ไขข้อมูล
Global blnNewData As Boolean
' ให้เกิดการ Update ในฟอร์มที่มีการเปลี่ยนแปลง
Global FormUpdate As Boolean

' #####################################################
' MODULE ทำมาหากินเลยครับ ... ไว้ใช้เชื่อมต่อไฟล์ฐานข้อมูล MS Access
' Save เป็น Bas เอาไว้ เวลานำไปใช้ในโปรเจคอื่นๆ ก็แค่ Add Module เข้าไป
' แล้วเปลี่ยนชื่อไฟล์ DB ใหม่ ... ก็เท่านั้นเอง ... ไม่ได้ตัดแปะอย่างที่เขาว่ากันสักกะหน่อย อิอิอิอิอิ
' #####################################################
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 & "MyDB.MDB"
    ' เชื่อมต่อไฟล์ข้อมูล - Open Connection
    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

' #####################################################
' ปิดการเชื่อมต่อกับไฟล์ฐานข้อมูล ...
' การสั่งทดสอบ ConnDB.State = adStateOpen ว่า Connect อยู่หรือไม่
' ก็แค่ทดสอบว่า หากมันยังเปิด Connect อยู่ก็สั่งปิดซ่ะ ...
' หากไม่ใช่ ก็ไม่ต้องมาเสียเวลาในการทำงาน ให้จบโปรแกรมออกไปเลย ... ก็เท่านั้นเอง ...
' จะเขียน ConnDB.Close: Set ConnDB = Nothing แค่นี้ก็พอ ... หากมันปิดอยู่ ก็ไม่มีผลอะไรเลยครับ
' เพราะการ Connect ของผม จะ Connect ครั้งเดียว และ Disconnect ครั้งเดียวด้วย
' #####################################################
Public Sub CloseDataBase()
    If ConnDB.State = adStateOpen Then
        ConnDB.Close
        Set ConnDB = Nothing
    End If
End Sub

ส่วนนี้อยู่ในฟอร์มของการ Print Preview (frmAR2PrintEnvelope.frm)

Option Explicit

' ประกาศตัวแปรแบบ Object เพื่อรับงานไปพิมพ์
Dim rptPrint As Object
' ตัวแปรที่ใช้ในระบุการค้นหา หรือ กำหนดช่วงการพิมพ์หรือไม่
Dim blnSearch  As Boolean

' #####################################################
' ทำการ Query ข้อมูลตามที่ต้องการ ก่อนที่จะส่งข้อมูลไปให้กับ AR Designer
' #####################################################
Private Sub cmdPreview_Click()

    ' สร้าง Instance ขึ้นมาใหม่ พร้อมกับทำการปิด RecordSet เดิมด้วย (หากลืมปิด RS.Close)
    Set RS = New ADODB.Recordset
    
    ' หากกรณีต้องการเฉพาะข้อมูลบางอย่าง ต้องมาสร้างเงื่อนไข WHERE ที่นี่
    If blnSearch Then
        ' ว่างเปล่า ... แก้ไขเอาน่ะ
    
    ' ให้แสดงผลข้อมูลออกมาทั้งหมด นั่นคือเราทำ Query ที่นี่ ไม่ใช่ไปทำที่ Active Report Designer น่ะครับ
    Else
        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 " & _
                        " ORDER BY CustomerPK "
    End If
    
    ' อ่านข้อมูลแบบเดินหน้าอย่างเดียว (adOpenForwardOnly) จะทำให้เข้าถึงข้อมูลได้เร็วขึ้น
    ' เพราะนำข้อมูลมาแสดงผลเท่านั้น ไม่ได้นำมาทำการบันทึกผลยังไงล่ะครับ
    RS.Open Statement, ConnDB, adOpenForwardOnly, adLockReadOnly, adCmdText
    
    ' ส่วนสำคัญในการทำรายงานด้วย Active Report คือการตั้งค่า หรือ การผูกรายงานเข้ากับ Object
    Set rptPrint = New arPrintEnvelope   ' มาจากชื่อไฟล์รายงาน (Designer)
    
    ' ARViewPrintEnvelope ตัวนี้คือ Control ที่วางไว้อยู่บนฟอร์ม frmAR2PrintEnvelope
    Set Me.ARViewPrintEnvelope.object = rptPrint
    
    ' ผูกตารางข้อมูล (Bound Control) แบบ Run Time เข้ากับ Object
    ' dcRptData (ADO Control) ตัวนี้มันไปอยู่ที่แบบฟอร์มการแสดงผลของ ActiveReport น่ะครับ
    Set rptPrint.dcRptData.Recordset = RS
    
End Sub

Private Sub Form_KeyDown(KeyCode As Integer, Shift As Integer)
    ' ก่อนจะใช้ Sub Program นี้ได้ ... พี่น้องต้องกำหนดค่าของ Form ให้คุณสมบัติ KeyPreview = True ก่อน
    Select Case KeyCode
        Case vbKeyF1: MsgBox "No help now."
        Case vbKeyF7:
                    Call cmdPreview_Click
        Case vbKeyF10:
                    cmdExit_Click
   
   End Select
End Sub

Private Sub Form_Load()
On Error GoTo ErrorHandler
    
    ' ตั้งตำหน่งกึ่งกลางจอภาพ ... การใช้ \ หรือ การหารตัดเศษ จะทำงานได้เร็วกว่าการหาร /
    Me.Move (Screen.Width - Me.Width) \ 2, (Screen.Height - Me.Height) \ 2
    
    ' เชื่อมต่อฐานข้อมูล
    Call OpenDataBase
    
    With ARViewPrintEnvelope
        .Zoom = 100   ' ค่า % การแสดงผล Preview
        ' และอื่นๆอีก ...
    End With
    
    ' ตั้งค่าเป็นเท็จเพื่อบ่งบอกว่าไม่ใช่การค้นหาข้อมูล
    blnSearch = False
    ' อันที่จริงก่อนทำการ Preview ผมจะทำเป็นฟังค์ชั่นแทนเอา เช่น PreviewReport(False)
    ' เวลาประกาศในฟังค์ชั่น ก็จะประกาศแบบนี้ ... Function PreviewReport(blnSearch As Boolean)
    ' เพราะสั่งแบบนี้มันง่าย และ สะดวกกว่าเยอะครับ
    
ExitProc:
    Exit Sub
    
ErrorHandler:
    MsgBox "Error : " & Err.Number & vbCrLf & Err.Description, vbOKOnly + vbExclamation, "รายงานความผิดพลาด"
    Resume ExitProc
End Sub

Private Sub Form_Resize()
    ' จัดระเบียบสังคม เอ๊ย หน้าตาการแสดงผล ...
    ' เทคนิค:- การคำนวณหาระยะการแสดงผลให้ง่ายนั้น ควรเอา Control ต่างๆ ไปวางไว้ใน Frame ก่อน (fraData)
    fraData.Width = Me.ScaleWidth - 30
    fraData.Height = Me.ScaleHeight - fraCommand.Height
    ARViewPrintEnvelope.Move 15, 120, fraData.Width - 60, fraData.Height - 180
    fraCommand.Move 15, fraData.Top + fraData.Height, Me.ScaleWidth - 30
    cmdExit.Move fraCommand.Width - cmdExit.Width - 60
    cmdPreview.Move fraCommand.Width - cmdExit.Width - cmdPreview.Width - 120
End Sub

Private Sub Form_Unload(Cancel As Integer)
On Error Resume Next
    ' ก่อนจบโปรแกรม ควรลบไฟล์ขยะออกไปให้หมด (Temporary File)
    If Dir$(App.Path & "\*.tmp") <> "" Then Kill App.Path & "\*.tmp"
    
    ' ปิดฐานข้อมูล
    Call CloseDataBase
    End
End Sub

Private Sub cmdExit_Click()
    Unload Me
End Sub

ส่วนของ Active Reports Designer (arPrintEnvelope.dsr)

Option Explicit

' #####################################################
' โปรแกรมย่อยในการตั้งค่าเริ่มต้น (Initialize) ต่างๆ ก่อนที่จะทำการพิมพ์ออกไป
' #####################################################
Private Sub ActiveReport_Initialize()
    
    ' ตั้งค่ากระดาษในแนวนอน
    PageSettings.Orientation = ddOLandscape
    
    ' ซองจดหมาย หรือ Envelope #10, ขนาด 4 1/8 x 9 1/2 in
    PageSettings.PaperSize = 20
    ' อันที่จริงใช้ User Defined แทนเอาก็ได้ เพราะเครื่องพิมพ์ทุกวันนี้มันทำ Custom Size ได้
    
    ' ปรับระยะขอบต่างๆ
    PageSettings.LeftMargin = 300
    PageSettings.RightMargin = 300
    PageSettings.BottomMargin = 100
    PageSettings.TopMargin = 300
    
    ' เคลียร์ค่าการพิมพ์รายชื่อ ที่อยู่ลูกค้า
    txtCustomer.Text = ""
    ' ตัวอย่างการปรับ Font แบบ Run Time
    ' txtCustomer.Font.Name = "Tahoma"
    ' txtCustomer.Font.Size = 14
    ' txtCustomer.Font.Bold = True
    
    ' เคลียร์ค่าการพิมพ์ชื่อผู้ส่ง หากไม่เอาก็กำหนดเป็น txtSender.Visible = False
    txtSender.Text = ""
    
    ' กำหนดตำแหน่งการพิมพ์แบบ Run Time ... อย่าลืมว่ามันเป็นหน่วย Twips
    ' หากอยากได้หน่วยวัดแบบอื่นก็เทียบบัญญัติไตรยางค์เอาครับ ... 1440 Twips = 1 นิ้ว หรือ 2.54 ซม.
    txtCustomer.Left = 4420
    
End Sub

' #####################################################
' Detail คือส่วนที่พิมพ์ข้อมูล รายละเอียดต่างๆ ของการทำรายงานด้วย Active Report
' #####################################################
Private Sub Detail_Format()
On Error Resume Next
    
    ' ############################################################
    ' จากฟอร์ม Print Preview ... Set rptPrint.dcRptData.Recordset = RS
    ' ดังนั้นพอใน AR Designer เราก็ใช้ dcRptData เป็นตัวจัดการข้อมูลแทน
    ' โดยอ้างอิงตามฟิลด์ที่ส่งมา เช่น dcRptData.Recordset("CustomerName")
    ' ############################################################

    ' ใช้ TextBox ตัวเดียวก็พอในการพิมพ์ที่อยู่ผู้รับ ... แต่อย่าลืมตั้งค่า Properties ให้ MultiLine = True
    txtCustomer.Text = "กรุณานำส่ง" & vbCrLf
    txtCustomer.Text = txtCustomer.Text & Trim(dcRptData.Recordset("CustomerName")) & vbCrLf
    txtCustomer.Text = txtCustomer.Text & Trim(dcRptData.Recordset("Address")) & vbCrLf
    
    
    ' ในการ Query จริงๆนั้น ไม่จำเป็นต้องเอาฟิลด์ ProvincePK มา เพราะมันเกิดการเชื่อมความสัมพันธ์กันแล้วครับ
    ' จะใช้ ProvinceFK ในการทดสอบ หรือ ProvinceName ก็ได้ครับ ... แต่แนะนำให้ใช้ ProvinceFK จะดีกว่า
    ' เพราะในตัวอย่างจะใช้คำว่า กรุงเทพฯ หากตอนหลังอยากเปลี่ยนเป็น กรุงเทพมหานคร ก็จะได้ไม่ต้องมานั่งแก้ไขโค้ด
    
    ' ############################################################
    ' ตรวจสอบกรณี ProvinceFK = 2 หรือ กรุงเทพฯ ให้ใช้คำว่าเขต แทน อำเภอ
    ' และไม่ต้องมีคำว่าจังหวัดนำหน้า ... หากไม่อยากมาปวดหัวทีหลังก็ต้องออกแบบตารางข้อมูลให้ดีๆด้วยน่ะครับ
    ' นี่แหละคือสิ่งที่จะพิสูจน์ว่า การออกแบบตารางข้อมูลของเราถูกต้อง แม่นยำ เพียงใดครับ ... พี่น้อง
    ' ############################################################
    If dcRptData.Recordset("ProvinceFK") = 2 Then
        txtCustomer.Text = txtCustomer.Text & "เขต" & Trim(dcRptData.Recordset("Amphur")) & _
                            "   " & (dcRptData.Recordset("ProvinceName")) & _
                            "   " & Trim(dcRptData.Recordset("PostCode"))
    
    ' กรณีที่ไม่ใช่กรุงเทพมหานคร (ProvinceFK <> 2) ให้ใช้คำว่าอำเภอ และ จังหวัดแทน
    Else
        txtCustomer.Text = txtCustomer.Text & "อ." & Trim(dcRptData.Recordset("Amphur")) & _
                            "   จ." & Trim(dcRptData.Recordset("ProvinceName")) & _
                            "    " & Trim(dcRptData.Recordset("PostCode"))
    End If

End Sub

Private Sub PageHeader_Format()
    ' อย่าส่งจดหมายมาหาคนชื่อข้างล่างนี้ก็แล้วกันน่ะครับ ... 55555+
    txtSender.Text = "ชื่อผู้ส่ง" & vbCrLf & _
                            "ด.ช. ทองก้อน นารีแขยง" & vbCrLf & _
                            "142 ม.11 ถ.กลางเมือง ต.เมืองเก่า อ.เมือง" & vbCrLf & _
                            "จ.ขอนแก่น 40000" & vbCrLf

End Sub

Private Sub ActiveReport_ReportEnd()
    Unload Me
End Sub
Conclusion:
หลายคนโดยเฉพาะโปรแกรมเมอร์มือใหม่ๆ ก็คงจะสงสัยแหละว่า ทำไมข้อมูลของผมมันถึงไม่ได้เหมือนในหนังสือ หรือ ตามห้องเรียนเลย ... ขอบอกเลยครับว่า แรกๆตอนที่ผมเรียนรู้ ฝึกฝนด้วยตัวเอง ผมก็หลงทิศ หลงทาง ออกอ่าวตังเกี๋ยไปโน่นเลยครับ 55555+ ... ก็เคยเจอปัญหามาก่อนแล้ว เลยต้องมานั่งคิด นั่งแก้ หาทางออกอยู่ตั้งนาน ... ดังนั้นวัตถุประสงค์ที่สำคัญของส่วนบทความต่างๆในเว็บไซต์แห่งนี้ คือ ผมไม่อยากให้น้องๆโปรแกรมเมอร์รุ่นใหม่ๆ ต้องมาเสียเวลาเหมือนอย่างผม อยากให้พวกน้องๆ ได้รับรู้ข้อมูลที่ถูกต้อง พร้อมๆกับสามารถก้าวกระโดดในการพัฒนาฝึกฝนไปได้อย่างรวดเร็ว ... เพื่อเป็นกำลังสำคัญของชาติต่อไป

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