2015/03/30

TIWDBAsyncNavigator 圖片無法顯示的問題

IntraWeb version: V14.2.3
TMS IntraWeb version: TMS IntraWeb Component Pack Pro Script Edition V5.4.1.1

在使用 TIWDBAsyncNavigator 元件時,實際執行的畫面如下:
TIWDBAsyncNavigator 的圖片無法顯示
沒有指定按鈕圖片時,IWDBAsyncNavigator預設會載入 IWDBNavigator 的圖片。

網頁原始碼比較:

IWDBAsyncNavigator 圖示路徑
IWDBNavigator 圖示路徑
檔名相同,但 IWDBAsyncNavigator 路徑缺少根目錄符號(/)。不知道是什麼原因造成,於是進入 IWDBAsyncNavigator.pas 來檢查。

在 LINE 1036 處修改如下:
{$IFDEF TMSIW11}
    {$IF FALSE}
    if url = '' then
      url := '$/gfx/DBNAV_' + Action + '.gif';
    if urld = '' then
      urld := '$/gfx/DBNAV_' + Action + 'Disabled.gif';
    {$ELSE}
    if url = '' then
      url := 'gfx/DBNAV_' + Action + '.gif';
    if urld = '' then
      urld := 'gfx/DBNAV_' + Action + 'Disabled.gif';
    {$IFEND}
{$ELSE}
    if url = '' then
      url := '/gfx/DBNAV_' + Action + '.gif';
    if urld = '' then
      urld := '/gfx/DBNAV_' + Action + 'Disabled.gif';
{$ENDIF}


IWDBAsyncNavigator  圖示終於可以正確顯示


參考資料:IWDBAsyncNavigator images

2015/03/28

Firebird Embedded 連線注意的地方

Delphi的DBX一直都無法連線到Firebird 1.5。

老是出現「DBX Error: Driver could not be properly. Client may be misiing, not installed properly, of the wrong version, or thr driver may be misiing from the system path.」

原來是DBX Driver不只是單認system path,連VendorLib也強迫認「fbclient.dll」。

難怪我就算把VendorLib設為「[DirPath]\fbembed.dll」也完全連接不上Firebird Embedded。

結論:
1.把Firebird的fbembed.dll複製到Delphi安裝目錄\Bin。
2.fbembed.dll改名為fbclient.dll

連結成功!

2015/03/10

IntraWeb使用ClientDataSet的注意事項


Working with ClientDataSets裡是這麼寫的:
If you are using a ClientDataSet component and you get an Access Violation when exiting the application, you need to add the DBClient unit in the application uses clause before IWMain.
The reason for the access violation is that if DBClient is not included in project file uses clause, it's internal interfaces are freed before all sessions are closed and when IW closes it's sessions it will try to free ClientDataSet component, and you will get the access violation.
When DBClient is placed before IWMain IW will free sessions before DBClient interfaces are freed.
這章節在描述TClientDataSet可能會在Session釋放前就先被清除(Freed),然後在Session要釋放時又再被釋放一次,進而發生 Access Violation。

解決的方法就是在 IWMain 前的 uses 加入 DBClient,讓釋放的順序正確,Access Violation的問題才能獲得解決。





Delphi XE7使用Indy 10.6.1連結Gmail SMTP

我在KTOP中回答「xe6 使用gmail的問題」這個主題,因為程式碼編排有問題,故在這裡另外轉貼。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
unit Unit1;
 
interface
 
uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, IdExplicitTLSClientServerBase, IdSMTP, IdSSLOpenSSL,
  IdMessage, IdAttachmentFile, Vcl.StdCtrls;
 
type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    function SendEmail(sendTo: string;
                    subject: string;
                    body: string;
                    attachFiles: TStringList;
                    smtpHost: string;
                    smtpPort: Integer;
                    smtpUser: string;
                    smtpPass: string;
                    tls: TIdUseTLS): boolean;
  end;
 
var
  Form1: TForm1;
 
implementation
 
{$R *.dfm}
 
{ TForm1 }
 
procedure TForm1.Button1Click(Sender: TObject);
begin
  SendEmail( 'mybuddy@example.com'// 目的地E-Mail
                    'This is the subject',
                    'This is the body of the email....',
                    nil,
                    'smtp.gmail.com',
                    587,
                    'myusername@gmail.com'// 登入帳號
                    'mypassword'{ 登入密碼 }, utUseExplicitTLS);
end;
 
function TForm1.SendEmail(sendTo, subject, body: string;
  attachFiles: TStringList; smtpHost: string; smtpPort: Integer; smtpUser,
  smtpPass: string; tls: TIdUseTLS): boolean;
var
    smtp: TIdSmtp;
    ssl: TIdSSLIOHandlerSocketOpenSSL;
    msg: TIdMessage;
    i: Integer;
begin
    smtp:=TIdSmtp.Create(nil);
    ssl:=TIdSSLIOHandlerSocketOpenSSL.Create(nil);
    msg:=TIdMessage.Create(nil);
    try
 
        try
            smtp.Host:=smtpHost;
            smtp.Port:=smtpPort;
            smtp.Username:=smtpUser;
            smtp.Password:=smtpPass;
 
            //smtp.OnConnected :=IdSMTP1Connected;
            //smtp.OnDisconnected :=IdSMTP1Disconnected;
            //smtp.OnFailedRecipient :=IdSMTP1FailedRecipient;
            //smtp.OnStatus :=IdSMTP1Status;
            //smtp.OnTLSNotAvailable :=IdSMTP1TLSNotAvailable;
            //smtp.OnWork :=IdSMTP1Work;
 
            if not (tls=utNoTLSSupport) then begin
                ssl.Destination:=smtpHost + ':' + IntToStr(smtpPort);
                ssl.Host:=smtpHost;
                ssl.Port:=smtpPort;
                ssl.SSLOptions.Method:=sslvTLSv1;
 
                //ssl.OnStatusInfo:=IdSSLIOHandlerSocketOpenSSL1StatusInfo;
                //ssl.OnGetPassword:=IdSSLIOHandlerSocketOpenSSL1GetPassword;
                //ssl.OnStatus:=IdSSLIOHandlerSocketOpenSSL1Status;
 
                smtp.IOHandler:=ssl;
                smtp.UseTLS:=tls;
            end;
 
            msg.Recipients.EMailAddresses := sendTo;
            msg.Subject:=subject;
            msg.Body.Text:=body;
 
            if(Assigned(attachFiles)) then begin
                for i := 0 to attachFiles.Count - 1 do begin
                   if FileExists(attachFiles[i]) then
                        TIdAttachmentFile.Create(msg.MessageParts, attachFiles[i]);
                end;
            end;
 
            smtp.Connect;
            smtp.Send(msg);
            smtp.Disconnect;
 
            result:=true;
        finally
            msg.Free;
            ssl.Free;
            smtp.Free;
        end;
    except
       result:=false;
    end;
end;
 
end.

結論:
上述程式可以正確發出和收到信件,在XE7下驗證無誤。


參考資料來源:Sending email with attachments using Delphi, Indy 10.5.5 and GMail




2015/03/09

ADO 是個好東西,不用嗎?

ADO--ActiveX Data Object

在RAD Studio裡被歸類在「ADO Express」元件盤中,RAD Studio 2006後則改名為「dbGo」。

ADO架構
圖片來源:MSDN Microsoft Data Development Technologies: Past, Present, and Future


ADO的缺點在於,全世界的作業系統中,只有Windows才有具備Ole Provider。

換言之,ADO只能運行在Windows的世界。

然而在深入了解ADO後,發現ADO的核心概念和DataSnap幾乎一樣,透過Windows平台內建的OleDB engine,要單層開發、兩層開發,有狀態還是無狀態形式的公事包開發都不是問題,一切只需要一個ADO元件就可以完成。

ADO是個好東西,不用嗎?

單機,對於Access、Excel都有非常多的範例和很極佳的效能。
兩層,對於SQL Server的連線效能極佳,又額外提供其它資料庫的Ole Provider可以擴充。

無狀態的儲存,我們可以採用「公事包」的方式,把ADODataSet的Data另存為實體檔案,待網路順暢時再行傳輸。

「公事包」這名詞,微軟是這麼說的:

使用公事包同步 -- Windows 7 說明
您可以使用 [公事包] 來讓兩部不同電腦之間的檔案保持同步,即使電腦不在相同的網路上。 如果電腦不在相同的網路上,您可以使用卸除式媒體從一部電腦將檔案複製到另一部電腦,再使用另一部電腦處理那些檔案,然後使用 [公事包] 對原始電腦進行變更同步。

ADO 裡的 LockType 中,ltBatchOptimistic 參數便是對應公事包特性。
更淺碟一點的說法,就是「離線編輯」。

實際的範例用法如下:
dfm:

object ADOConnection1: TADOConnection
  Connected = True
  ConnectionString =
    'Provider=SQLOLEDB.1;Persist Security Info=False;User ID=root;' +
    'Initial Catalog=AdventureWorks;Data Source=127.0.0.1;password=ILoveEden!'
  KeepConnection = False
  LoginPrompt = False
  Mode = cmReadWrite
  Provider = 'SQLOLEDB.1'
  Left = 60
  Top = 34
end
object DBNavigator1: TDBNavigator
  Left = 42
  Top = 25
  Width = 240
  Height = 25
  DataSource = DataSource1
  TabOrder = 0
end
object ADODataSet1: TADODataSet
  Active = True
  Connection = ADOConnection1
  CursorType = ctStatic
  LockType = ltBatchOptimistic
  CommandText = 'select * from Emp'
  Parameters = <>
  Left = 60
  Top = 86
end
object DataSource1: TDataSource
  DataSet = ADODataSet1
  Left = 58
  Top = 136
end
object DBGrid1: TDBGrid
  Left = 12
  Top = 62
  Width = 379
  Height = 171
  DataSource = DataSource1
  TabOrder = 1
  TitleFont.Charset = DEFAULT_CHARSET
  TitleFont.Color = clWindowText
  TitleFont.Height = -11
  TitleFont.Name = 'Tahoma'
  TitleFont.Style = []
end
object TrackBar1: TTrackBar
  Left = 464
  Top = 13
  Width = 79
  Height = 32
  Max = 1
  Position = 1
  TabOrder = 2
  OnChange = TrackBar1Change
end
object Label1: TLabel
  Left = 406
  Top = 23
  Width = 52
  Height = 13
  Caption = 'Connected'
end
object CheckBox1: TCheckBox
  Left = 406
  Top = 56
  Width = 97
  Height = 17
  Caption = 'Offline Mode'
  TabOrder = 3
  OnClick = CheckBox1Click
end
object Button1: TButton
  Left = 410
  Top = 86
  Width = 75
  Height = 25
  Caption = 'ApplyUpdate'
  TabOrder = 4
  OnClick = Button1Click
end
object Button2: TButton
  Left = 410
  Top = 122
  Width = 101
  Height = 25
  Caption = 'Save Package'
  TabOrder = 5
  OnClick = Button2Click
end
object Button3: TButton
  Left = 410
  Top = 153
  Width = 101
  Height = 25
  Caption = 'Load Package'
  TabOrder = 6
  OnClick = Button3Click
end

Pas:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls, Grids, DBGrids, DB, ADODB, ExtCtrls, DBCtrls;

type
  TForm1 = class(TForm)
    ADOConnection1: TADOConnection;
    DBNavigator1: TDBNavigator;
    ADODataSet1: TADODataSet;
    DataSource1: TDataSource;
    DBGrid1: TDBGrid;
    TrackBar1: TTrackBar;
    Label1: TLabel;
    CheckBox1: TCheckBox;
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure TrackBar1Change(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  ADODataSet1.Connection := ADOConnection1;
  ADODataSet1.UpdateBatch;
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  ADODataSet1.SaveToFile('OfflinePackage.data');
end;

procedure TForm1.Button3Click(Sender: TObject);
begin
  ADODataSet1.Close();
  ADODataSet1.LoadFromFile('OfflinePackage.data');
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  ADODataSet1.Connection := nil;
end;

procedure TForm1.TrackBar1Change(Sender: TObject);
begin
  ADOConnection1.Connected := (TrackBar1.Position = 1);
  ADODataSet1.Active       := ADOConnection1.Connected;
end;

end.

成果圖:

ADO 公事包範例

故可從以上得知,ADO 真的是很棒的架構。

公司採用 SQL Server,以及沒有跨平台需求的專案,ADO 真的可以完全符合需求。

安全性則可以透過 VPN 和 NAT 來解決。



DataSnap 好像又不是這麼必要了。是嗎?

對 dbExpress 的誤會

dbExpress是個「單向」和「唯讀」的資料集,嚴格來說,它只是個「游標」,連資料集都不算,有什麼好期待的?

按過往的批評dbExpress的文章來看,大部份比較的對象都是BDE和ADO。

然而,dbExpress本身就不負責BDE和ADO內三分之二的工作,要說它爛是不是有點搞錯對象。

dbExpress提供的是「架構」層面的思考,和BDE、ADO本身要求速效的概念,是先天上的不同。

深入推廣的DataSnap才是dbExpress的戰略目標。


只是戰技(Driver)和戰術(3rd Party)搭配的實在是不高明,領教過的開發者應該很明白這件事。

戰技缺點多,Driver的相容性和臭蟲不少,官方修正力道很微弱。
戰術閉塞不開,3rd Party在dbExpress官方的臭蟲影響下,也很難有伸展的空間。

甚至Devart的Document是這麼寫的:
We tried to include maximal support of server-specific features in both dbExpress drivers and DACs. However, the nature of dbExpress technology has some restrictions that cannot be overcome.

話雖如此,dbExpress概念是良好的,因為中間層的確是不需要太多狀態的資訊存在,而dbExpress也確實做到這一點,所以dbExpress的簡易和輕量化在這訴求下是必需的。

dbExpress最初立意是良好的,只是Borland手上的好牌被玩爛了,這實在是非戰之罪。

現在在行動裝置上,DataSnap也漸漸復活了,當初採用dbExpress的開發者應該開始嚐到一點開發甜頭了吧!

2015/03/08

現在是開發兩層還是三層?

dbExpress的開發畫面大概是這個樣子:
 

DataSnap的開發畫面大概是這個樣子:
Server端設計畫面
Client端設計畫面

好像有點一樣,又好像有點不一樣。
如果dbExpress改一下,就變成下面的畫面:
增加建立一個DataModule放dbExpress元件

ClientDataSet還在原來的位置,只是ProviderName會是DataModule.DataSetProvider1
相較之下,dbExpress好像只差了一個TDCOMConnection元件外,和DataSnap長相簡直一模一樣。

也難怪dbExpress要被人說閒話了,這複雜難用的神馬東西。

說易用沒有BDE來得自動化:TQuery + TUpdateSQL,BDE小小調整一下,收工。
說功能沒有ADO來得全面:光是ADOConnection的Properties內容就可以談上十餘頁A4紙;ADOQuery幾乎把BDE元件所有功能包完。

反觀dbExpress,Driver Params就只有幾項,光產出的元件就比BDE多,程式的所有細節居然還全都要人工硬嗑,就算不寫程式碼,需要看的資料也絕對不下於ADO。

那dbExpress的架構到底是什麼?Borland開發這個是拿磚頭砸自己的腳嗎?(是的,它是)


用到現在,我終於參悟了dbExpress到底是什麼,算得上是三層嗎?



如果只能用一句話來描述dbExpress的層數,那我想我會說:





「兩層以上,三層未滿」(友達以上,戀人未滿)