-
Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
ongke0711 > 17-03-20, 02:02 PM
Kỹ thuật huỷ lưu dữ liệu sau khi nhập liệu cho Form ở dạng Main - SubForm.
-------------------------------------------------------------------------------------------------------------------------------
Có nhiều bạn khi thiết kế Form nhập liệu vướng phải vấn đề là:
- Sau khi nhập liệu xong, sai, không muốn lưu thì làm sao? vì Access đã tự động lưu dữ liệu xuống Table khi bạn di chuyển con trỏ (nhảy) qua dòng khác.
- Khi đã nhập liệu nhiều dòng trong SubForm (dạng Datasheet hoặc Continuous) rồi, giờ muốn huỷ tất cả các dòng vừa nhập thì làm như thế nào?
Ở bài này tôi sẽ nói về Form nhập liệu ở dạng Mainform-SubForm. Nếu Form dạng Single Form thì việc "Undo" dữ liệu vừa nhập đã có nhiều bài hướng dẫn rồi. Các bạn có thể tìm kiếm trên diễn đàn, xem thêm bài của bác XuanThanh.
Link: https://thuthuataccess.com/forum/thread-5350.html
Để bàn tới các thủ thuật Huỷ (Undo) lưu dữ liệu khi nhập liệu thì các bạn phải xét tới việc thiết kế Form theo kiểu nào, dạng Unbound Form hay Bound Form vì cách thức xử lý sẽ khác nhau.
- Bound Form là Form có gắn với Record Source (Table, Query) và các control (Textbox, combo...) cũng có Control Source là tên các Field tron Table, Query tương ứng.
- Unbound Form: là Form không có có Record Source và các control trên Form cũng vậy - không có control source.
1. Bound Form:
Đây là điểm mạnh của Access. Nó hỗ trợ việc di chuyển tới lui các record, lưu , thêm, sửa xoá v.v.. rất nhanh, không tốn nhiều code để thực hiện các tác vụ trên như Unbound Form. Một điểm hay cũng là điểm bất tiện của nó là việc Lưu dữ liệu. Sau khi nhập liệu, chỉ cần di chuyển con trỏ chuột qua dòng khác là dữ liệu lưu ngay xuống Table, không cần phải code cho nút Lưu. Nhưng điểm bất tiện của tính năng này là: khi người dùng không muốn Lưu dữ liệu vừa thay đổi đó thì phải viết code để trả lại dữ liệu ban đầu. Vấn đề này sẽ phức tạp hơn nếu nhập liệu nhiều dòng trong liên tiếp như trong Form dạng Datasheet hoặc Continuous. Khi đó ta không thể chạy code của sự kiên Form_ BeforeUpdate vì Access sẽ bật thông báo hỏi có muốn Lưu hay không mỗi khi qua dòng mới, nhập liệu 1 chục dòng , bấm nút trả lời 1 chục lần. Thường thì sau khi nhập 1 lúc nhiều dòng thì người dùng mới bấm Lưu, khi đó thì dữ liệu của Subform cũng đã lưu xuống table rồi, vậy hồi lại các dòng cũ như thế nào. Các giải pháp cho trường hợp này như sau:
- Dùng Table tạm làm RecordSource cho Subform, khi nào bấm nút Lưu sẽ chạy lệnh "INSERT INTO..." ghi dữ liệu xuống Table. Hoặc
- Xoá các record vừa nhập dựa trên Khoá ngoại (Foreign Key) của dữ liệu vừa thêm vào. Thiết lập Relationship với tuỳ chọn "Cascade Delete Related Record" để khi xoá 1 dòng trong Table Cha thì nó tự động xoá luôn nhiều dòng liên quan trong Table Con thông qua khoá ngoại.
- Dùng DAO Transaction như bài bạn maidinhdan vừa chia sẻ.
2. Unbound Form:
Dùng dạng này sẽ ít tốn code cho việc bẫy lỗi khi Lưu, Huỷ lưu dữ liệu. Nhưng phải tốn code để tải dữ liệu lên Form khi Form khởi chạy (Form_Load). Tôi thì thích kiểu nàyn
Mặc dùng là Unbound Form nhưng khi thiết kế ở dạng có Subform đi kèm thì lại phải kết hợp vừa Unbound cho Main form và Bound cho Subform. Không có cách gì thiết kế nhập liệu nhiều dòng cho Subform ở dạng Unbound cả.
Tuỳ sở thích lập trình mà dev chọn kiểu Unbound hay Bound. Tôi thì thích dùng cách Unbound phối hợp như trên hơn.
Giới thiệu cơ bản với các bạn về các kiểu Form để hiểu tại sao phải có các kỹ thuật xử lý khác nhau khi muốn Huỷ lưu trên Form nhập liệu. Sau đây tôi sẽ lần lượt giới thiệu demo các cách xử lý theo từng kiểu Form.
-----------------------------------------------------------------------------------------------------------------------------------------
Phần 1: Bound Form với thiết kế có Main - Subform
- Không dùng Table tạm cho Subform.
- Dùng kỹ thuật DAO Transaction (bạn maidinhdan vừa giới thiệu cho nó nóng sốt).
Đây là demo nhập liệu Phiếu Nhập/ Xuất cho của một chương trình bán hàng đơn giản. Tôi thiết kế theo quan điểm cá nhân là:
- Có 1 Form danh sách các phiếu nhâp/xuất để có cái nhìn tổng quát các PN/PX đã phát sinh. Đây cũng là Form tìm kiếm luôn.
- Từ Form Danh sách sẽ Xem chi tiết hoặc Sửa các PN/ PX khi double-click vào nó. Có thể thêm mới phiếu từ Form này.
- Các thức tôi code cho form này là:
+ Dùng Bound Form nhưng không gán sẳn Record Source cho Form mà chi khi Load lên mới gán bằng câu lệnh SQL.
+ Khai báo một Workspace. Khi nạp dữ liệu cho Form sẽ bắt đầu transaction: WS.BeginTrans. Khi lưu sẽ WS.CommitTrans. Khi Huỷ sẽ: WS.Rollback. Vì Workspace sẽ có tác động lên toàn bộ Recordset nào đã khai báo khi BeginTrans.
- Chú ý thứ tự code ở các events, nếu sai chút là báo lỗi tùm lùm.
Link demo: https://drive.google.com/open?id=1O0HjQ5...D1ezktg8R5
Code của frmNhap:
Mã PHP:Option Compare Database
Option Explicit
Dim arrArgs() As String
Private Sub cmdClose_Click()
Set WS = Nothing 'Phai dóng WS, neu không Access không thoát hoan toàn.
DoCmd.Close
End Sub
Private Sub cmdHuy_Click()
WS.Rollback
WS.BeginTrans
Me.Refresh 'Tránh báo lõi #name khi click nhieu lan.
End Sub
Private Sub cmdLuu_Click()
WS.CommitTrans
SetFormState False
Me.Recalc 'De AllowEdit có tác dung
'Requery các form liên quan
Select Case arrArgs(2)
Case "frmDSPhieuNhapXuat"
Forms!frmDSPhieuNhapXuat!sfmDSPhieuNX.Requery
Case Else
'do nothing
End Select
End Sub
Private Sub cmdThem_Click()
'Xu ly WS de tranh loi khi them moi.
If Me.cmdLuu.Enabled = True Then
If msgBoxUni("B" & ChrW(7841) & "n có mu" & ChrW(7889) & "n L" & ChrW(432) & "u d" & ChrW(7919) & " li" & ChrW(7879) & "u hi" & ChrW(7879) & "n t" & ChrW(7841) & "i tr" & ChrW(432) & ChrW(7899) & "c khi m" & ChrW(7903) & " phi" & ChrW(7871) & "u m" & ChrW(7899) & "i?", vbQuestion + vbYesNo, "Thông báo") = vbYes Then
WS.CommitTrans 'Phai ket thuc phiên Transaction cu, moi gan rs cho subF duoc.
Else
WS.Rollback
End If
End If
arrArgs(0) = "Add"
NapPhieuNhap
SetFormState True
End Sub
Private Sub Form_Load()
Set WS = DBEngine.Workspaces(0)
If Len(Nz(Me.OpenArgs, "")) > 0 Then
arrArgs = Split(Me.OpenArgs, "|")
Else 'Mo tu Menu
ReDim arrArgs(0 To 2) As String
arrArgs(0) = "Add"
arrArgs(1) = ""
arrArgs(2) = ""
End If
NapPhieuNhap
End Sub
Sub NapPhieuNhap()
Dim db As DAO.Database
'Dim rs As DAO.Recordset, rs2 As DAO.Recordset '-->Khai báo toàn cuc
Dim strSQL As String, strSQL2 As String
Set db = CurrentDb
Select Case arrArgs(0)
Case "Add"
strSQL = "SELECT * FROM tblNhapXuat WHERE 1=2"
strSQL2 = "SELECT 'Xóa' As Xoa,* FROM tblNhapXuat_CT WHERE 1=2"
Case "Edit"
strSQL = "SELECT * FROM tblNhapXuat WHERE ID=" & arrArgs(1)
strSQL2 = "SELECT 'Xóa' As Xoa, * FROM tblNhapXuat_CT WHERE IDPhieu=" & arrArgs(1)
End Select
Set rs = Nothing 'Tránh lõi khi gán rs moi, khi chon dòng khác - Sua
Set rs = db.OpenRecordset(strSQL, dbOpenDynaset)
Set Me.Recordset = rs
Set rs2 = Nothing
Set rs2 = db.OpenRecordset(strSQL2, dbOpenDynaset)
If rs2.BOF And rs2.EOF Then
'Bãy lôi khi mo recordset thêm moi. Chua có du lieu de MoveLast.
Else
rs2.MoveLast 'Phai activate rs2 de lay toàn bo record gán vào SubF, neu không se treo ung dung.
End If
Set Me.sfmNhapXuatCT.Form.Recordset = rs2
WS.BeginTrans
End Sub
Sub SetFormState(blnState As Boolean)
Me.cmdClose.SetFocus
Me.cmdLuu.Enabled = blnState
Me.cmdHuy.Enabled = blnState
Me.cmdThem.Enabled = blnState
Me.AllowEdits = blnState
Me.cmdThem.Enabled = Not blnState
Me.sfmNhapXuatCT.Locked = Not blnState
End Sub
----------------------------------------------------------------------------------------------------------------------------
Bài kế tiếp sẽ làm demo kỹ thuật khác cho UnBound Form với thiết kế có Main - Subform -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
dotrung > 23-03-20, 06:37 PM
Đang hóng kỹ thuật UnBound Form với thiết kế có Main - Subform của anh Ongke, đặc biệt trên class modules -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
ongke0711 > 01-04-20, 01:35 AM
-----------------------------------------------------------------------------------------
Phần 2: Unbound Form với thiết kế kiểu Main - Subform
- Dùng table tạm cho Subform. Như đã nói ở bài trước Subform dạng Datasheet hoặc Continuous phải ở dạng Bound Form mới hiển thị nhiều dòng được.
- Đây là kiểu kết hợp Unbound (MainF) với Bound Form (SubF).
Cách thức xử lý của Form này:
- Khi Form được mở lên để Sửa một phiếu Nhâp/ xuất nào đó sẽ dùng hàm để lọc, lấy ra record từ table chính rồi gán lên các Unbound textbox trên MainForm. Song song đó sẽ load bộ record cần sửa vào table tạm làm nguồn cho Subform.
- Vì là Unbound Form và gắn với table tạm nên sau khi nhập liệu, nếu không muốn lưu thì chỉ cần đóng form là xong, không cần phải Undo dữ liệu đã nhập. Ngược lại thì muốn Lưu bạn phải dùng code để ghi toàn bộ dữ liệu trên Form vào các table chính.
Trong file demo này tôi có kèm theo các kỹ thuật như:
- Tham số OpenArgs: dùng để truyền các tham số từ Form trước qua Form sau theo yêu cầu.
- Hàm kiểm tra các Textbox có rỗng hay không trước khi lưu dữ liệu. Hàm duyệt qua tất cả các control trên form để kiểm tra chứ không cần phải viết từng dòng code kiểm tra cho từng Textbox như: If IsNull(txtNgay) Then ....
- Hàm tải record lên Unbound Form, cập nhật, thêm mới. Tận dụng thuộc tính Tag của Form property phục vụ cho code.
- Các bẫy lỗi khi form không có dữ liệu, bẫy lỗi để form hoạt động trơn tru.
Hình minh hoạ bẫy lỗi các Textbox buộc phải có dữ liệu, không được để trống.
Đây là bộ code cho các hàm lấy dữ liệu, cập nhât, thêm mới dữ liệu cho Unbound Form. Copy vào Module.
Mã PHP:Option Compare Database
Option Explicit
Function GetRecord(rs As DAO.Recordset, frm As Form)
On Error GoTo ErrHandler
Dim fld As Field
Dim ctl As control
For Each fld In rs.Fields
For Each ctl In frm.Controls
If ctl.Name = "txt" & fld.Name Or ctl.Name = "cbo" & fld.Name Or ctl.Name = "chk" & fld.Name Or ctl.Name = "fra" & fld.Name Then
ctl = rs.Fields("" & fld.Name & "").Value
ElseIf ctl.Name = "img" & fld.Name Then
If Not IsNull(rs.Fields("" & fld.Name & "").Value) Then
On Error Resume Next
ctl.Picture = CStr(rs.Fields("" & fld.Name & "").Value)
End If
End If
Next
Next
'rs.Close '-> Không close, de tái su dung cho hàm Update/Add trong form
'Set rs = Nothing'
Exit Function
Exit_ErrHandler:
On Error Resume Next
rs.Close
Set rs = Nothing
Exit Function
ErrHandler:
msgBoxUni "Mã l" & ChrW(7895) & "i: " & Err.Number & vbCrLf _
& "N" & ChrW(7897) & "i dung l" & ChrW(7895) & "i: " & Err.Description, vbCritical, "Get Record Error"
Resume Exit_ErrHandler
End Function
Function AddRecord(rs As DAO.Recordset, frm As Form)
On Error GoTo ErrHandler
Dim fld As Field
Dim ctl As control
rs.AddNew
For Each fld In rs.Fields
For Each ctl In frm.Controls
If ctl.Name = "txt" & fld.Name Or ctl.Name = "cbo" & fld.Name Or ctl.Name = "chk" & fld.Name Then
rs.Fields("" & fld.Name & "").Value = ctl
End If
Next
Next
rs.Update
'rs.Close
'Set rs = Nothing
Exit Function
Exit_ErrHandler:
On Error Resume Next
rs.Close
Set rs = Nothing
Exit Function
ErrHandler:
msgBoxUni "L" & ChrW(432) & "u d" & ChrW(7919) & " li" & ChrW(7879) & "u th" & ChrW(7845) & "t b" & ChrW(7841) & "i." & vbCrLf & vbCrLf _
& "Mã l" & ChrW(7895) & "i: " & Err.Number & vbCrLf _
& "N" & ChrW(7897) & "i dung l" & ChrW(7895) & "i: " & Err.Description, vbCritical, "Add Record Error"
Resume Exit_ErrHandler
End Function
Function UpdateRecord(rs As DAO.Recordset, frm As Form)
On Error GoTo ErrHandler
Dim fld As Field
Dim ctl As control
rs.Edit
For Each fld In rs.Fields
For Each ctl In frm.Controls
If ctl.Tag Like "no" Then
'do nothing'
Else
If ctl.Name = "txt" & fld.Name Or ctl.Name = "cbo" & fld.Name Or ctl.Name = "chk" & fld.Name Then
rs.Fields("" & fld.Name & "").Value = ctl
End If
End If
Next ctl
Next fld
rs.Update
'fMsgBox getMes(26), vbInformation, getTit(1)'
'rs.Close
'Set rs = Nothing
Exit Function
Exit_ErrHandler:
On Error Resume Next
rs.Close
Set rs = Nothing
Exit Function
ErrHandler:
msgBoxUni "L" & ChrW(432) & "u d" & ChrW(7919) & " li" & ChrW(7879) & "u th" & ChrW(7845) & "t b" & ChrW(7841) & "i." & vbCrLf & vbCrLf _
& "Mã l" & ChrW(7895) & "i: " & Err.Number & vbCrLf _
& "N" & ChrW(7897) & "i dung l" & ChrW(7895) & "i: " & Err.Description, vbCritical, "Update Record Error"
Resume Exit_ErrHandler
End Function
Public Sub ClearRecord(frm As Form)
Dim ctl As control
For Each ctl In frm.Controls
If ctl.Tag Like "no" Then
'do nothing'
Else
Select Case ctl.ControlType
Case acTextBox, acComboBox
ctl.Value = ""
Case acCheckBox
ctl.Value = False
Case acImage
ctl.Picture = ""
Case Else
'do nothing'
End Select
End If
Next
End Sub
>> Để dùng các hàm này lấy dữ liệu lên Form thì các Control (Textbox, combo, checkbox...) phải tuân theo qui tắc đặt tên là: "txt" + tên Field dùng trong table. Tiền tố là 3 ký tự: "txt" - cho Textbox; "cbo" - cho ComboBox; "chk" - cho CheckBox...
Sau đây là link file demo. Các bạn download về từ từ ngâm cứu.
Link: https://drive.google.com/open?id=1wxpmOa...-oigvGFrLk
------------------------------------------------------------------------------------------------------------------------------
Bài tiếp sẽ nói về cách: Dùng OpenArgs để truyền tham số từ Form này qua Form khác. -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
dotrung > 01-04-20, 10:29 PM
anh Bảo quả thật cao thủ, trước giờ em không biết phần nạp tham số unbound, cảm ơn anh Ongke0711 rất nhiều. -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
huuduy.duy > 16-04-20, 04:10 PM
(01-04-20, 01:35 AM)ongke0711 Đã viết: -----------------------------------------------------------------------------------------
Phần 2: Unbound Form với thiết kế kiểu Main - Subform
Do yêu cầu công việc phải chọn nhiều nhân viên từ Danh sách nhân viên chung, nên em thay Subform thành Listbox để chọn nhân viên qua lại giữ 2 Listbox.
Phần Form Phiếu Yêu cầu đào tạo: sẽ cập nhật thông tin như:
1/ Người yêu cầu, nội dung yêu cầu, Thời gian đào tạo, Nơi đào tạo, .. cập nhật vào bảng tblPhieuYeuCau
2/ Mỗi phiếu Yêu cầu như vậy sẽ chọn ra danh sách nhân viên từ 1 danh sách nhân viên chung của Cty cần đào tạo theo nội dung đó và ghi bảng tblNhanVienDaDaoTao.
Em lầm đến đoạn gọi Phiếu Yêu cầu lên để bổ sung thêm nhân viên cần đào tạo thì bị lỗi. Nhờ anh xem giúp
Trân trọng cảm ơn
Link
** Ngoài cách sử dụng Listbox, thì mình có thể sủ dụng Subform có checkbox để check những nhân viên cần chọn được không anh
[img]http://[/img]
-
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
Xuân Thanh > 16-04-20, 10:15 PM
1/ Gọi hết danh sách nhân viên đưa vào list bên phải
2/ Cái list bên trái chỉ làm list tạm. Sau khi đưa hết danh sách đã chọn từ list bên phải qua thì mới cập nhật vào tblPhieuYeuCau -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
huuduy.duy > 16-04-20, 11:27 PM
(16-04-20, 10:15 PM)Xuân Thanh Đã viết: 1/ Gọi hết danh sách nhân viên đưa vào list bên phải
Cái này được rồi anh.
2/ Cái list bên trái chỉ làm list tạm. Sau khi đưa hết danh sách đã chọn từ list bên phải qua thì mới cập nhật vào tblPhieuYeuCau
Chỉ còn 1 cái là gọi lại cái đã nhập để bổ sung thêm nhân viên từ bên phải vào bên trái ạ -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
huuduy.duy > 16-04-20, 11:39 PM
(16-04-20, 11:30 PM)Xuân Thanh Đã viết:
(16-04-20, 11:27 PM)huuduy.duy Đã viết: Cái này được rồi anh.
Chỉ còn 1 cái là gọi lại cái đã nhập để bổ sung thêm nhân viên từ bên phải vào bên trái ạ
Thì lôi ngược cái table PYC đó ra dễ ẹc mà
Dạ, đối với anh thì dễ, nhưng đối với em, em lôi nó ra được rồi, nhưng bổ sung thêm thì bị lỗi. Nhờ anh giúp ạ -
RE: Kỹ thuật không cho lưu dữ liệu (Undo) ở Subform dạng Datasheet
ongke0711 > 17-04-20, 12:31 AM
(16-04-20, 04:10 PM)huuduy.duy Đã viết: Do yêu cầu công việc phải chọn nhiều nhân viên từ Danh sách nhân viên chung, nên em thay Subform thành Listbox để chọn nhân viên qua lại giữ 2 Listbox.
Phần Form Phiếu Yêu cầu đào tạo: sẽ cập nhật thông tin như:
1/ Người yêu cầu, nội dung yêu cầu, Thời gian đào tạo, Nơi đào tạo, .. cập nhật vào bảng tblPhieuYeuCau
2/ Mỗi phiếu Yêu cầu như vậy sẽ chọn ra danh sách nhân viên từ 1 danh sách nhân viên chung của Cty cần đào tạo theo nội dung đó và ghi bảng tblNhanVienDaDaoTao.
Em lầm đến đoạn gọi Phiếu Yêu cầu lên để bổ sung thêm nhân viên cần đào tạo thì bị lỗi. Nhờ anh xem giúp
Trân trọng cảm ơn
Link
** Ngoài cách sử dụng Listbox, thì mình có thể sủ dụng Subform có checkbox để check những nhân viên cần chọn được không anh
Cái Form đặc thù của em áp dụng cách làm như file demo của anh cũng không khác biệt gì nhiều ở qui trình xử lý, chỉ là khác nhau trong phần nạp dữ liệu thôi.
Khi bấm nút [Sửa], sẽ nạp thông tin của PYC đó vào frmNhapPYC:
- Nạp tblNhanVien vào tblNhanVienTemp.
- Select query từ tblNhanVienDaDaoTao để lấy ra số nhân viên đã đăng ký theo số PYC đang gọi (fldFlag = True).
- Dùng vòng lặp Recordset để cập nhật fldFlag=True của các nhân viên trong tblNhanVienTemp.
- Xong.
Ngoại trừ cách dùng vòng lặp Recordset, có thể dùng hàm gộp các MSNV thành chuỗi rồi dùng câu lệnh SQL "UPDATE ... SET...", có thể tốc độ xử lý sẽ nhanh hơn. Em tự test thử đi nhé.
VD: UPDATE tblNhanVienTemp SET fldFlag = True WHERE MSNV IN ('S00234','S00695','S01234')
Anh thấy em tận dụng thuộc tính Tag của control để lưu câu lệnh SQL cũng sáng tạo đó. Lưu ý là chỉ có thể lưu tối đa 2.048 ký tự (byte) thôi nhé.
Lưu ý: anh có ghi chú trong demo ở trên là phải chú ý đặt tên (name) của textbox trên Form phải trùng với tên Field trong Table cần lấy dữ liệu thì các hàm GetRecord(), UpdateRecord() mới chạy đúng.
Link file đã sửa: http://www.mediafire.com/file/khx6yy982u...accdb/file
Còn về việc dùng SubForm có checkbox chọn nhân viên thì cũng gần tương tự cách xử lý trên. Chú ý là có thêm comboBox chọn Phòng ban để giới hạn việc phải kéo lên xuống danh sách nhân viên quá dài. (Giống như cái ListBox bên phải Form em đang làm).