Cấu Hình Vận Chuyển và Yêu Cầu Compiler
Để kích hoạt giao thức vận chuyển gốc (Native Transport) cho CrossSocket, người dùng bắt buộc phải thêm dòng xác định macro vào tệp cấu hình biên dịch `kbmMWConfig.inc` như sau:
<code>{$DEFINE KBMMW\_NATIVETRANSPORT\_SUPPORT}</code>
Những Vấn Đề Với Việc Mở Lại ClientQuery
Khi sử dụng tính năng SmartBind nhằm mục đích tái sử dụng mối liên kết (binding) mà không cần khởi tạo lại từ đầu, vẫn tồn tại những bất cập chưa được khắc phục triệt để. Đặc biệt, kết quả kiểm thử phụ thuộc nhiều vào giá trị của thuộc tính `AutoFieldDefsOnOpen` trong các kịch bản khác nhau giữa Windows và Android:
- Tập hợp trường thiết kế sẵn (mwafoNever): Nếu các trường được định nghĩa tại thời điểm thiết kế và khóa chế độ tự động định nghĩa, hoạt động ổn định trên cả hai nền tảng.
- Thiết lập đơn lần (mwafoOnce): Hợp lệ trên Windows, tuy nhiên trên Android, việc mở lại ClientQuery lần thứ hai sẽ gây ra lỗi nghiêm trọng về ngữ cảnh dữ liệu.
- Thiết lập luôn luôn (mwafoAlways): Không đáng tin cậy, dễ dẫn đến lỗi địa chỉ bộ nhớ hoặc thiếu tập dữ liệu trong các bước gọi lại.
Vấn đề nổi cộm nhất trên môi trường Android là thông báo lỗi 'Field 'FName' has no dataset'. Nguyên nhân gốc rễ nằm ở việc SmartBind cố gắng truy xuất dữ liệu trong khi tập dữ liệu (dataset) đang ở trạng thái đóng hoặc chưa được đồng bộ hóa đúng cách.
Mô Hình Triển Khai Ràng Buộc Dữ Liệu
Dưới đây là ví dụ về hàm cấu hình liên kết đã được tối ưu hóa logic và đổi tên biến số để rõ ràng hơn:
<code>
procedure TDataManager.SetupSmartBoundBindings(AQuery: TkbmMWQuery);
var
BindingSet: TkbmMWBindings;
NavControl: IkmbWMWBinder;
begin
if BindingSet = nil then
begin
// Gán dữ liệu từ lưới lên bảng dữ liệu
NavControl := BindingSet.Bind(AQuery, 'ID', lvDisplay, '#Text1');
BindingSet.Bind(AQuery, 'DisplayName', lvDisplay, '#Text2');
BindingSet.Bind(AQuery, 'RecordCount', lvDisplay, '#Text3');
BindingSet.Bind(AQuery, '@', lvDisplay, '@', [mwboTwoWay]);
end;
// Cập nhật giao diện
NavControl.Navigator.Refresh;
end;
</code>
Thông thường, việc đóng/mở lại tập dữ liệu (`q.Close; q.Open`) kèm theo việc gọi lại thủ tục liên kết sẽ dẫn đến lỗi do trạng thái đồng bộ bị gián đoạn. Một giải pháp tạm thời là vô hiệu hóa sự kiện cập nhật tự động trước khi mở, và kích hoạt lại sau khi đã mở xong. Tuy nhiên, cách tiếp cận này tạo ra sự phụ thuộc chặt chẽ giữa thành phần giao diện và module dữ liệu.
Cải Tiến Qua Thuộc Tính Lifecycle
Để giải quyết triệt để lỗi thiếu ngữ cảnh dữ liệu trên Android mà không ảnh hưởng đến kiến trúc chung, phương pháp tối ưu nhất là điều chỉnh thuộc tính `LifeCycles` của mảng Fields. Dưới đây là quy trình khởi tạo lại an toàn:
<code> procedure TDataManager.ResetAndRefresh(APort: Word); begin kbmMWPool.Active := False; kbmMWTrans.Host := EditHost.Text; kbmMWTrans.Port := StrToIntDef(EditPort.Text, 80); q.AutoFieldDefsOnOpen := mwafoOnce; // Quan trọng: Thiết lập lifecycle để giữ trường trường // Phải thực hiện khi Dataset đang mở hoặc ngay trước khi mở q.Fields.LifeCycles := [lcPersistent]; q.SQL.Text := 'SELECT * FROM table_main'; q.Open; SetupSmartBoundBindings(q); end; </code>
Cơ chế hoạt động:
Ký hiệu [lcPersistent] yêu cầu hệ thống giữ lại các đối tượng TField trong vòng đời của ứng dụng thay vì hủy bỏ chúng khi gọi `Close`. Điều này tương đương với việc bạn đã định nghĩa các trường đó một cách tĩnh (persistent) mặc dù bạn dùng chế độ định nghĩa động. Do đó, khi mở lại, hệ thống tìm thấy cấu trúc trường còn tồn tại nên không sinh ra lỗi thiếu dữ liệu.
Khắc Phục Lỗi Chế Độ Chỉnh Sửa
Việc gán giá trị trực tiếp thông qua thành phần điều hướng (Navigator) như Navigator.Value['fid']:=1 có thể sinh ra ngoại lệ 'DataSet is not in edit mode'. Để sửa lỗi này, phương thức thiết lập giá trị nội bộ cần kiểm soát trạng thái của tập dữ liệu:
<code>
procedure TkbmMWBindingNav.SetFieldValue(const Name: string; Val: TValue);
var
Fld: TField;
begin
if IsValid then
begin
Fld := FDataset.FindField(Name);
if Fld <> nil then
begin
// Tự động chuyển sang chế độ chỉnh sửa nếu chưa sẵn sàng
if not (FDataset.State in [dsInsert, dsEdit]) then
FDataset.Edit;
Fld.AsVariant := TkbmMWRTTI.ConvertValue(Val, nil);
end;
end;
end;
</code>
Cuối cùng, hãy lưu ý rằng việc gọi lại Navigator.Refresh() hai lần đôi khi là bắt buộc để đảm bảo dữ liệu hiển thị đầy đủ trên giao diện, tránh bị mất nét trong một số tình huống tải dữ liệu trễ.