(第二部分)
Borland® Delphi® 2005 Migration to .NET using VCL for .NET
by Bob Swart, Bob Swart Training & Consultancy, translate into chinese by Visli.
数据存取
前面我们已经移植了一个 VCL 应用程序到 .NET, 而实际应用中许多程序都包含有数据的存取, 现在我们就移植一个大一点的这样的程序. 让我们来看一下 Delphi7/Demos/Db 目录. 这里面有几个示例程序, MastApp 与 IBMastApp 是最大的, 使用了大量的单元与窗体.
如果您没有 Delphi 7, 那么就转到 BDS/3.0/Demos/DelphiWin32/Db 目录, 这里也有同样功能的 MastApp 与 IBMastApp 工程 (但它们已经移植到了新的工程格式 - 包含了一个 .bdsproj 文件).
MastApp 已被移植到了 .NET, 我们可以在 BDS/3.0/Demos/Delphi.NET/DB/MastApp 目录中找到它, 但是 IBMastApp 工程尚未被移植到 VCL for .NET , 所以我们不能在 BDS/3.0/Demos/Delphi.NET/DB 或 BDS/3.0/Demos/Delphi.NET/VCL/Db 目录中找到它.
因此, 我们把它作为我们的用例, 让我们 IBMastApp 应用程序从 Win32 来移植到 .NET - 一个不错的数据库示例程序.
首先, 让我们创建一个必要的文件拷贝.
- 在 BDS/3.0/Demos/Delphi.NET/VCL/Db 目录创建一个 IBMastApp 子文件夹. 用来作为我们的工作目录.
- 复制 BDS/3.0/Demos/DelphiWin32/VCLWin32/Db/IBMastApp 目录下的所有文件到 BDS/3.0/Demos/Delphi.NET/VCL/Db/IBMastApp 目录.
- 删掉 mastapp.bdsproj 文件.
现在我们就准备工作在新的 IBMastApp 工程上, 并把它移植到 .NET.
- 运行 Delphi 2005
- 在Delphi 中从 BDS/3.0/Demos/Delphi.NET/VCL/Db/IBMastApp 目录打开 mastapp.dpr 工程文件.
由于该工程现在已经没有了 .bdsproj 文件来关联它, 所以 Delphi 2005 IDE 会询问您是把它更新为 Win32 工程还是 .NET 工程. 下面就是工程更新对话框:
- 选择 Delphi for .NET 选项, 然后点击确定按钮.
这将为我们产生一个新的 mastapp.bdsproj 文件, 里面存储了 .NET 指示特性. 我们现在保存工程.
- 选择 File | Save All 菜单, 这样 mastapp 工程就被保存了(包括新的 mastapp.bdsproj 文件).
移植数据模块
现在我们已经把工程关联为 Delphi for .NET, 我们首先需要确定数据库是否指到了正确的位置. 这是在您在首次尝试编译之前需要做的. 一旦数据库连接正确了, 我们就能集中精力在源代码的移植上.
- 打开数据模块, 也就是 DataMod.pas 文件.
数据模块的设计窗口大致应该如下图所示, 本程序使用的是 InterBase Express 作为数据存取技术.
(译注:您需要安装InterBase, 否则下图中的数据连接组件不会显示)
我们需要重新配置 Database 组件, 位于数据模块右下角的那个(上图红圈).
- 在数据模块上选择 Database 组件 (它是 TIBDatabase 类型) .
- 在 Database 组件上右击鼠标, 这将显示一个弹出菜单告诉您 InterBaseExpress 的版本(9.09) 以及提供一个 Database Editor (数据库编辑器)的菜单项. 选择 Database Editor, 您将看到如下对话框:
注意图中路径位置的 D:/ . 您应该把该路径指到您计算机中 Interbase 数据库文件 mastsql.gdb 所在的具体位置. 在我的机器中, mastsql.gdb 位于 C:/Program Files/Common Files/Borland Shared/Data/mastsql.gdb.
- 点击 Test 按钮来检验 InterBase 数据库 mastsql.gdb 能否正确连接. 如果不能, 请确认mastsql.gdb 文件的位置是否正确, 以及 InterBase 数据库服务是否已经运行.
- 一旦您得到 "Successful Connection" (成功连接)的提示, 您就可以关闭数据库组件编辑器了.
*注意: 在您把 VCL 工程移植到 .NET 时多半需要完成这项工作: 首先确认数据存取组件指到了正确的数据库. 现在大多数的 VCL 数据存取组件已经有了对应的 VCL for .NET 组件, 但在 .NET 中并不支持 SQL Links , 因此这时您可以用 dbExpress, dbGo for ADO, InterBaseExpress 或其它 VCL for .NET 数据存取技术来完成移植.
移植源代码
一旦完成了数据连接检验, 我们就可以开始首次编译工程了.
- 保存全部文件.
- 按下 Ctrl+F9 来首次编译 mastapp 工程.
这将给你带来大约 17 条警告信息和1条错误信息, 如下所示:
[Warning] mastapp.dpr(23): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Windows' is specific to a platform [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Messages' is specific to a platform [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform [Warning] MAIN.PAS(6): W1005 Unit 'Borland.Vcl.Controls' is specific to a platform [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Dialogs' is specific to a platform [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Buttons' is specific to a platform [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.StdCtrls' is specific to a platform [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.Menus' is specific to a platform [Warning] MAIN.PAS(7): W1005 Unit 'Borland.Vcl.ExtCtrls' is specific to a platform [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Windows' is specific to a platform [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Messages' is specific to a platform [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Graphics' is specific to a platform [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Controls' is specific to a platform [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Forms' is specific to a platform [Warning] DataMod.pas(8): W1005 Unit 'Borland.Vcl.Dialogs' is specific to a platform [Fatal Error] DataMod.pas(9): F1026 File not found: 'VarUtils.dcuil'
警告信息可以被忽略 - 这些警告只是告诉我们, 我们所使用的VCL for .NET是用于特别的平台的. 要取消这个警告功能, 您可以禁用它.
- 选择菜单 Project | Options 打开选项对话框. 在 Compiler Messages (编译器信息)分类中, 您取消选中 "Platform Unit" 警告 (如下图所示). 如果您再编译工程, 就会发现警告信息不再出现了.
- 按下 Shift+F9 重新编译. 这下不再出现前面的17个警告, 但错误仍在.
[Fatal Error] DataMod.pas(9): F1026 File not found: 'VarUtils.dcuil'
这个错误信息是由 VCL for .NET 引起的, VarUtils 单元不再有了 (很可能已经集成了 Variants 单元了). 所以我们可以从 uses 子句中移除不再存在的 VarUtils 单元.
- 在 DataMod.pas 的 interface 区的 uses 子句中删除 VarUtils.
*注意, 如果您希望您的工程在 Win32 下也能编译通过, 您就可以在 VarUtils 单元引用上放上一个 {$IFDEF WIN32} .... {$ENDIF} 的编译指令.
- 可以修改 uses 子句如下:
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, DB, IBQuery, IBCustomDataSet, IBTable, IBDatabase, IB, Variants {$IFDEF WIN32}, VarUtils {$ENDIF};
- 保存全部文件, 按下 Shift+F9 来重新编译.
这时将出现 3 个提示与 2 个错误.
[Error] EDCUST.PAS(54): E2010 Incompatible types: 'Variant' and 'Double' [Fatal Error] BrCstOrd.pas(48): F2063 Could not compile used unit 'EDCUST.PAS'
引起类型不兼容的代码如下:
procedure TEdCustForm.Edit(CustNo: Double); begin MastData.Cust.Open; MastData.Cust.Locate('CustNo', CustNo, []); ShowModal; end;
错误信息就是 MastData.Cust.Locate 的 CustNo 参数引起的. 它的类型是 Double, 但需要的是一个 Variant 类型. 这应该没什么问题, 但编译器需要有 Variants 单元添加到 uses 子句.
- 在 Edcust.pas 单元 implementation 区添加 Variants 单元.
- 保存文件, 按下 Shift+F9 来重新编译.
这将返回3个提示与1个错误.
[Fatal Error] EDORDERS.PAS(10): F1026 File not found: 'DBLookup.dcuil'
It appears that the DBLookup unit is also no longer available in VCL for .NET - or required for this project. We can safely remove it.
- Remove the DBLookup unit from the uses clauses of the interface section of Edorders.pas.
*Note that if you want your project to be compilable to a Win32 target as well, you may want to place the DBLookup unit in an {$IFDEF WIN32} .... {$ENDIF} block instead of just removing the unit from the uses clause.
- If you decide to use the IFDEF solution, modify the uses clause so it looks as follows:
uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Dialogs, Forms, StdCtrls, DBGrids, DBCtrls, DB, Buttons, Grids, {$IFDEF WIN32} DBLookup, {$ENDIF} ExtCtrls, Mask;
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This will result in 3 hints and 4 errors.
[Error] SrchDlg.pas(54): E2010 Incompatible types: 'Variant' and 'Double' [Error] SrchDlg.pas(64): E2010 Incompatible types: 'Variant' and 'Double' [Error] SrchDlg.pas(98): E2010 Incompatible types: 'Variant' and 'TCaption' [Fatal Error] EDORDERS.PAS(76): F2063 Could not compile used unit 'SrchDlg.pas'
The first three problems are very similar to an error that we saw before, which could simply be solved by adding the Variants unit to the uses clause.
- Add the Variants unit to the uses clause of the implementation section of unit SrchDlg.pas.
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This will result in 3 hints, 1 warning, and 2 errors.
[Error] EDORDERS.PAS(105): E2010 Incompatible types: 'Variant' and 'Double' [Warning] EDORDERS.PAS(229): W1050 WideChar reduced to byte char in set expressions [Fatal Error] BrCstOrd.pas(48): F2063 Could not compile used unit 'EDORDERS.PAS'
The error should be known by now, as well as the solution. The warning will be taken care of at the end of the cycle - first let's spend our efforts solving the compiler errors.
- Add the Variants unit to the uses clause of the implementation section of unit Edorders.pas.
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This will result in 3 hints, 1 warning, and 3 errors.
- Add the Variants unit to the uses clause of the implementation section of unit BrCstOrd.pas.
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This will result in 3 hints, 1 warning, and 1 error.
- Remove DBLookup from uses clause (interface) of Edparts.pas, or place the DBLookup unit in an {$IFDEF WIN32} block as follows:
uses SysUtils, Windows, Messages, Classes, Graphics, Controls, Forms, Dialogs, DB, StdCtrls, ExtCtrls, Mask, DBCtrls, {$IFDEF WIN32} DBLookup, {$ENDIF} Buttons;
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This will result in 3 hints, 1 warning, and 2 errors.
- Add the Variants unit to the uses clause of the implementation section of unit Edparts.pas.
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This will result in 3 hints, 1 warning, and 2 errors.
- Add the Variants unit to the uses clause of the implementation section of unit Brparts.pas.
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
If you are running the updated version of Delphi 2005, then your application will compile and link at this time and you can move on to the section "Running the Marine Adventure Order Entry Application". If you find that you still have some errors, then please continue on here.
This will result in 3 hints, 1 warning, and 1 fatal error.
[Hint] DataMod.pas(244): H2443 Inline function 'ExpandFileName' has not been expanded because unit 'System.IO' is not specified in USES list [Hint] DataMod.pas(743): H2443 Inline function 'FileExists' has not been expanded because unit 'System.IO' is not specified in USES list [Hint] DataMod.pas(748): H2443 Inline function 'ExtractFileName' has not been expanded because unit 'System.IO' is not specified in USES list [Warning] EDORDERS.PAS(229): W1050 WideChar reduced to byte char in set expressions [Fatal Error] CustRpt.pas(6): F1026 File not found: 'Quickrpt.dcuil'
At this time, you will also get a design-time error dialog that mentions properties which cannot be found when the Delphi 2005 VCL Designer tries to display the CustRpt unit.
No QuickReports
The problems are caused by the fact that the Quickrpt.dcuil unit cannot be found by the CustRpt.pas unit: QuickReport for .NET is not included with Delphi 2005. The recommended way to migrate this functionality, is to use Rave Reports for example. Or look for a .NET version of QuickReports, or find some other reporting framework to use.
Reporting is left as exercise for the reader, but we still need to make the project compile - even without the reporting functionality.
- Do Project | View Source to open the project source file mastapp.dpr, and look for the QuickReports units in the uses clause (these are the lines with the TQuickRep types), and place them in comments as follows:
uses Forms, Main in 'MAIN.PAS' {MainForm}, Brparts in 'BRPARTS.PAS' {BrPartsForm}, QryCust in 'QryCust.pas' {QueryCustDlg}, Edparts in 'EDPARTS.PAS' {EdPartsForm}, BrCstOrd in 'BrCstOrd.pas' {BrCustOrdForm}, Edcust in 'EDCUST.PAS' {EdCustForm}, Edorders in 'EDORDERS.PAS' {EdOrderForm}, SrchDlg in 'SrchDlg.pas' {SearchDlg}, Splash in 'SPLASH.PAS' {SplashForm}, Pickdate in 'PICKDATE.PAS' {BrDateForm}, About in 'ABOUT.PAS' {AboutBox}, Pickrep in 'PICKREP.PAS' {PickRpt}, //CustRpt in 'CustRpt.pas' {CustomerByInvoiceReport: TQuickRep}, //OrderRpt in 'OrderRpt.pas' {OrdersByDateReport: TQuickRep}, //InvcRpt in 'InvcRpt.pas' {InvoiceByOrderNoReport: TQuickRep}, PickInvc in 'PickInvc.pas' {PickOrderNoDlg}, DataMod in 'DataMod.pas' {MastData: TDataModule};
A nice quick way to place source lines in comments is to press the Ctrl+/ keys. This keystroke will toggle a line of code from uncommented to commented, and move the cursor to the next line. So you can quickly place all three lines in comments.
*Note that you can also select a block of lines of code and then press Ctrl+/ to place the entire block in comments all at once.
- Move to the bottom of the project source file, and locate the three CreateForm statements that create the QuickReports forms. Select them in a block, and press Ctrl+/ to place them all in comments, resulting in the following code:
begin Application.Initialize; SplashForm := TSplashForm.Create(Application); SplashForm.Show; SplashForm.Update; Application.Title := 'Marine Adventures Order Entry'; Application.HelpFile := 'MASTAPP.HLP'; Application.CreateForm(TMainForm, MainForm); Application.CreateForm(TBrPartsForm, BrPartsForm); Application.CreateForm(TQueryCustDlg, QueryCustDlg); Application.CreateForm(TEdPartsForm, EdPartsForm); Application.CreateForm(TBrCustOrdForm, BrCustOrdForm); Application.CreateForm(TEdCustForm, EdCustForm); Application.CreateForm(TEdOrderForm, EdOrderForm); Application.CreateForm(TSearchDlg, SearchDlg); Application.CreateForm(TBrDateForm, BrDateForm); Application.CreateForm(TAboutBox, AboutBox); Application.CreateForm(TPickRpt, PickRpt); // Application.CreateForm(TCustomerByInvoiceReport, CustomerByInvoiceReport); // Application.CreateForm(TOrdersByDateReport, OrdersByDateReport); // Application.CreateForm(TInvoiceByOrderNoReport, InvoiceByOrderNoReport); Application.CreateForm(TPickOrderNoDlg, PickOrderNoDlg); Application.CreateForm(TMastData, MastData); SplashForm.Hide; SplashForm.Free; Application.Run; end.
This is not enough, however, since the units that use QuickReports are also used in other places of the application. We need to find and comment all of these.
- Do Search | Find in Files, and look for CustRpt (which could be in the uses clause of some other units, and should then be removed).
*Note that we can group the results by file, and the results will be displayed in a new treeview. As a result, the Main.pas unit is shown.
- Open file Main.pas, and take a look at the uses clause of the implementation section, which includes the CustRpt unit.
*Note that Error Insight automatically marks this unit, as well as the OrderRpt and InvcRpt units as being invalid (the compiler cannot resolve the unit names).
- Use Ctrl+/ to place the CustRpt, OrderRpt, and InvcRpt units in comments.
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
The next error is shown in the PrintCustomerReport method.
- Select the code inside the PrintCustomerReport method and place it in comments, for example as follows (feel free to select more or less code to comment):
procedure TMainForm.PrintCustomerReport(Preview: Boolean); begin with MastData.CustByLastInvQuery do begin Open; // if Preview then // CustomerByInvoiceReport.Preview // else // CustomerByInvoiceReport.Print; Close; end; end;
The next problem can be found in the PrintOrderReport method, which again should be placed in comments.
- Select the code inside the PrintOrderReport method, and press Ctrl+/ to put everything in comments, as follows:
There's one place left: the PrintInvoiceReport.
- Select the code inside the PrintInvoiceReport method and place it in comments, for example as follows (feel free to select more or less code to comment):
procedure TMainForm.PrintInvoiceReport(Preview: Boolean); begin if PickOrderNoDlg.ShowModal = mrOk then // if Preview then // InvoiceByOrderNoReport.Preview // else // InvoiceByOrderNoReport.Print; end;
- Press Shift+F2 to Save All files in the project, and then press Shift+F9 to rebuild the project.
This time, the project compiles and links without problems.
DatabasePath
There's one more thing that needs some attention. If you run the application now, there's a chance that it will work but there's also a good chance that you'll get the message that the database could not be located.
This last issue is actually caused by the code in the application itself. Inside the data module, there's a function called DataDirectory, which is defined as follows:
function TMastData.DataDirectory: string; begin { Assume data is in ../../../../../Common Files/Borland Shared/DATA/data relative to where we are } Result := ExtractFilePath(ParamStr(0)); //Result := ExpandFileName(Result + '../../DATA/'); Result := ExpandFileName(Result + '../../../../../Common Files/Borland Shared/DATA/'); end;
As the comments say, it's an assumption that the data is located in ../../../../../Common Files/Borland Shared/DATA/data relative to where this application is located. This may work for the Demo directory structure, but is not something I would like to use when deploying the application.
In fact, right at the beginning when we started to migrate this project, we made sure that the database could be connected to at design-time. So the Database.DatabaseName is already pointing to the right place.
The DataDirectory function is used in one place: the MastDataCreate method. And we have to undo what's being done there.
- Edit the datamod.pas unit, and locate the MastDataCreate method. Make sure to assignment DataFile with the value of Database.DatabaseName, overwriting the value of the DataDirectory, as follows:
procedure TMastData.MastDataCreate(Sender: TObject); var DataFile: string; begin DataFile := DataDirectory + 'MASTSQL.GDB'; DataFile := Database.DatabaseName; // already working at design-time! if not FileExists(DataFile) then if MessageDlg('Could not locate MASTSQL.GDB. Would you like to locate the file?', mtError, [mbYes, mbNo], 0) = mrYes then if OpenDialog.Execute then begin if UpperCase(ExtractFileName(OpenDialog.FileName)) = 'MASTSQL.GDB' then DataFile := OpenDialog.FileName else raise Exception.Create('Invalid File: ' + OpenDialog.FileName); end else raise Exception.Create('Cannot locate Interbase data file: MASTSQL.GDB'); Database.DatabaseName := DataFile; Database.Open; Transaction.StartTransaction; end;
*Note that you can also remove the call to DataDirectory here, but you still need to assign the new value to DataFile.
- Press Shift+F2 to Save All files in the project
Running the Marine Adventures Order Entry Application
- Press Shift+F9 to rebuild the project.
The project again compiles and runs, showing the Marine Adventures Order Entry application, as follows:
-
- Click on the New Order button for the Order Form:
- Close the Order Form and Click on the Browse button for the Orders by Customer Form:
- Close the Orders by Customer Form and Click on the Parts button for the Browse Parts Form:
- Close the Browse Parts Form and if you are not running on an updated Delphi 2005, click on the Reports button for the Report Selection Form:
*Note that neither of the three reports will be available - the buttons are not working - because we placed all use of QuickReports and the reporting forms in comments. Feel free to add your own reports.
Summary and Conclusions
In this tutorial, we have seen how to migrate examples of existing Win32 VCL applications to .NET using Delphi 2005 and VCL for .NET. We started with a threading example, which was first migrated to an unsafe .NET application, and then turned into a 100% safe native .NET application. We then moved on with a more complex example using databases.
The amount of effort that it takes in migrating VCL applications from Win32 to .NET is far less than it would take to rebuild the application. Delphi 2005 offers great support for migration to .NET, protecting our investments of existing Win32 applications.