Về việc nạp dữ liệu nguồn cho form, chúng ta cũng cần chú ý đến việc nạp dữ liệu nguồn cho các ComboBox hoặc ListBox.
Khi nạp dữ liệu nguồn cho các ComboBox hoặc ListBox chúng ta cũng phải tuân thủ nguyên tắc chỉ nạp dữ liệu trong phạm vi giới hạn vừa đúng với nhu cầu khai thác xử liệu.
Sau đây tôi xin trình bày một trong những cách thức nạp dữ liệu nguồn cho ComboBox xác định tuân thủ nguyên tắc nêu trên. Cụ thể như sau:
Nhu cầu đặt ra là: trên form cần có 1 ComboBox dùng để liệt kê sẵn danh sách tên và địa chỉ khách có trong bảng dữ liệu “tblDanhsach”.
Thay vì ta cho nạp nguồn dữ liệu cho ComboBox này 1 lần ngay khi form được mở, ta sẽ cho lọc danh sách nguồn theo 1 điều kiện xác định.
Điều kiện lọc ở đây được ban hành bằng cách ta nhập thẳng 1 vài từ cần tìm trong tên của khách (có trong bảng danh sách), sau đó chương trình sẽ tự động nạp nguồn dữ liệu theo điều kiện lọc này. Làm như vậy ta sẽ hạn chế được khối lượng dữ liệu hữu ích cần nạp, đồng thwofi cũng làm cho việc hiện danh sách sổ xuống nhanh hơn.
Cách làm như sau:
- Giả định ta đặt tên ComboBox nói trên là “combo0”
- Trong class module của form chứa ComboBox nêu trên, ta viết 1 thủ tục có nội dung như sau để thiết lập nguồn dữ liệu cho ComboBox “combo0”.
Thủ tục này có tham số “stFilter” sẽ là chuỗi ký tự lập thành điều kiện lọc do người sử dụng nhập vào tại ComboBox “combo0”.
Mã:
[COLOR=green]Private Sub SetComboRowSource([COLOR=blue]stFilter[/COLOR])[/COLOR]
Dim sqlSt As String
Dim r As ADODB.Recordset
sqlSt = "SELECT ten, diachi, danhbaid FROM " & GetSchemaTable("tblDanhsach") & ".tblDanhsach"
sqlSt = sqlSt & [COLOR=#ff0000]" WHERE ten LIKE N'%" & stFilter & "%'"[/COLOR]
sqlSt = sqlSt & " ORDER BY danhbaid"
Set r = ProcessRecordset(sqlSt)
[COLOR=#0000cd]
Set Me.Combo0.Recordset = r[/COLOR]
With Me.Combo0
.BoundColumn = 1
.ColumnCount = 3
.ColumnWidths = "7 Cm;7 Cm;0"
End With
r.Close
Set r = Nothing
[COLOR=teal]End Sub[/COLOR]
Các Bạn lưu ý: thay vì cho nạp chuỗi nguồn dữ liệu cho property “RowSource” của ComboBox, tôi cho nạp thuộc tính “Recordset” cho ComboBox này. Tôi làm như vậy để cho gọn gàng thôi.
- Với ComboBox “combo0” ta viết thủ tục sự kiện Enter có nội dung như sau:
Mã:
[COLOR=teal]Private Sub Combo0_Enter()[/COLOR]
If Len(Me.Combo0) > 0 Then SetComboRowSource Me.Combo0
[COLOR=teal]End Sub[/COLOR]
Có Bạn nào có giải pháp khác xin trao đổi thêm nhé.
Chào các Bạn,
Tôi vừa nhận được email của 1 Bạn hỏi về vấn đề nạp nguồn dữ liệu cho ComboBox mà chúng ta đã trao đổi ở #21. Bạn ấy hỏi:
"Tôi muốn cứ mỗi khi gõ vào 1 chuỗi thì ComboBox được lọc ngay theo chuỗi này thì phải làm sao?"
Ở đây ta cần cân nhắc xem việc lọc nguồn dữ liệu có cần thực hiện ngay tại thời điểm "cứ mỗi khi gõ vào" hay không?
Rõ ràng trong thực tế ta không cần đến mức tức thì "cứ mỗi khi gõ vào" như vậy. Nếu làm việc này tôi e rằng sẽ mất rất nhiều thời gian để ứng dụng nạp xong dữ liệu nguồn theo điều kiện lọc ta gõ vào.
Do đó, tôi đề nghị 1 giải pháp như sau: chỉ khi nào ta bấm phím lệnh cho hiện danh sách sổ xuống thì lúc ấy ứng dụng hãy cho nạp dữ liệu nguồn được lọc theo chuỗi ta đã nhập vào ComboBox. Cách làm như sau:
1. Bỏ thủ tục đáp ứng sự kiện Enter của ComboBox như ta đã làm như đã trình bày trong bài trên (#21)
2. Viết thủ tục đáp ứng sự kiện KeyDown như sau, để mỗi khi ta bấm phím F4 hoặc tổ hợp phím (Alt+Mũi tên xuống) ứng dụng sẽ cho nạp dữ liệu nguồn được lọc theo chuỗi ta đã nhập vào ComboBox này.
Chúng ta đã biết: phím F4 hoặc tổ hợp phím (Alt+Mũi tên xuống) dùng để cho hiện danh sách sổ xuống của ComboBox
Mã:
[COLOR=#006400]Private Sub Combo0_KeyDown([/COLOR][COLOR=#0000cd][B]KeyCode [/B]As Integer, Shift As Integer[/COLOR][COLOR=#006400])[/COLOR]
Dim stFilter
If KeyCode = vbKeyF4 Or (KeyCode = vbKeyDown And Shift = acAltMask) Then [COLOR=#ff0000]'Bẩy phím F4 hoặc Alt+Mũi tên xuống[/COLOR]
stFilter = Me.Combo0.Text
If Len(stFilter) > 0 Then
SetComboRowSource stFilter
End If
End If
[COLOR=#006400]End Sub[/COLOR]
Các Bạn nào có giải pháp khác xin trao đổi thêm nhé.
Về việc kết nối với dữ liệu nguồn qua mạng máy tính tôi thấy cũng cần trao đổi thêm về việc tổ chức dữ liệu sao cho việc kết nối dữ liệu được thuận lợi và hiệu quả nhất.
Theo tôi thấy (có thể các Bạn sẽ thấy khác): Trong thực tế, không phải lúc nào chúng ta cũng cần lấy dữ liệu xuống bằng cách kết nối với dữ liệu nguồn đặt tại server; có những nguồn dữ liệu có tính ổn định nhất định (không bị thay đổi thường xuyên) ta có thể cho trích xuất với phạm vi giới hạn nhất định và cho lưu xuống máy client (máy khách cần kết nối vào server), sau đó ta sẽ cho nạp nguồn dữ liệu từ dữ liệu đã được trích xuất này. Làm như vậy ta vừa cải thiện được tốc độ truy xuất dữ liệu, vừa giảm được tải không cần thiết cho cả server và client.
Các Bạn thử xem xét tình huống sau đây nhé:
Với doanh nghiệp bán hàng trên phạm vi rộng, có ứng dụng chạy trên client kết nối đến dữ liệu nguồn ở server, ứng dụng này dành cho các nhân viên thị trường sử dụng trên các laptop để thực hiện nhiệm vụ "Tìm kiếm khách mua hàng và lập đơn đặt hàng theo bảng giá ấn định chung". Mỗi nhân viên thị trường đều được phân công phụ trách một phạm vi địa lý nhất định.
Như vậy, ta có thể cho trích xuất các nguồn dữ liệu sau lưu xuống máy client để ứng dụng client sử dụng trực tiếp, không cần phải lấy từ server thông qua kết nối qua mạng:
+ Danh sách khách hàng trong phạm vi địa lý đã phân công cho từng nhân viên;
+ Danh mục hàng hoá (có bảng giá) cũng trong giới hạn cần thiết.
Đồng thời với đó, ta sẽ có các thủ tục thích hợp để cho đồng bộ dữ liệu đang lưu tạm trên các máy Client với dữ liệu gốc trên server. Việc đồng bộ dữ liệu này sẽ được thực hiện tại thời điểm thích hợp (trong ngày hoặc trong tuần) hoặc khi có sự kiện thay đổi dữ liệu xảy ra (như đơn giá được người có thẩm quyền cập nhật mới, ...).
Các Bạn có thấy điều gì không ổn trong đề nghị trên của tôi không? Xin vui lòng góp ý trao đổi thêm.
Trước hết tôi rất cám ơn những gì bạn đã tận tình hướng dẫn cho mọi người, nhất là những thành viên còn rất mơ hồ về Access như tôi. Bạn hướng dẫn rất cụ thể và tận tình, những bài viết của bạn thật hữu ích.
Qua đường link của bài này khi mình tải về thì có hiện tượng như sau:
Sau khi bấm nút "Lấy Danh Sách" thì nó hiện một thông báo:
Nếu bấm No sẽ hiện ra lỗi:
Chọn Debug thì lỗi đặt tại đây:
Như vậy cần phải làm gì để bẫy lỗi này? Và nguyên nhân từ đâu?
Chào Bạn,
Muốn sử dụng chức năng "Lấy Danh sách" trước hết bạn phải tạo 1 bảng dữ liệu ngay bên trong file ứng dụng, tên bảng là "tblDs" với 2 cột:
+ Cột Id, kiểu dữ liệu là Number (Long)
+ Cột Ten, kiểu dữ liệu là Text
Nhưng cho mình hỏi, mình vẫn chưa hiểu cái nút Lấy danh sách. Sau khi đặt thủ tục không hiện lên thông báo, thì tất cả những gì có trong tblDs đã bị xóa sạch. Vậy Lấy danh sách gì vậy bạn? Hỏi để biết thêm nguyên lý hoạt động của form này.
Xin cám ơn.
Nhưng cho mình hỏi, mình vẫn chưa hiểu cái nút Lấy danh sách. Sau khi đặt thủ tục không hiện lên thông báo, thì tất cả những gì có trong tblDs đã bị xóa sạch. Vậy Lấy danh sách gì vậy bạn? Hỏi để biết thêm nguyên lý hoạt động của form này.
Xin cám ơn.
Chào Bạn,
Về vấn đề Bạn hỏi về cái nút lệnh "Lấy Danh sách": đây chỉ làm ví dụ minh hoạ cho việc tạo và sử dụng 1 Collection tự tạo thôi (xem #16). Mặt khác, qua đó tôi cũng đã ngầm chuẩn bị cho nội dung như đã trao đổi ở bài #23 ở trên về việc trích xuất dữ liệu từ server cho lưu xuống máy client. Đây cũng chỉ là 1 trong rất nhiều giải pháp thôi, không phải là duy nhất.
Còn về việc ra lệnh xoá nội dung bảng "tblDs" rồi nạp lại để làm gì? Trong tình huống này, bảng "tblDs" như là 1 bảng lưu tạm dữ liệu lên máy client để tôi dùng vào 1 việc gì đó (chẳng hạn làm nguồn cho 1 ComboBox hoặc ListBox), khi cần nạp nội dung khác thì phải xoá nội dung cũ đi để nạp lại cái mới.
Khi xem file ứng dụng minh hoạ, các Bạn chỉ nên coi đó là trường hợp minh hoạ cho những nội dung tôi trao đổi cùng các Bạn, đừng xem đó là 1 ứng dụng hoàn chỉnh.
Chào các Bạn,
Có Bạn hỏi: muốn thiết kế 1 form có SubForm theo UnBound Form thì phải làm sao? Chẳng hạn như thiết kế 1 form để nhập chứng từ nhập / xuất hàng hoá (với SubForm trình bày chi tiết hàng hoá phát sinh của chứng từ).
Do quá bận nên tôi chưa thể trao đổi cụ thể được về vấn đề này, xin hẹn các Bạn trong những ngày tới. Hôm nay chỉ xin trao đổi một số gợi ý để các Bạn tham khảo như sau:
- Với MainForm để đăng ký các thông tin chung của chứng từ chúng ta thiết kế form và viết thủ tục để truy xuất dữ liệu có liên quan theo cách tương tự như ta đã làm trong file ứng dụng minh hoạ với Danh sách trong Danh bạ điện thoại.
- Để hiển thị thông tin hàng hoá chi tiết phát sinh của chứng từ, các Bạn có thể thiết kế theo 1 trong các cách sau:
+ Thiết kế 1 ListBox gồm có các cột dữ liệu phản ảnh thông tin chi tiết của hàng hoá
+ Hoặc thiết kế 1 Form độc lập để làm SubForm, lấy dữ liệu nguồn là Recordset được lọc theo số chứng từ phát sinh xác định, số chứng từ này ta sẽ lấy từ ô ghi số chứng từ trên MainForm. Các Bạn cần chú ý thiết lập kiểu dữ liệu của Recordset này phù hợp với nhu cầu chỉ để hiển thị thông tin thôi.
+ Thiết kế các ô để nhập dữ liệu chi tiết hàng hoá phát sinh (như: mã hàng, tên hàng, đơn vị tính, số lượng, đơn giá,...), các ô dữ liệu này cũng không gắn liền với nguồn dữ liệu xác định nào cả (nghĩa là không khai báo ControlSource). Để cập nhật thông tin nhập trên các ô này vào bảng dữ liệu có liên quan ta sẽ viết 1 thủ tục cập nhật (tương tự như thủ tục cập nhật danh sách phát sinh trong danh bạ vậy).
Để giúp các Bạn có điều kiện test dữ liệu qua internet, tôi đã bổ sung vào database "danhba" (là nguồn dữ liệu SQL SERVER được sử dụng trong file ứng dụng minh hoạ ta đã dùng từ bài đầu đến nay) 2 bảng dữ liệu sau đây:
1. Bảng dữ liệu để đăng ký thông tin chung của chứng từ nhập / xuất:
- Tên bảng: "tblctunx"
- Các Field dữ liệu:
+ Id (PK - numeric theo dạng AutoNumber)
+ soctu kiểu nchar(20)
+ ngay kiểu smalldatetime
+ msnv kiểu nchar(10) - dùng để đăng ký nghiệp vụ phát sinh là nhập hay xuất (và loại nhập xuất cụ thể nào, nếu các Bạn muốn phân biệt tới mức chi tiết như vậy)
+ mskh kiểu numeric(18,0) - dùng để đăng ký mã số khách hàng
+ tsuatvat kiểu numeric(18,0) - dùng để đăng ký thuế suất thuế VAT
2. Bảng đăng ký thông tin chi tiết hàng hoá:
- Tên bảng: "tblctunxct"
- Các Fields dữ liệu:
+ Id (PK, kiểu numeric(18,0)
+ soctu (PK, nchar(20)
+ mshh (PK, numeric(18,0) - đăng ký mã số hàng hoá
3 Field trên đều được khai báo là khoá chính của bảng (PK) để tránh trùng dữ liệu theo quy tắc: mỗi mặt hàng chỉ được đăng ký 1 dòng trong bảng.
+ dvt kiểu smallint - đăng ký đơn vị tính, tạm thời ta quy ước đơn vị tính thấp nhât với chỉ số = 1, sau này ta sẽ thiết kế bảng đăng ký hệ thống đơn vị tính cho hàng hoá (theo hướng 1 mặt hàng có thể đăng ký nhiều đơn vị tính khác nhau, các đơn vị tính này có liên quan với nhau thông qua 1 chỉ số quy số lượng về đơn vị tính thấp nhất)
+ soluong kiểu numeric(18,0)
+ dongia kiểu numeric(18,0)
Để cho đơn giản, trước mắt ta cho nhập tự do mã số khách hàng và mã số hàng hoá; sau này ta sẽ tạo thêm 2 bảng ghi danh sách khách hàng và ghi danh mục hàng hoá.
Hôm nay xin trao đổi tiếp tục vấn đề đang bỏ dỡ hôm trước: Thiết kế 1 UnBound Form có SubForm kết nối dữ liệu tới SQL Server
Nhu cầu ứng dụng: Ta cần 1 form để quản lý chứng từ nhập xuất kho hàng, bao gồm các chức năng: cho nhập chứng từ mới phát sinh, cho truy xuất lại chứng từ đã lập, cho cập nhật lại các thông tin của chứng từ đã nhập.
Các bảng dữ liệu SQL Server phục vụ cho nhu cầu trên đã được tôi chuẩn bị sẵn gồm có:
1. Bảng ghi danh mục hàng hóa: tbldmhanghoa
Gồm các cột dữ liệu sau:
+ mshh: PK, numeric(18,0)
+ tenhanghoa: nvarchar(255)
+ xuatxu: nvarchar(50)
+ dactrung: nvarchar(255)
2. Bảng ghi hệ thống đơn vị tính của từng mặt hàng: tbldonvitinh
Gồm các cột dữ liệu sau:
+ mshh: PK, numeric(18,0)
+ cap: PK, smallint, đăng ký cấp của đơn vị tính
+ kihieu: nchar(10)
+ mota: nvarchar(50)
+ quycap1: numeric(18,0)
+ dongianhap: numeric(18,0)
+ dongiaxuat1: numeric(18,0)
+ dongiaxuat2: numeric(18,0)
+ dongiaxuat3: numeric(18,0)
3. Bảng ghi các thông tin chung của chứng từ nhập xuất phát sinh: tblctunx
Gồm các cột dữ liệu sau:
+ Id: PK, numeric(18,0)
+ soctu: nchar(20)
+ ngay: smalldatetime
+ msnv: nchar(10)
+ mskh: numeric(18,0)
+ tsuatvat: numeric(18,0)
+ nguoigiaodich: nvarchar(255)
4. Bảng ghi các thông tin về chi tiết hàng hóa của chứng từ nhập xuất phát sinh: tblctunxct
Gồm các cột dữ liệu sau:
+ Id: PK, numeric(18,0)
+ soctu: nchar(20)
+ mshh: numeric(18,0)
+ dvt: smallint
+ soluong: numeric(18,0)
+ dongia: numeric(18,0)
+ lacktyle: bit, đăng ký nội dung: có phải là chiết khấu theo tỷ lệ hay không?
+ mucck: decimal(18,2), đăng ký nội dung: mức chiết khấu cụ thể là bao nhiêu? Nếu là chiết khấu tỷ lệ thì nhập nguyên không có chia phần trăm (thí dụ: nếu chiết khấu với tỷ lệ là 2,5%, ta nhập 2,5)
5. Bảng đăng ký danh mục các nghiệp vụ phát sinh: tbldmnghiepvu
Khi lập chứng từ nhập xuất, để xác định nghiệp vụ phát sinh cụ thể (cần thống nhất mã nghiệp vụ phát sinh để tiện quản lý về sau này)
Gồm các cột dữ liệu sau:
+ msnv: PK, nchar(5), đăng ký mã số nghiệp vụ
+ tennghiepvu: nvarchar(255)
Với file ứng dụng minh họa này,
- Để hiển thị nội dung thông tin chi tiết các mặt hàng trong chứng từ phát sinh, tôi thiết kế 1 Subform với nguồn dữ liệu được nạp một cách linh hoạt, không cố định, tùy thuộc vào số chứng từ đang mở trên form chính.
- Khi thiết kế UnBound Form theo nhu cầu như trên đã nêu, theo tôi chúng ta cần phải chú ý những vấn đề sau đây:
1. Việc nạp nguồn dữ liệu cho SubForm nên chọn nạp thông qua property “Recordset” của SubForm, điều này khác với cách hay làm thông thường là xác định thông qua thuộc tính “RecordSource”.
Các Bạn có thể thấy cách thức tôi đã làm trong file ứng dụng mẫu, để nạp nguồn dữ liệu cho SubForm tôi đã viết thủ tục sau trong module “modQuanlyDulieu”:
Mã:
[B]Sub SetSourceRecForSubForm(mForm As Form, sForm As String)[/B]
Dim SQLst As String
Dim SQLrec As ADODB.Recordset
Dim tblName As String
Dim vSoCtu, stChema As String
vSoCtu = mForm!cmbSoCtu
If Not IsNull(vSoCtu) Then
tblName = "tblctunxct"
stChema = GetSchemaTable(tblName)
SQLst = "SELECT " & stChema & ".tbldmhanghoa.tenhanghoa, " & stChema & ".tblctunxct.*"
SQLst = SQLst & " FROM " & stChema & ".tbldmhanghoa INNER JOIN " & stChema & ".tblctunxct"
SQLst = SQLst & " ON " & stChema & ".tbldmhanghoa.mshh=" & stChema & ".tblctunxct.mshh"
SQLst = SQLst & " WHERE " & stChema & ".tblctunxct.soctu = '" & vSoCtu & "'"
Set SQLrec = ProcessRecordset(SQLst)
Set mForm(sForm).Form.Recordset = SQLrec
With mForm(sForm).Form
.Requery
!txtId.ControlSource = "id"
!txtMSHH.ControlSource = "mshh"
!txtTenHanghoa.ControlSource = "tenhanghoa"
!txtCapDvt.ControlSource = "dvt"
!txtDvt.ControlSource = "=IIF(not isnull(dvt),flookup('kihieu','tbldonvitinh','cap=' & [dvt]),'')"
!txtSoluong.ControlSource = "soluong"
!txtDongia.ControlSource = "dongia"
!chkCKTL.ControlSource = "lacktyle"
!txtMucCK.ControlSource = "mucck"
End With
‘Nhớ đóng Recordset đã gán cho SubForm bằng 2 dòng lệnh sau nhằm mục đích tiết kiệm tài nguyên hệ thống:
SQLrec.Close
Set SQLrec = Nothing
End If
[B]End Sub[/B]
Như các Bạn đã thấy trong thủ tục trên, ngay sau khi đã gán Recordset SQLrec cho SubForm qua dòng lệnh:
Mã:
Set mForm(sForm).Form.Recordset = SQLrec
Tôi đã cho đóng Recordset SQLrec này lại. Việc đóng Recordset SQLrec không dẫn đến việc đóng Recordset của SubForm.
2. Chúng ta cũng cần lưu ý đến nhu cầu kép đối với nguồn dữ liệu của SubForm phải vừa cho hiển thị nội dung, vừa cho cập nhật lại hoặc xóa chi tiết hàng hóa phát sinh.
Để đáp ứng nhu cầu trên, tôi đã cho SubForm chỉ làm nhiệm vụ hiển thị nội dung thông tin chi tiết về hàng hóa phát sinh.
Đối với nhu cầu cập nhật lại hoặc xóa tôi cho thực hiện bằng cách:
+ Trên Form chính, tôi thiết kế các ô dữ liệu tương ứng với các cột dữ liệu của chi tiết hàng hóa cần cập nhật lại hoặc nhập mới, đồng thời viết thủ tục cho cập nhật các chi tiết này ngay trong class module của Form chính.
Nút lệnh gọi thủ tục cập nhật này được bố trí bên phải của các ô dữ liệu tương ứng, có hình Floppy-Disk
Nút lệnh gọi thủ tục xóa chi tiết hàng đang chọn được bố trí bên trái của các ô dữ liệu tương ứng, có hình gạch chéo màu đỏ. Muốn xóa 1 dòng chi tiết hàng nào đó, trước hết ta phải cho nạp dòng đó lên các ô dữ liệu tương ứng đang nói ở đoạn này.
Thủ tục cập nhật về chi tiết hàng hóa của chứng từ như sau:
Mã:
[B]Sub SaveToInvoiceDetailFromForm(Optional InVoiceDetailId)[/B]
'Luu thong tin tren form vao tblctunxCT
'UpdateInvoiceDetail
On Error GoTo HandleError
Dim SQLst As String, tblName As String
Dim vId
Dim MucCK As Double, CKTL As Byte
Call OpenMyConnection
tblName = "tblctunxct"
With Me
vId = Me.txtDetailId
MucCK = Nz(.txtMucCK)
If IsNull(.chkCKTL) Then
CKTL = 0
Else
If .chkCKTL.Value = True Then
CKTL = 1
Else
CKTL = 0
End If
End If
If Not IsNull(vId) Then
SQLst = "UPDATE " & GetSchemaTable(tblName) & "." & tblName & " SET "
SQLst = SQLst & " soctu ='" & Trim(.cmbSoCtu) & "',"
SQLst = SQLst & " mshh =" & .cmbMSHH & ","
SQLst = SQLst & " dvt =" & .cmbDvt & ","
SQLst = SQLst & " soluong =" & .txtSoluong & ","
SQLst = SQLst & " dongia =" & .txtDongia & ","
SQLst = SQLst & " lacktyle =" & CKTL & ","
' SQLst = SQLst & " mucck =" & Format(MucCK, "#,###.0#")
SQLst = SQLst & " mucck =" & MucCK
SQLst = SQLst & " WHERE ("
SQLst = SQLst & " soctu='" & Trim(Me.cmbSoCtu) & "'"
SQLst = SQLst & " AND id=" & InVoiceDetailId
SQLst = SQLst & ")"
Else
SQLst = "INSERT INTO " & GetSchemaTable(tblName) & "." & tblName
SQLst = SQLst & "(soctu, mshh, dvt, soluong, dongia, lacktyle, mucck)"
SQLst = SQLst & " VALUES ("
SQLst = SQLst & " '" & Trim(.cmbSoCtu) & "',"
SQLst = SQLst & " " & .cmbMSHH & ","
SQLst = SQLst & " " & .cmbDvt & ","
SQLst = SQLst & " " & .txtSoluong & ","
SQLst = SQLst & " " & .txtDongia & ","
SQLst = SQLst & " " & CKTL & ","
SQLst = SQLst & " " & Nz(MucCK)
SQLst = SQLst & ")"
End If
End With
Debug.Print SQLst
MyConn.Execute SQLst
Call CloseMyConnection
HandleError:
If Err > 0 Then
GeneralErrorHandler Err.Number, Err.Description, NhapXuat_FORM, "SaveToInvoiceDetailFromForm"
Exit Sub
End If
[B]End Sub[/B]
3. Với các ComboBox, chúng ta cũng cần cân nhắc việc nạp nguồn dữ liệu cho các ComboBox này (để có danh sách sổ xuống) sao cho phù hợp, chỉ nạp khi cần và với giới hạn xác định.
Để đáp ứng nhu cầu này, tôi chỉ cho nạp nguồn dữ liệu cho ComboBox khi nào ta cho gọi hiện danh sách sổ xuống (thường là bằng cách bấm phím F4 hoặc Alt + phím mũi tên xuống). Do vậy, tôi viết thủ tục sau để gán nguồn dữ liệu cho ComboBox, và khai báo thủ tục sự kiện KeyDown (khi có phím bấm xuống) tại ComboBox.
Thủ tục gán dữ liệu nguồn:
Mã:
[B]Private Sub SetComboRowSource(ComboName As String, RecSourceSt As String, stFilter As String)[/B]
'Nap RowSource cho ComboBox có tên qua biến ComboName
Dim SQLst As String
Dim SourceRec As ADODB.Recordset
SQLst = RecSourceSt & " WHERE " & stFilter 'ten LIKE N'%" & stFilter & "%'"
Set SourceRec = ProcessRecordset(SQLst)
Set Me(ComboName).Recordset = SourceRec
SourceRec.Close
Set SourceRec = Nothing
[B]End Sub[/B]
Và nội dung thủ tục bẩy sự kiện tương tự như sau (ở đây là bẩy sự kiện KeyDown của ComboBox lấy danh sách khách hàng từ nguồn là bảng tblDanhsach):
Mã:
[B]Private Sub cmbKhachhang_KeyDown(KeyCode As Integer, Shift As Integer)[/B]
Dim srcSt As String, sCri As String
Dim tblName As String
Dim InputSt
'Set RowSource For CmbKhachhang
'SetComboRowSource
If KeyCode = vbKeyF4 Or (KeyCode = vbKeyDown And Shift = acAltMask) Then
InputSt = Me.cmbKhachhang.Text
tblName = "tblDanhsach"
srcSt = "SELECT * FROM " & GetSchemaTable(tblName) & "." & tblName
sCri = " ten LIKE N'%" & InputSt & "%'"
SetComboRowSource "cmbkhachhang", srcSt, sCri
End If
'
[B]End Sub[/B]
4. Về việc cập nhật thông tin chung của chứng từ chúng ta cũng cần cân nhắc với 2 trường hợp phân biệt là Thêm chứng từ mới hay Cập nhật lại các thay đổi của chứng từ đã lập.
Tôi giải quyết vấn đề trên như sau:
- Trong cấu trúc bảng tblctunx có 1 field được xác định là khóa chính (PK) là field “Id”. Trên Form chính tôi bố trí 1 TextBox để nhận giá trị của field khóa chính này:
+ Khi TextBox này có giá trị xác định, nghĩa là trường hợp form đang hiển thị nội dung của 1 chứng từ xác định đang hiện hữu trong bảng tblctunx. Việc cập nhật thay đổi được thực hiện thông qua thủ tục SaveToInvoiceFromForm sau đây với biến InvoiceId xác định (trong thủ tục này InvoiceId là 1 biến tùy chọn – với từ khóa Optional phía trước)
Thủ tục đó như sau:
Mã:
[B]Sub SaveToInvoiceFromForm(Optional InvoiceId)[/B]
'Luu thong tin tren form vao tblctunx
On Error GoTo HandleError
Dim SQLst As String, tblName As String
Dim vId
Call OpenMyConnection
tblName = "tblctunx"
With Me
vId = Me.txtId
If Not IsNull(vId) Then ‘Nếu giá trị của TextBox txtId không là Null nghĩa là Form đang hiển thị thông tin của chứng từ đang hiện hữu.
SQLst = "UPDATE " & GetSchemaTable(tblName) & "." & tblName & " SET "
SQLst = SQLst & " soctu ='" & .cmbSoCtu & "',"
SQLst = SQLst & " ngay ='" & Format$(.txtNgay, "dd-mmm-yy") & "',"
SQLst = SQLst & " msnv ='" & .cmbNghiepvu & "',"
SQLst = SQLst & " mskh ='" & .cmbKhachhang & "',"
SQLst = SQLst & " nguoigiaodich ='" & .txtNguoiGiaodich & "',"
SQLst = SQLst & " tsuatvat =" & .txtTsuat
SQLst = SQLst & " WHERE ("
SQLst = SQLst & " soctu='" & InvoiceId & "'"
SQLst = SQLst & ")"
Else ‘Nếu giá trị của TextBox txtId là Null nghĩa là Form đang hiển thị thông tin của chứng từ chờ lưu mới.
If IsNull(.cmbSoCtu) Then Exit Sub
SQLst = "INSERT INTO " & GetSchemaTable(tblName) & "." & tblName
SQLst = SQLst & "(soctu, ngay, msnv, mskh, tsuatvat)"
SQLst = SQLst & " VALUES ("
SQLst = SQLst & " '" & .cmbSoCtu & "',"
SQLst = SQLst & " '" & Format$(.txtNgay, "dd-mmm-yy") & "',"
SQLst = SQLst & " '" & .cmbNghiepvu & "',"
SQLst = SQLst & " '" & .cmbKhachhang & "',"
SQLst = SQLst & " '" & .txtNguoiGiaodich & "',"
SQLst = SQLst & " " & .txtTsuat
SQLst = SQLst & ")"
End If
End With
MyConn.Execute SQLst
Call CloseMyConnection
HandleError:
If Err > 0 Then
GeneralErrorHandler Err.Number, Err.Description, NhapXuat_FORM, "SaveToInvoiceFromForm"
Exit Sub
End If
[B]End Sub[/B]
Và thủ tục để nạp thông tin của chứng từ đang hiện hữu trong abrng tblctunx lên Form chính như sau:
Mã:
[B]Sub LoadInvoiceInfoToForm(SoCtuSt)[/B]
Dim SQLst As String, SQLrec As ADODB.Recordset
Dim KHrec As ADODB.Recordset
Dim tblName As String, MsKH As Long
tblName = "tblctunx"
If IsNull(SoCtuSt) Then Exit Sub
SQLst = "SELECT * FROM " & GetSchemaTable(tblName) & "." & tblName
SQLst = SQLst & " WHERE soctu ='" & SoCtuSt & "'"
Set SQLrec = ProcessRecordset(SQLst)
'
If SQLrec.RecordCount > 0 Then
Set objKhachHang = New clsDanhba
With Me
.txtId = SQLrec!id
.txtNgay = SQLrec!ngay
.cmbNghiepvu = SQLrec!msnv
.txtTsuat = SQLrec!tsuatvat
.txtNguoiGiaodich = SQLrec!nguoigiaodich
MsKH = SQLrec!MsKH
SQLst = "SELECT * FROM " & GetSchemaTable("tblDanhsach") & ".tblDanhsach"
SQLst = SQLst & " WHERE danhbaid = " & MsKH
Set KHrec = ProcessRecordset(SQLst)
objKhachHang.PopulatePropertiesFromRecordset KHrec
.cmbKhachhang = MsKH
.cmbKhachhang.RowSourceType = "Value List"
.cmbKhachhang.RowSource = objKhachHang.Ten & ";" & MsKH
.txtDiachi = objKhachHang.Diachi
.txtPhone = objKhachHang.Dtvp
.txtMasoThue = objKhachHang.Msthue
KHrec.Close
Set KHrec = Nothing
'Dòng sau để cho nạp nguồn dữ liệu chi tiết hàng hóa tương ứng của chứng từ đã xác định
SetSourceRecForSubForm Me, "frmCtuNXCT"
End With
End If
'
SQLrec.Close
Set SQLrec = Nothing
[B]End Sub[/B]
Còn các vấn đề có liên quan khác như: tìm và xóa chứng từ, các Bạn tự làm nhé.
Như vậy là tôi đã trình bày xong 1 trong những cách thiết kế UnBound Form có chứa SubForm kết nối đến dữ liệu SQL Server. Và cũng xin nhắc lại rằng: có nhiều cách để ứng dụng cho nhu cầu này. Ở đây tôi chỉ trình bày cách dễ làm nhất thôi.
Có Bạn nào muốn thiết kế các Object tự tạo để quản lý các chứng từ nhập xuất phát sinh kiểu như ta đã làm để quản lý Danh bạ đã đề cập trong các bài trước không? Các Bạn thử xem sao nhé.
Cũng xin thông tin thêm về tình trạng các bảng dữ liệu mới bổ sung:
- Danh mục hàng hóa và đơn vị tính đã được nạp sẵn trên 1.000 mặt hàng, mỗi mặt hàng đều có từ 2 đến 3 đơn vị tính.
- Mới chỉ có vài chứng từ phát sinh
Chào các Bạn,
Xin trao đổi thêm nội dung còn thiếu về file ứng dụng minh họa được cập nhật hôm nay (16/7/2012):
1. Trên form chính "frmCtuNX":
+ Để nạp lại nội dung các chứng từ đã lưu trước đây, tại ô nhập số chứng từ xin bấm 1 vài ký tự số để lọc nhanh và cho sổ danh sách chứng từ xuống (với các chứng từ do tôi nhập đều có số 3 trong chuỗi số chứng từ, nên các Bạn nhập số 3), sau đó chọn số chứng từ xác định từ danh sách sổ xuống, chương trình sẽ cho nạp nội dung của chứng từ đó lên Form.
+ Để chọn khách hàng có sẵn từ danh sách: tại ô nhập khách hàng, cũng thao tác tương tự như trên, nghĩa là nhập vào 1 vài từ cần tìm rồi cho sổ danh sách xuống (thí dụ như nhập từ "Công ty"), sau đó chọn khách hàng thích hợp. Danh sách này truy xuất từ bảng dữ liệu lưu Danh bạ (tblDanhsach) ta đã xem xét trong các bài trước có sẵn trên 15.000 mẫu tin.
2. Để xóa trống các ô nhập chi tiết hàng phát sinh trong chứng từ: kích kép tại ô nhập mã số hàng hóa.
Khi chọn hoặc nhập mới số chứng từ, các ô này cũng sẽ tự động được xóa trống.
3. Với SubForm "frmCtuNXCT": xin các Bạn chú ý các thuộc tính được khai báo trong ảnh đính kèm.
Trong các thuộc tính này, các Bạn chú ý thuộc tính "Recordset-Type" đã được khai báo là kiểu "Snapshot".
Với kiểu Snapshot, Recordset sẽ được đặt ở chế độ chỉ xem, không hiệu chỉnh, không thêm, không xóa được. Access sẽ dành ít tài nguyên nhất để nạp Recordset kiểu "Snapshot"
Có một Bạn đã phát hiện lỗi không cập nhật được chứng từ mới phát sinh.
Tôi đã kiểm tra và phát hiện lỗi ở thủ tục sau, nằm bên trong Class module của Form "frmCtuNX":
Mã:
Sub SaveToInvoiceFromForm(Optional InvoiceId)
'Luu thong tin tren form vao tblctunx
'UpdateOrInsert:
'+ True: Luu thong tin thay doi vao mau tin dang hien huu
'+ Flase: Them mau tin moi
'InvoiceId: so chung tu
'
On Error GoTo HandleError
Dim SQLst As String, tblName As String
Dim vId
Call OpenMyConnection
tblName = "tblctunx"
With Me
vId = Me.txtId
If Not IsNull(vId) Then
If IsNull(InvoiceId) Then Exit Sub
SQLst = "UPDATE " & GetSchemaTable(tblName) & "." & tblName & " SET "
SQLst = SQLst & " soctu ='" & .cmbSoCtu & "',"
SQLst = SQLst & " ngay ='" & Format$(.txtNgay, "dd-mmm-yy") & "',"
SQLst = SQLst & " msnv ='" & .cmbNghiepvu & "',"
'[COLOR="green"]SQLst = SQLst & " mskh ='" & .cmbKhachhang & "',"[/COLOR] 'Đây là dòng sai, vì mskh có kiểu numeric nhưng ở đây có 2 dấu nháy ở 2 đầu nên thành kiểu Text
[COLOR="red"]SQLst = SQLst & " mskh =" & .cmbKhachhang & ","[/COLOR] 'Đây là dòng đã được hiệu chỉnh cho đúng, bỏ dấu nháy ở 2 đầu
[COLOR="blue"]SQLst = SQLst & " nguoigiaodich =N'" & .txtNguoiGiaodich & "',"[/COLOR] 'Và sẵn tiện sửa luôn dòng này để lưu được chuỗi Unicode
SQLst = SQLst & " tsuatvat =" & .txtTsuat
SQLst = SQLst & " WHERE ("
SQLst = SQLst & " soctu='" & InvoiceId & "'"
SQLst = SQLst & ")"
Else
If IsNull(.cmbSoCtu) Then Exit Sub
SQLst = "INSERT INTO " & GetSchemaTable(tblName) & "." & tblName
SQLst = SQLst & "(soctu, ngay, msnv, mskh, nguoigiaodich, tsuatvat)"
SQLst = SQLst & " VALUES ("
SQLst = SQLst & " '" & .cmbSoCtu & "',"
SQLst = SQLst & " '" & Format$(.txtNgay, "dd-mmm-yy") & "',"
SQLst = SQLst & " '" & .cmbNghiepvu & "',"
[COLOR="red"] SQLst = SQLst & " " & .cmbKhachhang & ","[/COLOR]
[COLOR="blue"] SQLst = SQLst & " N'" & .txtNguoiGiaodich & "',"[/COLOR]
SQLst = SQLst & " " & .txtTsuat
SQLst = SQLst & ")"
End If
End With
MyConn.Execute SQLst
Call CloseMyConnection
'
LoadInvoiceInfoToForm Me.cmbSoCtu
HandleError:
If Err > 0 Then
GeneralErrorHandler Err.Number, Err.Description, NhapXuat_FORM, "SaveToInvoiceFromForm"
Exit Sub
End If
End Sub
Xin cảm ơn các Bạn đã quan tâm.
Có Bạn nào thấy sai ở chỗ nào nữa không?
Và lỗi ở thủ tục sau đây, cũng ở trong Class module của form "frmCtuNX":
Mã:
Private Sub SetComboRowSource(ComboName As String, RecSourceSt As String, stFilter As String)
'Nap RowSource cho ComboBox
Dim SQLst As String
Dim SourceRec As ADODB.Recordset
SQLst = RecSourceSt & " WHERE " & stFilter 'ten LIKE N'%" & stFilter & "%'"
Set SourceRec = ProcessRecordset(SQLst)
[COLOR=#008000] 'Thêm 3 dòng kế bên dưới.
'Tôi viết kiểu With ... End With để phòng khi phải khai báo thêm gì nữa cho ComboBox [/COLOR]
[B] With Me(ComboName)[/B]
[B] .RowSourceType = "Table/Query"[/B]
[B] End With[/B]
Set Me(ComboName).Recordset = SourceRec
SourceRec.Close
Set SourceRec = Nothing
End Sub
Tối hôm qua có Bạn hỏi qua email:
Vì sao trong thủ tục "SetSourceRecForSubForm" (module modQuanlyDulieu) để gán Recordset cho SubForm tôi lại dùng câu lệnh:
Mã:
Set mForm(sForm).Form.Recordset = SQLrec
mà không phải là:
Mã:
mForm(sForm).Form.Recordset = SQLrec
Câu trả lời thật ngắn gọn là: Theo quy ước của VBA:
+ Recordset là 1 Object (các Bạn sử dụng thư viện ADO hay DAO cũng đều như vậy cả)
+ Trong thủ tục nêu trên SQLrec là 1 Recordset
+ Câu lệnh gán giá trị cho 1 biến Object phải tuân theo cú pháp: SET <Biến Object hoặc Property của Object> = Giá trị là 1 Object
Chào các Bạn,
Có một số Bạn gọi điện hỏi tôi vì sao truy xuất chậm quá, không giống như lần đầu sử dụng file minh họa?
Tôi đã kiểm tra lại và thấy tốc độ truy xuất vẫn như trước. Tôi đã cho nạp thử tiện ích VPN ảo thì thấy ứng dụng chạy chậm hẳn, lý do ở đây là khi nạp tiện ích này (và các tiện ích tương tự) máy tính của Bạn thay vì truy xuất trực tiếp đến host đang lưu file dữ liệu cần truy xuất, thì lại đi vòng qua 1 hoặc nhiều host khác nữa, nên bị chậm hẳn. Trong trường hợp này, các Bạn chỉ cần tắt hoặc DisConnect đến VPN ảo đi là nhanh trở lại.
Có Bạn bảo tôi: đã lỡ làm được tới đó rồi sao không tiện thể cho tự động đề nghị đơn giá mỗi khi chọn 1 mặt hàng hoặc chọn lại đơn vị tính?
Thấy nhu cầu này cũng cần để thêm phần sâu sắc cho vấn đề được minh họa nên tôi đã bổ sung nhu cầu trên vào file ứng dụng được cập nhật lúc 13 giờ trưa nay. Bạn nào có nhu cầu xin tải xuống từ link sau: http://www.mediafire.com/?7qesy6y1ec1d50z
Nội dung bổ sung được tôi sử dụng 1 thủ tục tự tạo thay thế cho hàm Dlookup của VBA, thủ tục này có tên là fLookup nằm trong module "modUtilities".
Nội dung thủ tục này như sau:
Mã:
[COLOR=#006400]Function fLookup[/COLOR]([COLOR=#0000cd]WhatField [/COLOR]As String, [COLOR=#0000cd]WhatTable [/COLOR]As String, [COLOR=#0000cd]CriSt [/COLOR]As String)
On Error GoTo xulynull
Dim SrcRec As ADODB.Recordset
Dim srcSt As String
If Len(CriSt) = 0 Then Exit Function
srcSt = "SELECT TOP 1 " & WhatField & " FROM " & GetSchemaTable(WhatTable) & "." & WhatTable
srcSt = srcSt & " WHERE " & CriSt
Set SrcRec = ProcessRecordset(srcSt)
If SrcRec.RecordCount > 0 Then fLookup = Trim(SrcRec(WhatField))
SrcRec.Close
Set SrcRec = Nothing
Exit Function
xulynull:
If Err > 0 Then fLookup = Null
Exit Function
[COLOR=#006400]End Function[/COLOR]
Để giúp các Bạn có căn cứ đánh giá và tối ưu hoá hiệu quả truy xuất dữ liệu của các thủ tục đang có trong file ứng dụng minh hoạ và các thủ tục do chính các Bạn viết hoặc hiệu chỉnh, tôi đã cho nạp vào file dữ liệu trên SQL SERVER:
+ Trên 12.000 chứng từ phát sinh (trong bảng "tblctunx")
+ Với trên 48.000 chi tiết hàng hoá phát sinh (trong bảng "tblctunxct")
Rất mong các Bạn cùng tham gia trao đổi để chúng ta cùng làm sáng tỏ những vấn đề đang thảo luận trong chuyên đề này.
Theo dõi thấy có nhiều Bạn đọc chuyên đề này, nhưng sao không thấy ý kiến gì trao đổi thêm, làm tôi thấy băn khoăn. Không biết những gì tôi trao đổi có mang đến cho các Bạn điều gì ích lợi không? Có gì chưa đúng hay sai chăng?
Thật tình, tôi cũng chỉ muốn chứng minh rằng Microsoft Access giúp ta được rất nhiều việc, trong đó có những việc mà bấy lâu nay chúng ta tưởng, và cũng có rất nhiều người chê Access cũng tưởng lầm rằng Access chỉ làm được ba cái ứng dụng "lẹt đẹt" mang tính "local" thôi, chứ đụng tới NET là chào thua.