亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb

首頁 > 學院 > 開發設計 > 正文

多層數據庫開發十三:剖析幾個數據庫應用程序

2019-11-18 18:10:55
字體:
來源:轉載
供稿:網友
  第十三章 剖析幾個數據庫應用程序
  前面已經詳細講述了Delphi 4的數據庫編程技術。為了使讀者能夠透徹地理解有關編程技術并靈活運用,我們把Delphi 4的幾個示范程序拿出來加以剖析,這些示范程序都編得非常有技巧。要說明的是,剖析程序時我們可能會忽略掉一些與主題無關的細節。
13.1 一個后臺查詢的示范程序
  這一節詳細剖析一個后臺查詢的示范程序,項目名稱叫Bkquery,它可以在C:/PRogram Files/Borland/Delphi4/Demos/Db/Bkquery目錄中找到。它的主窗體如圖13.1所示。
  圖13.1 Bkquery的主窗體
  我們先從處理窗體的OnCreate事件的句柄開始,因為它是應用程序的起點。Procedure TAdhocForm. FormCreate(Sender: TObject);
Procedure CreateInitialIni;
  Const 
  VeryInefficientName = 'IB:
  Very Inefficient Query';
  VeryInefficientQuery ='select EMP_NO, Avg(Salary) as Salary/n'+' from employee, employee, employee/n' +'
 group by EMP_NO';
  AmountDueName = 'DB: Amount Due By Customer';
  AmountDueByCustomer ='select Company, Sum(ItemsTotal) - Sum(AmountPaid) as AmountDue/n' +'
  from customer, orders/n' +'
  where Customer.CustNo = Orders.CustNo/n' + ' group by Company';
  Begin
  With SavedQueries Do
   Begin
    WriteString(VeryInefficientName, 'Query', VeryInefficientQuery);
    WriteString(VeryInefficientName, 'Alias', 'IBLOCAL');
    WriteString(VeryInefficientName, 'Name', 'SYSDBA');
   SavedQueryCombo.Items.Add(VeryInefficientName);
   WriteString(AmountDueName, 'Query',  AmountDueByCustomer);
    WriteString(AmountDueName, 'Alias', 'DBDEMOS');
    WriteString(AmountDueName, 'Name', '');
    SavedQueryCombo.Items.Add(AmountDueName);
   End;
  End;
Begin
  session.GetAliasNames(AliasCombo.Items);
  SavedQueries := TIniFile.Create('BKQUERY.INI');
  SavedQueries.ReadSections(SavedQueryCombo.Items);
  If SavedQueryCombo.Items.Count <= 0 then CreateInitialIni;
   SavedQueryCombo.ItemIndex := 0;
   QueryName := SavedQueryCombo.Items[0];
   Unmodify;ReadQuery;
End;
  FormCreate主要做了這么幾件事情:首先,它調用TSession的GetAliasNames函數把所有已定義的BDE別名放到一個字符串列表中,實際上就是填充圖13.1中的“Database Alias”框。接著,創建了一個TIniFile類型的對象實例,并指定文件名是BKQUERY.INI。如果這個文件現在還不存在的話,就需要調用CreateInitialIni去創建一個文件。至于怎樣寫.INI文件,這不是本章要討論的主題。最后,調用ReadQuery把文件中保存的有關參數讀出來。
  ReadQuery函數是這樣定義的:
Procedure TAdhocForm.ReadQuery;
Begin
If not CheckModified then Exit;
With SavedQueries Do
Begin
QueryName := SavedQueryCombo.Items[SavedQueryCombo.ItemIndex];
QueryEdit.Text := IniStrToStr(ReadString(QueryName, 'Query', ''));
AliasCombo.Text := ReadString(QueryName, 'Alias', '');
NameEdit.Text := ReadString(QueryName, 'Name', '');
End;
Unmodify;
If Showing thenIf NameEdit.Text <> '' then PassWordEdit.SetFocus else
QueryEdit.SetFocus;
End;
  當用戶單擊“Execute”按鈕,程序就調用BackgroundQuery在后臺執行查詢。Procedure TAdhocForm.ExecuteBtnClick(Sender: TObject);
Begin
BackgroundQuery(QueryName, AliasCombo.Text, NameEdit.Text, PasswordEdit.Text,QueryEdit.Text);
BringToFront;
End;
  BackgroundQuery是在另一個叫ResItFrm的單元中定義的,后面將重點介紹這個過程。當用戶單擊“New”按鈕,程序就把窗體上的一些窗口重新初始化。
Procedure TAdhocForm.NewBtnClick(Sender: TObject);
Function UniqueName: string;
var
I: Integer;
Begin
I := 1;
Repeat
Result := Format('Query%d', [I]);
Until
SavedQueryCombo.Items.IndexOf(Result) < 0;
End;
Begin
AliasCombo.Text := 'DBDEMOS';
NameEdit.Text := '';
PasswordEdit.Text := '';
QueryEdit.Text := '';QueryEdit.SetFocus;
QueryName := UniqueName;
SavedQueryCombo.ItemIndex := -1;
Unnamed := True;
End;
  當用戶單擊“Save”按鈕,程序就調用SaveQuery函數把當前有關參數保存到.INI文件中。
Procedure TAdhocForm.SaveBtnClick(Sender: TObject);
Begin
SaveQuery;
End;
  而SaveQuery是這樣定義的:
Procedure TAdhocForm.SaveQuery;
Begin
If Unnamed then SaveQueryAs
Else
With SavedQueries Do
Begin
WriteString(QueryName, 'Query', StrToIniStr(QueryEdit.Text));
WriteString(QueryName, 'Alias', AliasCombo.Text);
WriteString(QueryName, 'Name', NameEdit.Text);Unmodify;
End;
End;
  當用戶單擊“Save As”按鈕,程序調用SaveQueryAs函數以另一個名稱保存有關參數。
Procedure TAdhocForm.SaveAsBtnClick(Sender: TObject);
Begin
SaveQueryAs;
End;
  而SaveQueryAs是這樣定義的:
Procedure TAdhocForm.SaveQueryAs;
Begin
If GetNewName(QueryName) then
Begin
Unnamed := False;
SaveQuery;
With SavedQueryCombo, Items Do
Begin
If IndexOf(QueryName) < 0 then Add(QueryName);
ItemIndex := IndexOf(QueryName);
End;
End;
End;
  其中,GetNewName是在一個叫SaveQAs的單元中定義的,它將打開如圖13.2所示的對話框,讓用戶輸入一個文件名。圖13.2 指定另一個文件名此外,程序還處理了SavedQueryCombo框的OnChange事件:
Procedure TAdhocForm.SavedQueryComboChange(Sender: TObject);
Begin
ReadQuery;
End;
  所謂后臺查詢,實際上是運用多線程的編程技術,使查詢在一個專門的線程中進行。為此,首先要以TThread為基類聲明一個線程對象:
TypeTQueryThread = Class(TThread)PrivateQueryForm: TQueryForm;
MessageText: string;
Procedure ConnectQuery;
Procedure DisplayMessage;
ProtectedProcedure Execute;
override;
PublicConstructor Create(AQueryForm: TQueryForm);
End;
  我們先看線程對象是怎樣創建的:
Constructor TQueryThread.Create(AQueryForm: TQueryForm);
Begin
QueryForm := AQueryForm;
FreeOnTerminate := True;
Inherited Create(False);
End;
  當用戶單擊“Execute”按鈕,程序就調用BackgroundQuery函數在后臺執行查詢。BackgroundQuery是這樣定義的:
Procedure BackgroundQuery(const QueryName, Alias, User, Password, QueryText: string);
var
QueryForm: TQueryForm;
Begin
QueryForm := TQueryForm.Create(application);
With QueryForm, Database Do
Begin
Caption := QueryName;
QueryLabel.Caption := QueryText;
Show;
AliasName := Alias;
Params.Values['USER'] := User;
Params.Values['PASSWORD'] := Password;
Query.Sql.Text := QueryText;
End;
TQueryThread.Create(QueryForm);
End;
  BackgroundQuery主要做了三件事情,一是動態創建和顯示一個窗體(TQueryForm),因為要用這個窗體顯示查詢結果。二是把傳遞過來的參數分別賦給TDadabase構件的AliasName、Params以及TQuery構件的SQL屬性。三是創建線程對象的實例。由于線程對象的FreeOnTerminate屬性設為True,所以用不著專門去刪除線程對象。
  好,現在讓我們看看這個程序最關鍵的代碼,即線程對象的Execute函數:
Procedure TQueryThread.Execute;
varUniqueNumber: Integer;
Begin
Try
With QueryForm Do
Begin
UniqueNumber := GetUniqueNumber;
Session.SessionName := Format('%s%x', [Session.Name, UniqueNumber]);
Database.SessionName := Session.SessionName;
Database.DatabaseName:=Format('%s%x',[Database.Name,UniqueNumber]);
Query.SessionName := Database.SessionName;
Query.DatabaseName := Database.DatabaseName;
Query.Open;
Synchronize(ConnectQuery);MessageText := 'Query openned';
Synchronize(DisplayMessage);
End;
Except
On E: Exception Do
Begin
MessageText := Format('%s: %s.', [E.ClassName, E.Message]);
Synchronize(DisplayMessage);
End;
End;
End;
  由于這是個多線程的數據庫應用程序,因此,需要顯式地使用TSession構件,而且要保證每個線程所使用的BDE會話期對象是唯一的。所以,程序首先調用GetUniqueNumber來獲得一個唯一的序號。同樣,對于TDatabase構件來說,也有類似的問題。
  Execute通過Synchronize讓主線程去執行ConnectQuery、DisplayMessage等方法,這是因為ConnectQuery、DisplayMessage都需要與VCL打交道,必須用Synchronize作外套。
13.2 一個緩存更新的示范程序
  這一節詳細剖析一個緩存更新的示范程序,項目名稱叫Cache,它可以在C:/Program Files/Borland/Delphi4/Demos/Db/Cacheup目錄中找到。它的主窗體如圖13.3所示。
  圖13.3 Cache的主窗體
  主窗體上有一個“Cached Updates”復選框,如果選中此復選框,表示使用緩存更新技術。否則,表示不使用緩存更新技術,當用戶修改了數據后,數據被直接寫到數據集中。
  主窗體上還有一個“Use Update SQL”復選框,如果選中這個復選框,表示使用TUpdateSQL構件來進行緩存更新。
  當用戶單擊“Apply Updates”按鈕,就向數據庫申請更新數據。
  當用戶單擊“Cancel Updates”按鈕,所有未決的修改將被取消。
  當用戶單擊“Revert Record”按鈕,對當前記錄所作的修改將被取消。
  在“Show Records”分組框內有幾個復選框,用于選擇要在柵格中顯示哪些記錄,包括未修改的記錄、修改的記錄、插入的記錄和刪除的記錄。
  當用戶單擊“Re-Execute Query”按鈕,就重新執行查詢。此外,這個示范程序還用一個計算字段來表達當前的更新狀態。
  下面我們就來看看怎樣實現上述功能。在介紹程序代碼之前,我們先要介紹數據模塊CacheData,因為幾個關鍵的構件都是放在這個數據模塊上,如圖13.4所示。
  圖13.4 數據模塊
  數據模塊上有四個構件,分別是:一個TDataSource構件,其名為CacheDS,一個TDatabase構件名為CacheDB,一個TQuery構件名為CacheQuery,一個TUpdateSQL構件名為UpdateSQL。
  TQuery構件的OnCalcFields事件是這樣處理的:
Procedure TCacheData.CacheQueryCalcFields(DataSet: TDataSet);
ConstUpdateStatusStr: array[TUpdateStatus] of string = ('Unmodified', 'Modified','Inserted', 'Deleted');
Begin
If CacheQuery.CachedUpdates then
  CacheQueryUpdateStatus.Value := UpdateStatusStr[CacheQuery.UpdateStatus];
End;
  上述代碼用于給計算字段CacheQueryUpdateStatus賦值,以顯示當前的更新狀態。TQuery構件的OnUpdateError事件是這樣處理的:
Procedure TCacheData.UpdateErrorHandler(DataSet: TDataSet; E: EDatabaseError;
UpdateKind:TUpdateKind;
var UpdateAction: TUpdateAction);
Begin
UpdateAction := UpdateErrorForm.HandleError(DataSet, E, UpdateKind);
End;
  現在我們回到主窗體,從處理主窗體的OnCreate事件的句柄開始。
Procedure TCacheDemoForm. FormCreate(Sender: TObject);
Begin
FDataSet := CacheData.CacheDS.DataSet as TDBDataSet;
FDataSet.CachedUpdates := CachedUpdates.Checked;
SetControlStates(FDataSet.CachedUpdates);
FDataSet.Open;
End;
  第一行代碼從TDataSource構件的DataSet屬性取出當前的數據集,第二行代碼是根據復選框CachedUpdates來決定數據集的CachedUpdates屬性,進而再調用SetControlStates函數設置窗體上有關控件的狀態,最后調用Open執行查詢。SetControlStates是這樣定義的:
Procedure TCacheDemoForm.SetControlStates(Enabled: Boolean);
Begin
ApplyUpdatesBtn.Enabled := True;
CancelUpdatesBtn.Enabled := True;
RevertRecordBtn.Enabled := True;
UnmodifiedCB.Enabled := True;
ModifiedCB.Enabled := True;
InsertedCB.Enabled := True;
DeletedCB.Enabled := True;
UseUpdateSQL.Enabled := True;
End;
  下面是處理一些控件的事件。首先是復選框CachedUpdates的OnClick事件:
Procedure TCacheDemoForm.ToggleUpdateMode(Sender: TObject);
Begin
FDataSet.CachedUpdates := not FDataSet.CachedUpdates;
SetControlStates(FDataSet.CachedUpdates);
End;
  復選框UseUpdateSQL的OnClick事件是這樣處理的:
Procedure TCacheDemoForm.UseUpdateSQLClick(Sender: TObject);
Begin
FDataSet.Close;
If UseUpdateSQL.Checked then
  FDataSet.UpdateObject := CacheData.UpdateSQLElseFDataSet.UpdateObject := nil;
  FDataSet.Open;
End;
  當用戶單擊“Apply Updates”按鈕,就向數據庫申請更新數據。
Procedure TCacheDemoForm.ApplyUpdatesBtnClick(Sender: TObject);
Begin
FDataSet.Database.ApplyUpdates([FDataSet]);
End;
  當用戶單擊“Cancel Updates”按鈕,所有未決的修改將被取消。
Procedure TCacheDemoForm.CancelUpdatesBtnClick(Sender: TObject);
Begin
FDataSet.CancelUpdates;
End;
  當用戶單擊“Revert Record”按鈕,對當前記錄所作的修改將被取消。
Procedure TCacheDemoForm.RevertRecordBtnClick(Sender: TObject);
Begin
FDataSet.RevertRecord;
End;
  在“Show Records”分組框內的幾個復選框,它們的OnClick事件是這樣處理的:
Procedure TCacheDemoForm.UpdateRecordsToShow(Sender: TObject);varUpdRecTypes : TUpdateRecordTypes;
Begin
UpdRecTypes := [];
If UnModifiedCB.Checked then
  Include(UpdRecTypes, rtUnModified);
If ModifiedCB.Checked then Include(UpdRecTypes, rtModified);
If InsertedCB.Checked then Include(UpdRecTypes, rtInserted);
If DeletedCB.Checked thenInclude(UpdRecTypes, rtDeleted);
FDataSet.UpdateRecordTypes := UpdRecTypes;
End;
  UpdateRecordsToShow 函數首先聲明了一個TUpdateRecordTypes類型的變量UpdRecTypes,并把它初始化為空的集合。然后依次判斷四個復選框是否選中,如選中的話,就把對應的元素包含到這個集合中,作為數據集的UpdateRecordTypes屬性。
  當用戶單擊“Re-Execute Query”按鈕,就重新執行查詢。
Procedure TCacheDemoForm.ReExecuteButtonClick(Sender: TObject);
Begin
FDataSet.Close;
FDataSet.Open;
End;
  此外,在主窗體上,還有一個菜單命令叫About,此命令將調用ShowAboutDialog打開一個對話框。
  ShowAboutDialog是這樣定義的:
Procedure ShowAboutDialog;
Begin
With TAboutDialog.Create(Application) Do
Try
AboutMemo.Lines.LoadFromFile(ExtractFilePath(ParamStr(0))+'ABOUT.TXT');
ShowModal;
FinallyFree;
End;
End;
13.3 一個Client/Server示范程序
   這一節詳細剖析一個Client/Server示范程序,項目名稱叫Csdemos,它可以在C:/Program Files/Borland/Delphi4/Demos/Db/Csdemos目錄中找到。其主窗體如圖13.5所示。
   圖13.5 Csdemos的主窗體
  當用戶單擊“Show a View in action”按鈕時,就打開FrmViewDemo窗口。
Procedure TFrmLauncher.BtnViewsClick(Sender: TObject);
Begin
FrmViewDemo.ShowModal;
End;
  當用戶單擊“Salary Change Trigger Demo”按鈕時,就打開FrmTriggerDemo窗口。
Procedure TFrmLauncher.BtnTriggClick(Sender: TObject);
Begin
FrmTriggerDemo.ShowModal;
End;
  當用戶單擊“Query Stored Procedure Demo”按鈕時,就打開FrmQueryProc窗口。
Procedure TFrmLauncher.BtnQrySPClick(Sender: TObject);
Begin
FrmQueryProc.ShowModal;
End;
  當用戶單擊“Executable Stored Procedure Demo”按鈕時,就打開FrmExecProc窗口。
Procedure TFrmLauncher.BtnExecSPClick(Sender: TObject);
Begin
FrmExecProc.ShowModal;
End;
  當用戶單擊“Transaction Editing Demo”按鈕時,就打開FrmTransDemo窗口。
Procedure TFrmLauncher.BtnTransClick(Sender: TObject);
Begin
FrmTransDemo.ShowModal;
End;
  下面我們詳細介紹這些窗口。FrmViewDemo窗口如圖13.6所示。
   圖13.6 FrmViewDemo窗口
   當這個窗口彈出時,首先調用TTable構件的Open函數打開數據集。
Procedure TFrmViewDemo.FormShow(Sender: TObject);
Begin
VaryingTable.Open;
End;
  程序用兩個快捷按鈕來切換表格名稱,其中,左邊一個按鈕對應于EMPLOYEE表。
Procedure TFrmViewDemo.BtnShowEmployeeClick(Sender: TObject);
Begin
ShowTable('EMPLOYEE');
End;
  右邊一個按鈕對應于PHONE_LIST表。
Procedure TFrmViewDemo.BtnShowPhoneListClick(Sender: TObject);
Begin
ShowTable('PHONE_LIST');
End;
  ShowTable是這樣定義的:
Procedure TFrmViewDemo.ShowTable( ATable: string );
Begin
Screen.Cursor := crHourglass;
VaryingTable.DisableControls;
VaryingTable.Active := FALSE;
VaryingTable.TableName := ATable;
VaryingTable.Open;
VaryingTable.EnableControls;
Screen.Cursor := crDefault;
End;
  FrmTriggerDemo窗口如圖13.7所示:
  圖13.7 FrmTriggerDemo窗口
  當這個窗口彈出時,首先調用兩個TTable構件的Open打開數據集。
Procedure TFrmTriggerDemo.FormShow(Sender: TObject);
Begin
DmEmployee.EmployeeTable.Open;
DmEmployee.SalaryHistoryTable.Open;
End;
  其中,DmEmployee是數據模塊的名稱。FrmQueryProc窗口如圖13.7所示。
  圖13.7 FrmQueryProc
  當這個窗口彈出時,將觸發OnShow事件。這個事件是這樣處理的:
Procedure TFrmQueryProc.FormShow(Sender: TObject);
Begin
DmEmployee.EmployeeTable.Open;
EmployeeSource.Enabled := True;
With EmployeeProjectsQuery Do
If not Active then Prepare;
End;
  首先調用EmployeeTable的Open打開數據集,然后把數據源EmployeeSource的Enabled屬性設為True,接著調用Prepare準備查詢。
  為了執行查詢,程序處理了數據源EmployeeSource的OnDataChange事件:
Procedure TFrmQueryProc.EmployeeDataChange(Sender: TObject; Field: TField);
Begin
EmployeeProjectsQuery.Close;
EmployeeProjectsQuery.Params[0].AsInteger :=DmEmployee.EmployeeTableEmp_No.Value;
EmployeeProjectsQuery.Open;
WriteMsg('Employee ' + DmEmployee.EmployeeTableEmp_No.AsString +' is assigned to ' + IntToStr(EmployeeProjectsQuery.RecordCount) +' project(s).');
End;
  調用WriteMsg的目的是在狀態欄上顯示一個消息。WriteMsg是這樣定義的:
Procedure TFrmQueryProc.WriteMsg(StrWrite: String);
Begin
StatusBar1.SimpleText := StrWrite;
End;
  最后,當這個窗口暫時隱去時,應當把數據源EmployeeSource的Enabled屬性設為False:
Procedure TFrmQueryProc.FormHide(Sender: TObject);
Begin
EmployeeSource.Enabled := False;
End;
   FrmExecProc窗口如圖13.8所示。
  圖13.8 FrmExecProc
  當這個窗口彈出時,將觸發OnShow事件。這個事件是這樣處理的:
Procedure TFrmExecProc.FormShow(Sender: TObject);
Begin
DmEmployee.SalesTable.Open;
DmEmployee.CustomerTable.Open;
SalesSource.Enabled := True;
End;
  當用戶在柵格中瀏覽記錄時,將觸發SalesSource的OnDataChange事件。在處理這個事件的句柄中,要判斷ORDER_STATUS字段的值是否是SHipPED,如果是,就使“Ship Order”按鈕有效。
Procedure TFrmExecProc.SalesSourceDataChange(Sender: TObject; Field: TField);
Begin
If DmEmployee.SalesTable['ORDER_STATUS'] <> NULL then
  BtnShipOrder.Enabled :=AnsiCompareText(DmEmployee.SalesTable['ORDER_STATUS'],'SHIPPED')<>0;
End;
  當用戶單擊“Ship Order”按鈕,就執行存儲過程,存儲過程的參數取自PO_NUMBER字段。
Procedure TFrmExecProc.BtnShipOrderClick(Sender: TObject);
Begin
With DmEmployee Do
Begin
ShipOrderProc.Params[0].AsString := SalesTable['PO_NUMBER'];
ShipOrderProc.ExecProc;
SalesTable.Refresh;
End;
End;
  FrmTransDemo窗口如圖13.9所示。
  這個窗口演示了怎樣處理事務。首先,要調用EmployeeDatabase(TDatabase構件)的StartTransaction開始一次新的事務。此后,對數據庫的所有修改都暫時保留在緩存中,直到程序調用Commit或Rollback。
Procedure TFrmTransDemo.FormShow(Sender: TObject);
Begin
DmEmployee.EmployeeDatabase.StartTransaction;
DmEmployee.EmployeeTable.Open;
End;
  當用戶單擊“Commit Edits”按鈕,就要向服務器提交數據。首先要訪問TDatabase構件的InTransaction屬性,看看當前是否正在處理事務。如果是的話,還要彈出一個對話框,讓用戶確認是否要提交數據。程序代碼如下:
Procedure TFrmTransDemo.BtnCommitEditsClick(Sender: TObject);
Begin
If DmEmployee.EmployeeDatabase.InTransaction and(MessageDlg('Are you sure you want to commit your changes?',mtConfirmation, [mbYes, mbNo], 0) = mrYes) then
Begin
DmEmployee.EmployeeDatabase.Commit;
DmEmployee.EmployeeDatabase.StartTransaction;
DmEmployee.EmployeeTable.Refresh;
End
Else
MessageDlg('Can抰 Commit Changes:No Transaction Active',mtError, [mbOk], 0);
End;
  如果用戶回答Yes的話,調用Commit向服務器提交數據。當用戶單擊“Undo Edits”按鈕,調用Rollback取消所有的修改。
Procedure TFrmTransDemo.BtnUndoEditsClick(Sender: TObject);
Begin
If DmEmployee.EmployeeDatabase.InTransaction and(MessageDlg('Are you sure you want to undo all changes made during the ' +'current transaction?', mtConfirmation, [mbYes, mbNo], 0) = mrYes) then
Begin
DmEmployee.EmployeeDatabase.Rollback;
DmEmployee.EmployeeDatabase.StartTransaction;
DmEmployee.EmployeeTable.Refresh;
End
Else
MessageDlg('Can抰 Undo Edits: No Transaction Active', mtError, [mbOk], 0);
End;
  在窗口即將隱去的時候,也要調用Commit向服務器提交數據,因為用戶可能沒有單擊“Commit Edits”按鈕。
Procedure TFrmTransDemo.FormHide(Sender: TObject);
Begin
DmEmployee.EmployeeDatabase.Commit;
End;
13.4 一個TDBCtrlGrid構件的示范程序
  這一節詳細剖析一個TDBCtrlGrid構件的示范程序,項目名稱叫Ctrlgrid,它可以在C:/ Program Files/Borland/Delphi4/Demos/Db/Ctrlgrid目錄中找到。它的主窗體如圖13.10所示。
  我們先介紹數據模塊,因為幾個關鍵的構件在數據模塊上,如圖13.11所示
  可以看出,DM1上有三個TTable構件和三個TDataSource構件,這三個TTable構件分別訪問Master表、Industry表和Holdings表。
  主窗體上有兩個柵格,一個是用TDBGrid構件建立的柵格,另一個是用TDBCtrlGrid構件建立的柵格,這兩個柵格都用同一個TDBNavigator構件來導航。
  這個程序運用了這樣一個編程技巧,當用戶把輸入焦點移到TDBGrid構件建立的柵格中時,導航器就為TDBGrid構件建立的柵格導航;當用戶把輸入焦點移到TDBCtrlGrid構件建立的柵格中時,導航器就為TDBCtrlGrid構件建立的柵格導航。程序代碼如下:
Procedure TFmCtrlGrid.DBGrid1Enter(Sender: TObject);
Begin
DBNavigator1.DataSource := DM1.DSMaster;
End;

Procedure TFmCtrlGrid.DBCtrlGrid1Enter(Sender: TObject);
Begin
DBNavigator1.DataSource := DM1.DSHoldings;
End;
  當主窗體彈出時,將觸發OnShow事件。程序是這樣處理OnShow事件的:
Procedure TFmCtrlGrid.FormShow(Sender: TObject);
Begin
DM1.CalculateTotals(Sender, nil);
End;
  其中,CalculateTotals用于計算幾個數值,這些數值將顯示在“InvestmentValue”框內。CalculateTotals是在數據模塊DM1的單元中定義的:
Procedure TDM1.CalculateTotals(Sender: TObject; Field: TField);
var
flTotalCost, flTotalShares, flTotalValue, flDifference: Real;
strFormatSpec: string;
Begin{顯示股票交易的次數}
FmCtrlGrid.lPurchase.Caption := IntToStr( tblHoldings.RecordCount );
{如果股票交易次數為0,就把“Investment Value”框內的數值清掉}
If tblHoldings.recordCount = 0 then
Begin
FmCtrlGrid.lTotalCost.Caption := '';
FmCtrlGrid.lTotalShares.Caption := '';
FmCtrlGrid.lDifference.Caption := '';
End
Else
Begin
{ 把光標設為沙漏狀,因為計算數值的時間可能較長 }
Screen.Cursor := crHourglass;
{ 把數值初始化為0.0 }
flTotalCost := 0.0;
flTotalShares := 0.0;
{ 計算購買所持股票的金額 }
tblHoldings.DisableControls;
tblHoldings.First;
While not tblHoldings.eof Do
Begin
flTotalCost := flTotalCost + tblHoldingsPUR_COST.AsFloat;flTotalShares := flTotalShares + tblHoldingsSHARES.AsFloat;
tblHoldings.Next;
End;
tblHoldings.First;
tblHoldings.EnableControls;{ 計算股票的市值和贏虧 }
flTotalValue := flTotalShares * tblMasterCUR_PRICE.AsFloat;
flDifference := flTotalValue - flTotalCost;
strFormatSpec := tblMasterCUR_PRICE.DisplayFormat;
{ 顯示上述數據 }
FmCtrlGrid.lTotalCost.Caption := FormatFloat( strFormatSpec, flTotalCost );
FmCtrlGrid.lTotalShares.Caption := FormatFloat( strFormatSpec, flTotalValue );
FmCtrlGrid.lDifference.Caption := FormatFloat( strFormatSpec, flDifference );
{ 如果是賺的,就以綠色顯示。如果是虧的,就以紅色顯示 }
If flDifference > 0 then FmCtrlGrid.lDifference.Font.Color := clGreen
Else FmCtrlGrid.lDifference.Font.Color := clRed;
FmCtrlGrid.lDifference.Update;
{ 把光標恢復原狀 }
Screen.Cursor := crDefault;
End;
End;
  此外,當用戶選擇“About”命令時,將打開About框。程序代碼如下:
Procedure TFmCtrlGrid.About1Click(Sender: TObject);
Begin
With TFMAboutBox.Create(nil) Do
Try
ShowModal;
Finally
Free;
End;
End;
  當顯示Holdings表的數據集打開后,就動態指定CalculateTotals作為處理dsMaster的OnDataChange事件的句柄。
Procedure TDM1.tblHoldingsAfterOpen(DataSet: TDataSet);
Begind
sMaster.OnDataChange := CalculateTotals;
End;
  此外,這個程序還演示了書簽的用法。
Procedure TDM1.tblHoldingsAfterPost(DataSet: TDataSet);
var
bmCurrent : TBookmark;
Begin
With tblHoldings Do
Begin
bmCurrent := GetBookmark;
Try
CalculateTotals(nil, nil);
GotoBookmark(bmCurrent);
Finally;
FreeBookmark(bmCurrent);
End;
End;
End;
13.5 一個捕捉數據庫錯誤的示范程序
  這一節剖析一個捕捉數據庫錯誤的示范程序,項目名稱叫Dberrors,它可以在C:/Program Files/Borland/Delphi4/Demos/Db/Dberrors目錄中找到。它的主窗體如圖13.11所示。
  這個程序演示了怎樣捕捉數據庫錯誤。Delphi 4用OnPostError、OnEditError和OnDeleteError事件來捕捉錯誤,這些錯誤產生于用戶對數據庫的操作,如修改、刪除和插入記錄。
  首先從它的數據模塊開始。它的數據模塊叫DM,如圖13.12所示。
  圖13.12 數據模塊
  可以看出,數據模塊上有三個TTable構件和三個TDataSorce構件,這三個TTable構件分別訪問Customer表、Orders表和Items表。
  要說明的是,這三個表之間并不是并行的關系,而是一對多的Master/Detail關系。例如,Orders表的MasterSource屬性指定必須指定為CustomerSource,而Items表的MasterSource屬性必須指定為Orderssource。因此,這些TTable構件和TDataSource構件的生成順序(Creation Order)是很重要的,不能搞錯。
  這個程序的主窗體很簡單,有三個柵格(TDBGrid構件),分別顯示Customer表、Orders表和Items表的數據。
  這個程序用同一個TDBNavigator構件為這三個柵格導航。因此,這個程序運用了一個小小的編程技巧,即動態地切換TDBNavigator構件的DataSource屬性。程序代碼如下:
Procedure TFmMain.GridOrdersEnter(Sender: TObject);
Begin
DBNavigator1.DataSource := Dm.OrdersSource;
End;
Procedure TFmMain.GridCustomersEnter(Sender: TObject);
Begin
DBNavigator1.DataSource := Dm.CustomerSource;
End;
Procedure TFmMain.GridItemsEnter(Sender: TObject);
Begin
DBNavigator1.DataSource := Dm.ItemsSource;
End;
  如果用戶在Customer表中修改、插入或刪除了記錄,當用戶要把輸入焦點移到其他柵格中之前,應當調用Post把用戶對數據的編輯寫到數據庫中。
Procedure TFmMain.GridCustomersExit(Sender: TObject);
Begin
If Dm.Customer.State in [dsEdit,dsInsert] then Dm.Customer.Post;
End;
  此外,當用戶選擇“About”命令時,將顯示一個About框。代碼如下:
Procedure TFmMain.About1Click(Sender: TObject);
var fmAboutBox : TFmAboutBox;
Begin
FmAboutBox := TFmAboutBox.Create(self);
Try
FmAboutBox.showModal;
Finally
FmAboutBox.free;
End;
End;
  下面重點分析怎樣捕捉錯誤。凡是捕捉錯誤的代碼都是在數據模塊的單元中實現的,這也是使用數據模塊的好處之一。當程序調用Post或用戶單擊導航器上的Post按鈕,就會把用戶對數據的修改寫到數據庫中,如果出錯(可能是因為有重復的客戶編號),就會觸發OnPostError事件。讓我們來看看Customer表是怎樣處理OnPostError事件的:
Procedure TDM.CustomerPostError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction);
Begin
If (E is EDBEngineError) then
  If (E as EDBEngineError).Errors[0].Errorcode = eKeyViol then
Begin
MessageDlg('Unable to post: Duplicate Customer ID.',mtWarning,[mbOK],0);
Abort;
End;
End;
  其中,EDBEngineError是一個處理BDE錯誤的異常類,可以訪問它的Errors數組來獲取當前的錯誤代碼。如果錯誤代碼是eKeyViol的話,就顯示一個對話框,告訴用戶不能把數據寫到數據庫中,因為有重復的客戶編號。然后調用Abort放棄此次操作。
  在Customer表中刪除記錄時也有可能出錯,因為被刪除的客戶在Orders表和Items表中還有記錄,這種情況下,就會觸發OnDeleteError事件。讓我們來看看Customer表是怎樣處理OnDeleteError事件的:
Procedure TDM.CustomerDeleteError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction);
Begin
If (E is EDBEngineError) then
If (E as EDBEngineError).Errors[0].Errorcode = eDetailsExist then
Begin
MessageDlg('To delete this record, first delete related orders and items.',mtWarning, [mbOK], 0);
Abort;
End;
End;
  讀者可能發現,處理OnDeleteError事件的方式與處理OnPostError事件的方式差不多,首先判斷錯誤代碼是否是eDetailsExist,如果是的話,表示被刪除的客戶在Orders表和Items表中還有記錄,就顯示一個對話框告訴用戶:要刪除這條記錄,先要刪除Orders表和Items表中的相關記錄。然后調用Abort放棄此次操作。
  由于CustNo字段是Customer表的關鍵字段,當用戶修改CustNo字段的值但還沒有Post之前,為了防止顯示Orders表和Items表的柵格出現混亂,最好調用DisableControls函數暫時禁止刷新數據,等程序調用Post或用戶單擊導航器上的Post按鈕后,再調用EnableControls函數。
Procedure TDM.CustomerCustNoChange(Sender: TField);
Begin
Orders.DisableControls;
Items.DisableControls;
End;
  當程序調用Post或用戶單擊導航器上的Post按鈕后,將觸發AfterPost事件。程序是這樣處理Customer表的AfterPost事件的:
Procedure TDM.CustomerAfterPost(DataSet: TDataSet);
Begin
Dm.Orders.Refresh;
Dm.Items.Refresh;
Dm.Orders.EnableControls;
Dm.Items.EnableControls;
End;
  對于Items表來說,處理OnPostError事件的方式與Customer表處理OnPostError事件的方式大致上是相同的:
Procedure TDM.ItemsPostError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction);
Begin
If (E as EDBEngineError).Errors[0].Errorcode = eForeignKey then
Begin
MessageDlg('Part number is invalid', mtWarning,[mbOK],0);
Abort;
End;
End;
  Orders表是這樣處理OnPostError事件的:
Procedure TDM.OrdersPostError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction);
var iDBIError: Integer;
Begin
If (E is EDBEngineError) then
Begin
iDBIError := (E as EDBEngineError).Errors[0].Errorcode;
Case iDBIError of
eRequiredFieldMissing:
{EmpNo字段必須有值}
Begin
MessageDlg('Please provide an Employee ID', mtWarning, [mbOK], 0);
Abort;
End;
eKeyViol:
{對于Orders表來說,關鍵字段是OrderNo}
Begin
MessageDlg('Unable to post. Duplicate Order Number', mtWarning,[mbOK], 0);
Abort;
End;
End;
End;
End;
  由于Items表依賴于Orders表,因此,刪除Orders表中的記錄時也有可能出錯。因此,程序處理了Orders表的OnDeleteError事件。不過,與處理Customer表的OnDeleteError事件不同的是,這里用一個對話框讓用戶選擇是否要刪除這條有“問題”的記錄,如果用戶回答Yes,就把Items表的記錄全部刪掉,然后把Action參數設為daRetry,表示等退出這個事件句柄后將重新嘗試刪除這條記錄。如果用戶回答No,就調用Abort放棄此次操作。
Procedure TDM.OrdersDeleteError(DataSet: TDataSet; E: EDatabaseError; var Action: TDataAction);
Begin
If E is EDBEngineError then
If (E as EDBEngineError).Errors[0].Errorcode = eDetailsExist then
Begin
If MessageDlg('Delete this order and related items?', mtConfirmation,
[mbYes, mbNo], 0) = mrYes then
Begin
While Items.RecordCount > 0 Do
Items.delete;Action := daRetry;
End
Else
Abort;
End;
End;
13.6 一個對數據集進行過濾的示范程序
  這一節剖析一個對數據集進行過濾的示范程序,項目名稱叫Filter,它可以在C:/Program Files/Borland/Delphi4/Demos/Db/Filter目錄中找到。它的主窗體如圖13.13所示。
  這個示范程序演示了怎樣通過修改Filter屬性動態地設置過濾條件,怎樣在處理OnFilterRecord事件的句柄中改變過濾條件,怎樣通過TQuery構件的Datasource屬性從另一個數據集中獲取參數,一個柵格怎樣動態地切換數據集。
  我們還是從數據模塊開始,因為幾個關鍵的構件放在數據模塊上。這個程序的數據模塊叫DM1,如圖13.14所示。
  數據模塊上有一個TTable構件叫Customer,用于訪問Customer表。有一個TQuery構件叫SQLCustomer,通過SQL語句來訪問Customer表,其SQL語句如下:
  SELECT * FROM "CUSTOMER.DB"
  數據模塊上有一個TDataSource構件叫CustomerSource,它的DataSet屬性既可以設為Customer,也可以設為SQLCustomer。
  數據模塊上還有一個TQuery構件叫SQLOrders,用于查詢Orders表,SQL語句如下:
  Select * From Orders Where CustNo = :CustNo
  SQLOrders的DataSource屬性設為CustomerSource,表示:CustNo參數取自于Customer表的CustNo字段。主窗體上有兩個柵格,上面這個柵格叫DBGrid1,下面這個柵格叫DBGrid2。
  DBGrid1的DataSource屬性設為CustomerSource,而CustomerSource的DataSet屬性既可以設為Customer,也可以設為SQLCustomer,這是通過“DataSet”框內的兩個單選按鈕來切換的。
Procedure TfmCustView.rgDataSetClick(Sender: TObject);
var
st: string;
Begin
With DM1, CustomerSource Do
Begin
If Dataset.Filtered then st := Dataset.Filter;
Case rgDataset.ItemIndex of
0: If Dataset <> SQLCustomer then Dataset := SQLCustomer;
1: If CustomerSource.Dataset <> Customer then Dataset := Customer;
End;
If st <> '' then BeginDataset.Filter := st;
Dataset.Filtered := True;
End;
End;
End;
  當用戶單擊“Filter Customers”按鈕,就打開一個窗口讓用戶設置過濾條件。關于這個窗口后面再講。
Procedure TfmCustView.SpeedButton1Click(Sender: TObject);
Begin
fmFilterFrm.Show;
End;
  DBGrid2顯示Orders表的數據。用戶可以通過一個復選框來選擇是否要對數據集進行過濾,實際上就是修改SQLOrders的Filtered屬性。
Procedure TfmCustView.cbFilterOrdersClick(Sender: TObject);
Begin
DM1.SQLOrders.Filtered := cbFilterOrders.Checked;
If cbFilterOrders.Checked then
Edit1Change(nil);
End;
  如果選中這個復選框的話,就調用Edit1Change把“Amount Paid”框內輸入的數值賦值給DM1單元中的一個公共變量叫OrdersFilterAmount,至于這個變量有什么作用,后面在介紹DM1單元時會講到的。調用Refresh將觸發SQLOrders的OnFilterRecord事件。如果在調用Refresh之前用戶在“AmountPaid”框內鍵入了非數字字符,調用Refresh會觸發EConvertError異常,因此,程序用Try匛xcept結構對這段代碼進行了保護。
Procedure TfmCustView.Edit1Change(Sender: TObject);
Begin
If (cbFilterOrders.checked) and (Edit1.Text <> '') then
Try
DM1.OrdersFilterAmount := StrToFloat(fmCustView.Edit1.Text);
DM1.SQLOrders.Refresh;
ExceptOn EConvertError DoRaise Exception.Create('Threshold Amount must be a number')
End
End;
  前面多次介紹了這樣一個編程技巧,當一個導航器為幾個數據集導航時,應當處理柵格的OnEnter事件,以便動態地切換TDBNavigator構件的DataSource屬性。
Procedure TfmCustView.DBGrid1Enter(Sender: TObject);
Begin
DBNavigator1.DataSource := DBGrid1.DataSource;
End;
Procedure TfmCustView.DBGrid2Enter(Sender: TObject);
Begin
DBNavigator1.DataSource := DBGrid2.DataSource;
End;
  此外,當用戶選擇“About”命令時,將顯示About框。代碼如下:
Procedure TfmCustView.About1Click(Sender: TObject);
Begin
With TFMAboutBox.Create(nil) do
Try
ShowModal;
Finally
Free;
End;
End;
  這個程序還演示了怎樣處理OnFilterRecord事件:
Procedure TDM1.SQLOrdersFilterRecord(DataSet: TDataSet; var Accept: Boolean);
Begin
Accept := SQLOrdersAmountPaid.Value >= OrdersFilterAmount;
End;
  請讀者注意,由于OrdersFilterAmount是一個變量,這意味著用戶只要修改這個變量的值,就能使過濾條件動態地變化。當用戶單擊“Filter Customers”按鈕,就打開一個對話框讓用戶設置過濾條件。這個對話框如圖13.15所示。
  最上面的“List”框是一個組合框,用于列出過去曾經輸入過的過濾條件表達式。“ Condition”框是一個多行文本編輯器,用于輸入過濾條件表達式。
  “Fields”框是一個列表框,用于列出Customer表中的所有字段,因為過濾條件表達式中需要用到字段。因此,程序在處理這個窗口的OnCreate事件的句柄中首先要填充這個列表框。此外,程序還在“List”框中加入了兩個過濾條件。
Procedure TfmFilterFrm. FormCreate(Sender: TObject);
var
I: Integer;
Begin
For I := 0 to DM1.CustomerSource.Dataset.FieldCount - 1 do
ListBox1.Items.Add(DM1.Customer.Fields[I].FieldName);
ComboBox1.Items.Add('LastInvoiceDate >= ''' +DateToStr(EncodeDate(1994, 09, 30)) + '''');
ComboBox1.Items.Add('Country = ''US'' and LastInvoiceDate > ''' +DateToStr(EncodeDate(1994, 06, 30)) + '''');
End;
  當用戶從“List”框中選擇或輸入一個過濾表達式,應當首先把下面的“Condition”框清空,然后把用戶選擇或輸入的過濾表達式加到“Condition”框中。
Procedure TfmFilterFrm.ComboBox1Change(Sender: TObject);
Begin
Memo1.Lines.Clear;
Memo1.Lines.Add(ComboBox1.Text);
End;
  當用戶在“Fields”框中雙擊一個字段,就把該字段加到“Condition”框中。
Procedure TfmFilterFrm.AddFieldName(Sender: TObject);
Begin
If Memo1.Text <> '' then
Memo1.Text := Memo1.Text + ' ';
Memo1.Text := Memo1.Text + ListBox1.Items[ListBox1.ItemIndex];
End;
  當用戶在“Operators”框中雙擊一個運算符,就把該運算符加到“Condition”框中。
Procedure TfmFilterFrm.ListBox2DblClick(Sender: TObject);
Begin
If Memo1.Text <> '' thenMemo1.Text := Memo1.Text + ' '+ ListBox2.Items[ListBox2.ItemIndex];
End;
  由于用戶有可能把過濾條件表達式分成幾行寫,因此,程序需要把以行為單位的字符串轉換為一個字符串列表,因為Filter屬性是一個TStrings對象。
Procedure TfmFilterFrm.Memo1Change(Sender: TObject);
var I: Integer;
Begin
ComboBox1.Text := Memo1.Lines[0];
For I := 1 to Memo1.Lines.Count - 1 do
ComboBox1.Text := ComboBox1.Text + ' ' + Memo1.Lines[I];
End;
  程序用兩個復選框讓用戶設置過濾的選項。一個是“Case Sensitive”框,如果選中此框,FilterOptions屬性中將包含foCaseInSensitive元素。另一個是“NoPartial Compare”框,如果選中此框,FilterOptions屬性中將包含foNoPartialCompare元素。
Procedure TfmFilterFrm.cbCaseSensitiveClick(Sender: TObject);
Begin
With DM1.CustomerSource.Dataset Do
If cbCaseSensitive.checked then
FilterOptions := FilterOptions - [foCaseInSensitive]ElseFilterOptions := FilterOptions + [foCaseInsensitive];
End;
Procedure TfmFilterFrm.cbNoPartialCompareClick(Sender: TObject);
Begin
With DM1.CustomerSource.Dataset Do
If cbNoPartialCompare.checked then
FilterOptions := FilterOptions + [foNoPartialCompare]
Else
FilterOptions := FilterOptions - [foNoPartialCompare];
End;
  當用戶輸入了過濾條件表達式并且設置了過濾選項,就可以單擊“Apply”按鈕把過濾條件表達式賦給Filter屬性:
Procedure TfmFilterFrm.ApplyFilter(Sender: TObject);
Begin
With DM1.CustomerSource.Dataset Do
Begin
If ComboBox1.Text <> '' then
Begin
Filter := ComboBox1.Text;
Filtered := True;
fmCustView.Caption := 'Customers - Filtered';
End
Else
Begin
Filter := '';
Filtered := False;
fmCustView.Caption := 'Customers - Unfiltered'
End;
End;
End;
  如果用戶單擊“Clear”按鈕,就把“Condition”框清空,并把輸入的過濾條件表達式加到“List”框中。
Procedure TfmFilterFrm.SBtnClearClick(Sender: TObject);
var st: string;
Begin
Memo1.Lines.Clear;
st := ComboBox1.Text;
ComboBox1.Text := '';
If ComboBox1.Items.IndexOf(st) = -1 then ComboBox1.Items.Add(st);
End;
  當用戶單擊“Close”按鈕,就關閉這個窗口。
Procedure TfmFilterFrm.SBtnCloseClick(Sender: TObject);
Begin
Close;
End;
13.9 一個復雜的數據庫應用程序
  這一節介紹一個復雜的數據庫應用程序,項目名稱叫Mastapp,它可以在C:/Program Files/Borland/Delphi4/Demos/Db/ Mastapp目錄中找到。它的主窗體如圖13.18所示。
  圖13.18 Mastapp的主窗體
  這個程序比較復雜,讀者一定要對它的程序結構搞清楚。我們先介紹主窗體。我們還是從處理OnCreate事件的句柄開始,因為這是應用程序的起點。
Procedure TMainForm.FormCreate(Sender: TObject);
Begin
ClientWidth := CloseBtn.Left + CloseBtn.Width + 1;
ClientHeight := CloseBtn.Top + CloseBtn.Height;
MainPanel.Align := alClient;
Left := 0;
Top := 0;
InitRSRUN;
End;
  前面兩行代碼用于設置主窗口的寬度和高度。把Left屬性和Top屬性都設為0將使主窗口顯示在屏幕的左上角。
  注意:這個示范程序有一個錯誤是,從Delphi 3開始已經取消了ReportSmith,因此,這里調用InitRSRUN以及InitRSRUN中調用的UpdateRSConnect都是多余的。當用戶使用“File”菜單上的“New Order”命令或單擊工具欄上的“NewOrder”按鈕,程序將打開“Order Form”窗口,代碼如下:
Procedure TMainForm.NewOrder(Sender: TObject);
Begin
EdOrderForm.Enter;
End;
  當用戶使用“File”菜單上的“Print Report”命令,再選擇“Customer List”,將調用PrintCustomerReport函數打印客戶報表。
Procedure TMainForm.CustomerReport(Sender: TObject);
Begin
PrintCustomerReport(False);
End;
  其中,PrintCustomerReport是這樣定義的:
Procedure TMainForm.PrintCustomerReport(Preview: Boolean);
Begin
With MastData.CustByLastInvQuery Do
Begin
Open;
If Preview then CustomerByInvoiceReport.Preview
Else
CustomerByInvoiceReport.Print;
Close;
End;
End;
  由于傳遞給Preview參數的值是False,因此,這里將打印而不是預覽報表。當用戶使用“File”菜單上的“Print Report”命令,再選擇“Order History”,將調用PrintOrderReport函數打印定單報表。
Procedure TMainForm.OrderReport(Sender: TObject);
Begin
PrintOrderReport(False);
End;
  其中,PrintOrderReport是這樣定義的:
Procedure TMainForm.PrintOrderReport(Preview: Boolean);
Const FromToHeading = 'From ''%s'' To ''%s''';
Begin
With QueryCustDlg Do
Begin
MsgLab.Caption := 'Print all orders ranging:';
If FromDate = 0 then FromDate := EncodeDate(95, 01, 01);
If ToDate = 0 then ToDate := Now;
If ShowModal = mrOk then
With MastData.OrdersByDateQuery Do
Begin
Close;
Params.ParamByName('FromDate').AsDate := FromDate;
Params.ParamByName('ToDate').AsDate := ToDate;
Open;
OrdersByDateReport.FromToHeading.Caption :=Format(FromToHeading, [DateToStr(FromDate), DateToStr(ToDate)]);
If Preview then
OrdersByDateReport.Preview
Else OrdersByDateReport.Print;
Close;
End;
End;
End;
  PrintOrderReport函數首先彈出一個如圖13.19所示的對話框,讓用戶選擇首尾日期。
  圖13.19 選擇首尾日期
  當用戶選擇了首尾日期并單擊OK按鈕,就預覽報表,因為Preview參數是False。當用戶使用“File”菜單上的“Print Report”命令,再選擇“Invoice”,將調用PrintInvoiceReport函數打印發貨單報表。
Procedure TMainForm.InvoiceReport(Sender: TObject);
Begin
PrintInvoiceReport(False);
End;
  其中,PrintInvoiceReport是這樣定義的:
Procedure TMainForm.PrintInvoiceReport(Preview: Boolean);
Begin
If PickOrderNoDlg.ShowModal = mrOk then
If Preview then
InvoiceByOrderNoReport.Preview
Else
InvoiceByOrderNoReport.Print;
End;
  PrintInvoiceReport函數首先將彈出如圖13.20所示的對話框,讓用戶選擇定單編號。
  圖13.20 選擇定單編號
  當用戶使用“File”菜單上的“Printer Setup”命令,將打開“打印設置”對話框。
Procedure TMainForm.PrinterSetupClick(Sender: TObject);
Begin
PrinterSetup.Execute;
End;
  當用戶使用“View”菜單上的“Orders”命令或者單擊工具欄上的“Browse”按鈕,程序將打開“Order By Customer”窗口,代碼如下:
Procedure TMainForm.BrowseCustOrd(Sender: TObject);
Begin
Case GetDateOrder(ShortDateFormat) Of
doYMD: ShortDateFormat := 'yy/mm/dd';
doMDY: ShortDateFormat := 'mm/dd/yy';
doDMY: ShortDateFormat := 'dd/mm/yy';
End;
BrCustOrdForm.Show;
End;
  BrowseCustOrd首先調用GetDateOrder函數返回日期的格式,然后彈出“OrderBy Customer”窗口。GetDateOrder函數是這樣定義的:
Function GetDateOrder(const DateFormat: string): TDateOrder;
var I: Integer;
Begin
Result := doMDY;
I := 1;
While I <= Length(DateFormat) Do
Begin
Case Chr(Ord(DateFormat[I]) and $DF) of
'Y': Result := doYMD;
'M': Result := doMDY;
'D': Result := doDMY;
Else Inc(I);
Continue;
End;
Exit;
End;
Result := doMDY;
End;
  當用戶使用“View”菜單上的“Parts/Inventory”命令或單擊工具欄上的“Parts”按鈕,程序將打開“Browse Parts”窗口,代碼如下:
Procedure TMainForm.BrowseParts(Sender: TObject);
Begin
BrPartsForm.Show;
End;
  當用戶使用“View”菜單上的“Stay On Top”命令,就使主窗口總是在屏幕的前端。
Procedure TMainForm.ToggleStayonTop(Sender: TObject);
Begin
With Sender as TMenuItem Do
Begin
Checked := not Checked;
If Checked then MainForm.FormStyle := fsStayOnTop
Else MainForm.FormStyle := fsNormal;
End;
End;
  請讀者注意一個編程技巧,即怎樣使窗口總是在屏幕前端。
  這個程序可以讓用戶選擇用本地數據庫還是遠程數據庫。當用戶選擇“View”菜單上的“Local Data(Paradox Data)”命令時,就使用本地數據庫。當用戶選擇“View”菜單上的“Remote Data(Local Interbase)”命令時,就使用Interbase數據庫。注意:選擇后者時,必須保證已安裝Interbase服務器并且正在運行,否則會觸發異常。
Procedure TMainForm.ViewLocalClick(Sender: TObject);
Begin
CloseAllWindows;
MastData.UseLocalData;
ViewLocal.Checked := True;
Caption := Application.Title + ' (Paradox Data)';
End;

Procedure TMainForm.ViewRemoteClick(Sender: TObject);
Begin
CloseAllWindows;
MastData.UseRemoteData;
ViewRemote.Checked := True;
Caption := Application.Title + ' (Local Interbase)';
End;
  其中,UseLocalData和UseRemoteData是在數據模塊的單元中定義的。在切換數據庫之前必須調用CloseAllWindows關閉所有打開的窗口。CloseAllWindows是這樣定義的:
Procedure TMainForm.CloseAllWindows;
var I: Integer;
F: TForm;
Begin
For I := 0 to Application.ComponentCount - 1 Do
Begin
If Application.Components[I] is TForm then
Begin
F := TForm(Application.Components[I]);
If (F <> Self) and (F.Visible) then F.Close;
End;
End;
End;
  當用戶單擊工具欄上的“Reports”按鈕,就打開“Report Select”窗口,讓用戶選擇要打印或預覽哪個報表,代碼如下:
Procedure TMainForm.ReportBtnClick(Sender: TObject);
Begin
With PickRpt Do
If ShowModal = mrOK then
Case ReportType.ItemIndex of
0: PrintCustomerReport( Preview );
1: PrintOrderReport( Preview );
2: PrintInvoiceReport( Preview );
End;
End;

上一篇:多層數據庫開發十四:剖析幾個MIDAS示范程序

下一篇:多層數據庫開發十二:使用數據控件

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
學習交流
熱門圖片

新聞熱點

疑難解答

圖片精選

網友關注

亚洲香蕉成人av网站在线观看_欧美精品成人91久久久久久久_久久久久久久久久久亚洲_热久久视久久精品18亚洲精品_国产精自产拍久久久久久_亚洲色图国产精品_91精品国产网站_中文字幕欧美日韩精品_国产精品久久久久久亚洲调教_国产精品久久一区_性夜试看影院91社区_97在线观看视频国产_68精品久久久久久欧美_欧美精品在线观看_国产精品一区二区久久精品_欧美老女人bb
亚洲护士老师的毛茸茸最新章节| 欧洲日韩成人av| 精品国偷自产在线视频| 青草青草久热精品视频在线观看| 成人a级免费视频| 亚洲欧洲在线播放| 国产成+人+综合+亚洲欧美丁香花| 成年人精品视频| 91精品视频观看| 日韩一区二区福利| 一道本无吗dⅴd在线播放一区| 日韩精品亚洲精品| 国产精品a久久久久久| 久久视频精品在线| 69av视频在线播放| 欧美日韩国产色| 国产精品白嫩美女在线观看| 欧美一级淫片aaaaaaa视频| 久久躁狠狠躁夜夜爽| 秋霞av国产精品一区| 成人精品一区二区三区电影黑人| 日韩福利伦理影院免费| 欧美日韩福利在线观看| 日韩在线免费视频观看| 国产精品久久久久99| 国产日韩换脸av一区在线观看| 亚洲国产精品va在线看黑人动漫| 亚洲一区二区三区xxx视频| 中文字幕亚洲一区在线观看| 欧美福利视频在线| 国产欧美一区二区白浆黑人| 成人国产精品日本在线| 日本午夜人人精品| 国产成人综合精品| 91系列在线观看| www.久久撸.com| 伊人久久久久久久久久久久久| y97精品国产97久久久久久| 精品久久久久久| 国产欧美日韩综合精品| 国产一区视频在线播放| 精品久久香蕉国产线看观看gif| 日韩av免费一区| 久久免费视频在线| 1769国内精品视频在线播放| 久热精品在线视频| 免费91麻豆精品国产自产在线观看| 欧美午夜激情在线| 一二美女精品欧洲| 国产一区二区三区免费视频| 欧美日韩成人在线播放| 欧美美女18p| 国产欧美在线看| 不卡av电影院| 欧美午夜视频在线观看| 亚洲综合中文字幕在线| 亚洲午夜激情免费视频| 久久噜噜噜精品国产亚洲综合| 91精品国产91久久久久久吃药| 亚洲久久久久久久久久久| 国产深夜精品福利| 日韩性生活视频| 国产91精品视频在线观看| 97热精品视频官网| 久久6免费高清热精品| 亚洲自拍偷拍色图| 国色天香2019中文字幕在线观看| 狠狠久久五月精品中文字幕| 国产精品久久久久久婷婷天堂| 992tv在线成人免费观看| 欧美午夜宅男影院在线观看| 欧美精品精品精品精品免费| 精品女同一区二区三区在线播放| 91精品国产免费久久久久久| 久久综合电影一区| 国产精品视频网站| 欧美日韩国产影院| 国产成人+综合亚洲+天堂| 97在线观看免费高清| 久久久久久亚洲精品| 欧美成人精品h版在线观看| 91免费人成网站在线观看18| 亚洲综合精品伊人久久| 51午夜精品视频| 国产国语刺激对白av不卡| 日韩久久精品成人| 91精品国产91久久久久福利| 美日韩精品免费观看视频| 久久亚洲国产精品成人av秋霞| 成人在线观看视频网站| 欧美夜福利tv在线| 国语自产精品视频在线看| 中文字幕日韩综合av| 国模精品一区二区三区色天香| 久久香蕉国产线看观看网| 久久91亚洲精品中文字幕| 中文字幕综合在线| 91亚洲精品久久久久久久久久久久| 欧美日产国产成人免费图片| 国产精品自拍小视频| 精品久久久久久中文字幕一区奶水| 久久视频精品在线| 麻豆国产va免费精品高清在线| 久久久久久com| 国产精品影片在线观看| 成人免费视频在线观看超级碰| 精品国产一区二区三区久久狼黑人| 亚洲欧美日韩精品| 欧美激情一区二区三区高清视频| 欧美大片网站在线观看| 成人免费网站在线看| 国产成人在线一区二区| 欧美激情精品久久久久| 成人h片在线播放免费网站| 成人免费在线网址| 色综合老司机第九色激情| 国产性猛交xxxx免费看久久| 中文字幕无线精品亚洲乱码一区| 亚洲欧美日韩国产精品| 2021久久精品国产99国产精品| 国产一区二区三区三区在线观看| 一区二区在线免费视频| 国产精品色午夜在线观看| 久久久在线观看| 国产成人涩涩涩视频在线观看| 久久免费视频这里只有精品| 亚洲少妇激情视频| 在线电影av不卡网址| 久久久免费电影| 中文字幕一精品亚洲无线一区| 日韩美女免费观看| 日韩电视剧免费观看网站| 国产成人精品免费久久久久| 亚洲欧洲成视频免费观看| 久久国产精彩视频| 乱亲女秽乱长久久久| 777国产偷窥盗摄精品视频| 国产精品1234| 5566日本婷婷色中文字幕97| 欧美精品videofree1080p| 中文字幕精品国产| 欧美富婆性猛交| 日韩精品极品在线观看播放免费视频| 日本久久精品视频| 日韩视频在线免费| 久久久久久久亚洲精品| 日韩欧亚中文在线| 亚洲级视频在线观看免费1级| 97久久精品人搡人人玩| 成人观看高清在线观看免费| 国产精品一区av| 欧美成在线视频| 亚洲女人被黑人巨大进入| 在线精品国产欧美| 成人精品一区二区三区| 91影院在线免费观看视频| 色综合久久中文字幕综合网小说| 国产精品pans私拍| 亚洲mm色国产网站| 国产精品激情av在线播放| 综合国产在线视频| 在线国产精品视频| 国产成人久久久精品一区| 日韩成人在线电影网|