2018-11-26

修正 Delphi 標題 ~ Personalities

因為一台電腦要安裝多套 Delphi 開發工具,所以......
修正 Delphi Windows 上面的 Caption,讓他的辨識度更明確!

開啟 Regedit 修改:
[HKEY_CURRENT_USER\Software\Embarcadero\BDS\19.0\Personalities]
"Delphi.Win32"="Delphi 10.2.3 Tokyo"


 修正後的畫面就.... 明確多了吧!

[Delphi 無聊筆記]

2018-03-20

VirtualStringTree Column CheckBox

如何在 VirtualStringTree 上顯示 CheckBox

前面實作了 [ VirtualStringTree Load DataSet - Event ] 與 [ VirtualStringTree Filter Node ] 可以感覺得出來,VirtualStringTree 並不是最快的 Tree。所以還有另一個需求、多 Column。

A. Column 上的 CheckBox

如何在其他 Column 放上 CheckBox。呼叫 WinApi 的 DrawFrameControl 就可以輕鬆完成,比我想像中的簡單。

A1.畫上CheckBox

在 OnAfterCellPaint 開始畫。這裡可以看到 Winapi.Windows 內的樣式,不只有 CheckBox 連 RadioButton 也可以。
uses Winapi.Windows;

procedure TfrmFilterCust.vstAfterCellPaint(Sender: TBaseVirtualTree;
  TargetCanvas: TCanvas; Node: PVirtualNode; Column: TColumnIndex;
  CellRect: TRect);
var
  vANode: PCustNode;
begin
  if Column = 3 then
  begin
    vANode := Sender.GetNodeData(Node);
    if vANode.IsCustomer then
      DrawFrameControl(TargetCanvas.Handle, CellRect, DFC_BUTTON, DFCS_CHECKED)
    else
      DrawFrameControl(TargetCanvas.Handle, CellRect, DFC_BUTTON, DFCS_BUTTONCHECK);
  end
  else if Column = 4 then
  begin
    vANode := Sender.GetNodeData(Node);
    if vANode.IsSupplier then
      DrawFrameControl(TargetCanvas.Handle, CellRect, DFC_BUTTON, DFCS_CHECKED)
    else
      DrawFrameControl(TargetCanvas.Handle, CellRect, DFC_BUTTON, DFCS_BUTTONCHECK);
  end;
end;

A2.點擊CheckBox

先取得滑鼠 OnMouseDown 點選的 Node。
procedure TfrmFilterCust.vstMouseDown(Sender: TObject; Button: TMouseButton;
  Shift: TShiftState; X, Y: Integer);
begin
  FSeledNode := vst.GetNodeAt(X, Y);
end;

在 OnColumnClick 事件中修改資料屬性,在呼叫重畫 RepaintNode。
procedure TfrmFilterCust.vstColumnClick(Sender: TBaseVirtualTree;
  Column: TColumnIndex; Shift: TShiftState);
var
  vANode: PCustNode;
begin
  if Column = 3 then
  begin
    vANode := Sender.GetNodeData(FSeledNode);
    vANode.IsCustomer := not vANode.IsCustomer;
    Sender.RepaintNode(FSeledNode);
  end
  else if Column = 4 then
  begin
    vANode := Sender.GetNodeData(FSeledNode);
    vANode.IsSupplier := not vANode.IsSupplier;
    Sender.RepaintNode(FSeledNode);
  end;
end;

這樣就完成了 Column 上 CheckBox 的顯示與編輯了。

B.Header上的   CheckBox

在屬性上編輯 Header \ Columns \ Column[n] \ 設定屬性 :
CheckBox = True ,顯示 CheckBox。
CheckType = ctCheckBox ,種類是 CheckBox 還有 RadioButton
CheckState = csUncheckedNormal / csCheckedNormal ,是否有勾選。
在屬性上編輯 Header \ Options 勾選 hoShowImages
在屬性上編輯 TreeOptions \ MiscOptions 勾選 toCheckSupport

完成!好像有點單調....

範例程式:
test_FilterCust.7z


[不要忘記 CheckBox & VirtualStringTree / VirtualTreeView]

VirtualStringTree Filter Node

VirtualStringTree 過濾資料

過濾所有的 Node 當然就會依據不同需求而有不同的做法。這裡紀錄的是我在過濾 Node 學到的問題。
這裡用到的程式碼在上一段 [ VirtualStringTree Load DataSet - Event ] 實作。

A.開始發起

要來先從過濾的發起開始。A1 是最初認為的版本,開始過濾資料前後使用 BeginUpdate, EndUpdate。
procedure TfrmFilterCust.TimerFilterTimer(Sender: TObject);
var
  vDT: TDatetime;
  vStr: String;
begin
  TimerFilter.Enabled := False;
  vStr := Big5ToCn(edtFilter.Text);
  vDT := Now;

  //vst.BeginUpdate;
  Self.Cursor := crHourGlass;
  FilterNode(vStr);
  Self.Cursor := crDefault;
  //vst.EndUpdate;

  labRec.Caption := CurrToDecimal(vst.VisibleCount, 0) + '.';
  labTest.Caption :=
    CurrToDecimal((MilliSecondsBetween(Now, vDT)/1000), 3) + '/s';

  if FSeledNode <> nil then
    vst.ScrollIntoView(FSeledNode, True);
end;


B.過濾遞迴

以下的 FilterNode 是整個過濾資料的主遞迴程式,過濾的條件不說明。B1 這裡的 Root Node 是不顯示的。

最初認為的版本是有 A1 的 BeginUpdate, EndUpdate,而沒有 B2 的。這會在需要展開的時候呼叫 Expanded,但他只會觸動多個 OnInitChildren。注意的是多個 OnInitChildren 觸發完之後,回到 A1 的 EndUpdate 才觸動累積的 OnInitNode。這時候的 OnInitNode 所取得的 DataSet 永遠是最後一個 OnInitChildren 所留下的 Cursor。
function TfrmFilterCust.FilterNode(const aSubStr: string;
  ParentNode: PVirtualNode): Integer;
var
  vPosIdx: Integer;
  vCnStr: String;
  vIsRoot, vIsFound, vIsC ,vIsS: Boolean;
  vANode, vANode_up: PCustNode;
  vParentNode : PVirtualNode;
begin
  Result := 0;
  vIsRoot := (ParentNode = nil);
  if vIsRoot then
  begin
    vParentNode := vst.RootNode.FirstChild;  // B1
    vANode_up := nil;
  end
  else
  begin
    vParentNode := ParentNode.FirstChild;
    vANode_up := ParentNode.GetData;
  end;

  while vParentNode <> nil do
  begin
    vIsFound := (aSubStr = '');
    // 如 Parent 符合, Child 全部顯示 // VisibleChildNodes
    // 如 B Child 符合, Parent 顯示, 但 Other Child 不顯示
    vANode := vParentNode.GetData;
    if not vIsFound then // CustID: 交易商代碼;
    begin
      vCnStr := vANode.CustID;
      vPosIdx := Pos(aSubStr, vCnStr);

      if vPosIdx = 0 then
        vIsFound := False
      else if chkFilterL.Checked then //字首查詢
        vIsFound := vPosIdx = 1
      else if chkFilterR.Checked then //字尾查詢
        vIsFound := vPosIdx = Length(vCnStr)-Length(aSubStr)+1
      else
        vIsFound := vPosIdx > 0;
    end;
    if not vIsFound then // ShortName: 交易商簡稱;
    begin
      vCnStr := Big5ToCn(vANode.ShortName);
      vPosIdx := Pos(aSubStr, vCnStr);

      if vPosIdx = 0 then
        vIsFound := False
      else if chkFilterL.Checked then //字首查詢
        vIsFound := vPosIdx = 1
      else if chkFilterR.Checked then //字尾查詢
        vIsFound := vPosIdx = Length(vCnStr)-Length(aSubStr)+1
      else
        vIsFound := vPosIdx > 0;
    end;
    if not vIsFound then // FullName: 交易商全名;
    begin
      vCnStr := Big5ToCn(vANode.FullName);
      vPosIdx := Pos(aSubStr, vCnStr);

      if vPosIdx = 0 then
        vIsFound := False
      else if chkFilterL.Checked then //字首查詢
        vIsFound := vPosIdx = 1
      else if chkFilterR.Checked then //字尾查詢
        vIsFound := vPosIdx = Length(vCnStr)-Length(aSubStr)+1
      else
        vIsFound := vPosIdx > 0;
    end;

    if vANode_up = nil then
    begin
      vIsC := IIF(chkIsCustomer.Checked, vANode.IsCustomer, True);
      vIsS := IIF(chkIsSupplier.Checked, vANode.IsSupplier, True);
    end
    else
    begin // 如 Parent 不符合,Child 符合,就符合條件
      vIsC := IIF(chkIsCustomer.Checked, (vANode.IsCustomer or vANode_up.IsCustomer), True);
      vIsS := IIF(chkIsSupplier.Checked, (vANode.IsSupplier or vANode_up.IsSupplier), True);
    end;

    vIsFound := vIsFound and vIsC and vIsS;

    if vIsFound then
    begin
      vst.IsVisible[vParentNode] := True;
      Inc(Result);
      if vParentNode.ChildCount > 0 then
        VisibleChildNode(vParentNode);
    end
    else
    begin
      if vANode.ChildCount > 0 then
      begin
        if vParentNode.ChildCount = 0 then
        begin
          vst.BeginUpdate;  // B2
          vst.Expanded[vParentNode] := True;
          vst.EndUpdate;  // B2
        end;

        if FilterNode(aSubStr, vParentNode) > 0 then
        begin
          // 如果只有這一行,
          // 是 Parent 沒有,Child A 有,只顯示 Parent & Child A
          vst.IsVisible[vParentNode] := True;

          // 如 Parent 沒有,Child B 有,顯示 Parent & Child 全部
          VisibleChildNode(vParentNode);

          Inc(Result);
        end
        else
          vst.IsVisible[vParentNode] := False;
      end
      else
        vst.IsVisible[vParentNode] := False;
    end;

    vParentNode := vParentNode.NextSibling;
  end;
end;

速度

17.38秒:
挑一個過濾所展開符合條件的Parent,發現慢的還是 DataSet下 Filter 與 Load 資料的部分。
03.18秒:
如果上一段的測試都已經把 DataSet Load 到 Record 之後,後續的重新過濾 Node 就快多了。

範例程式:
test_FilterCust.7z

[不要忘記 Filter & VirtualStringTree / VirtualTreeView]

VirtualStringTree Load DataSet - Event

採用 Event 與 ChildCount

採用 Event 與 ChildCount 的方法,要準備如下四個步驟。Data Record、OnInitNode、OnInitChildren,OnGetText。這裡沒有提到的是事前的 Data Set 要先計算好 ChildCount。

A.Data Record

使用 Event 與 ChildCount 的方法,首先要先建立 Record。在這建立的 Record 中需要先到其Node 下有多少的 Child。
PCustNode = ^TCustNode;
 TCustNode = record
 CustID: String;
 CustID_up: String;
 ShortName: String;
 FullName: String;
 IsCustomer: Boolean;
 IsSupplier: Boolean;
 ChildCount: Integer;
 end;

B.OnInitNode

這裡的 OnInitNode 會是每一個 Node 被建立時的 Event,所以在這之前 DataSet 自己需要明確知道 Cursor 停留在哪裡。B1 是測試 OnInitNode 發生時,與 OnInitChildren 所觸動的 Filter 是否一致。當一致時、用 “=” 表示。不一致時、才用 “<>” 表示。
procedure TfrmFilterCust.vstInitNode(Sender: TBaseVirtualTree;
 ParentNode, Node: PVirtualNode; var InitialStates: TVirtualNodeInitStates);
var
 vANode: PCustNode;
 vCustID_up: String;
begin
 if ParentNode <> nil then // B1
 begin
 vANode := ParentNode.GetData;
 vCustID_up := FCDS.FieldByName('CustID_up').AsString;

 Self.Memo1.Lines.Append('ID_up:' + vANode.CustID +
 IfThen(vANode.CustID=vCustID_up, '=', '<>' + vCustID_up)
 );
 end;

 vANode := Sender.GetNodeData(Node);
 Initialize(vANode^);
 vANode.CustID := FCDS.FieldByName('CustID').AsString;
 vANode.CustID_up := FCDS.FieldByName('CustID_up').AsString;
 vANode.ShortName := FCDS.FieldByName('ShortName').AsString;
 vANode.FullName := FCDS.FieldByName('FullName').AsString;
 vANode.IsCustomer := (FCDS.FieldByName('IsCustomer').AsInteger = 1);
 vANode.IsSupplier := (FCDS.FieldByName('IsSupplier').AsInteger = 1);
 vANode.ChildCount := FCDS.FieldByName('ChildCount').AsInteger;

 if vANode.ChildCount > 0 then
 Include(InitialStates, ivsHasChildren); // B3
 //Include(InitialStates, ivsExpanded); //與 CDS.Filter 衝突。B2

 if not FCDS.eof then
 FCDS.Next;
end;

C.OnInitChildren

這個事件會在滑鼠點開收合的 Node 時、觸發。填入 ChildCount 的數量,並把 DataSet 的Cursor 到正確的位置。會被觸發的原因在 B3 標記了 InitialStates 具有 ivsHasChildren 特性,所以 C1 的判斷是預防。如果資料結構完整也可以省略。已經展開過後,並不會重複數發。事件後會依據 ChildCount 的數量在去處發 B.OnInitNode,但在 BeginUpdate, EndUpdate 的搭配上會有例外!會再另外做說明。
procedure TfrmFilterCust.vstInitChildren(
  Sender: TBaseVirtualTree; Node: PVirtualNode; var ChildCount: Cardinal);
var
  vCustID_up: String;
  vANode: PCustNode;
begin
  vANode := Sender.GetNodeData(Node);
  ChildCount := vANode.ChildCount;
  if ChildCount > 0 then  // C1
  begin
    vCustID_up := vANode.CustID;
    FCDS.Filtered := False;
    FCDS.Filter := 'CustID_up = ''' + vCustID_up + '''';
    FCDS.Filtered := True;
  end;
end;

D.OnGetText

這裡就最簡單的,把 B 所填入的 A.Record 回寫給 CellText,也就是 Node 表現的文字。在這裡 Column 3, 4 使用的是圖形化表示,所以不要給他文字內容。
procedure TfrmFilterCust.vstGetText(Sender: TBaseVirtualTree;
  Node: PVirtualNode; Column: TColumnIndex; TextType: TVSTTextType;
  var CellText: string);
var
  vANode: PCustNode;
begin
  vANode := Sender.GetNodeData(Node);
  if Column = 0 then
    CellText := vANode.CustID
  else if Column = 1 then
    CellText := vANode.ShortName
  else if Column = 2 then
    CellText := vANode.FullName
  else
    CellText := '';
end;

E.開始發起

上面四個步驟都準備好之後,就可以發起 VirtualStringTree 開始動作。E1 是當有設定ReadOnly 時,程式碼無法異動物件內容。這裡的 E2, E5 其實也可以不用設定,當E3的資料排列順序正確的時候。E4 給予 Root Node 有幾個 Child Node 要建立,與 C 的意義向同。注意在Tracer 的時候可以發現在 EndUpdate 之後,觸發 B 的 OnInitNode。而這一點很重要!這裡的Root 是看不見的底層。E5 的 SortTree 有網路解釋他會強迫每的 Node 觸發事件 OnInitNode 取得所有 Data 到 Record,其意思與 E6 的 FullExpand 相同。但測試解果在 BeginUpdate, EndUpdate 之中,不會如此。
procedure TfrmFilterCust.FormShow(Sender: TObject);
var
  isReadOnly: Boolean;
  vDT: TDatetime;
begin
  FCDS.DisableControls;
  FCDS.IndexFieldNames := 'CustID_up;CustID';  // E3
  if not FCDS.Active then
    FCDS.Active := True;

  begin
    isReadOnly := (toReadOnly in vst.TreeOptions.MiscOptions);  // E1
    if isReadOnly then
      with vst.TreeOptions do
        MiscOptions := MiscOptions - [toReadOnly];

    vDT := Now;
    vst.BeginUpdate;
    vst.Clear;
    vst.NodeDataSize := Sizeof(TCustNode);

    FCDS.Filtered := False;
    FCDS.Filter := 'CustID_up is NULL';  // E2
    FCDS.Filtered := True;
    FCDS.First;
    vst.RootNodeCount := FCDS.RecordCount;  // E4

    vst.SortTree(0, sdAscending, true);  // E5

    vst.EndUpdate;
    //vst.FullExpand;  // E6

    labTest.Caption :=
      CurrToDecimal((MilliSecondsBetween(Now, vDT)/1000), 3) + '/s';

    if isReadOnly then
      with vst.TreeOptions do
        MiscOptions := MiscOptions + [toReadOnly];
    labRec.Caption := CurrToDecimal(vst.VisibleCount, 0) + '.';
  end;
end;

速度

00.03秒:
VirtualStringTree 速度為什麼快,在於它的預設發起之動作、只實作第一層OnInitNode。如果 Node A 有 Child 會在 B3 加入 InitialStates 標記 ivsHasChildren,畫面上會出現合併前的收合十字。
33.33秒:
而要全部展開 E6 的 FullExpand 是一個辦法,在我的案例它耗近了40秒的漫長時間。另一個辦法會在說明。
範例程式:
test_FilterCust.7z
如何過濾資料 [ VirtualStringTree Filter Node ]
如何在 Column 上顯示 CheckBox [ VirtualStringTree Column CheckBox ]

[不要忘記 DataSet & VirtualStringTree / VirtualTreeView]

2018-03-06

Windows Auto Login Registry & DefaultDomainName

設定 Windows 自動登入電腦。真的是常常忘記,只為了記錄沒有別的。
  1. 開啟:regedit
  2. 進入註冊碼:HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon
  3. 設定自動Login開啟:AutoAdminLogon = 1
  4. 設定Login使用者ID:DefaultUserName = Jasper
  5. 設定Login密碼:DefaultPassword = 1438
  6. 如果您有加入網域,設定Login網域:DefaultDomainName = CCTV
[DefaultDomainName]

2018-02-06

Delphi 10.2.2 Tokyo Collect

Delphi 10.2.2 Tokyo 個人蒐集,請注意、這是我個人的蒐集。


IDDescriptionDate


2017
30808ISO for RAD Studio, Delphi, C++Builder 10.2 Release 2

A Double Layer high capacity DVD is required for burning a physical disc.

This CodeCentral entry was updated on Dec 17, 2017
build 2004 replaces the previously released RAD Studio 10.2.2 build 1978. The updated 10.2.2 build requires a full uninstall and reinstall.
12/17
URL10.2 Tokyo - Release 2 consists of new features, enhancements, and bug fixes.

Several key new features were added, including FireMonkey Quick Edit support, a number of new VCL controls, a new dark themed IDE option, a RAD Server single site deployment license in Enterprise and Architect editions, installer and welcome page enhancements and more.

http://edn.embarcadero.com/article/44770
12/17


2018
URLIt is a new function of 10.2.2, and it can be outputted by Ext JS store definition.

TFDBatchMoveJSONWriter is a Writer that combines with TFDBatchMove to out to a simple JSON. But, TFDBatchMoveJSONWriter has the function of generating Model definition of Ext JS.
1/1
30818RAD Studio 10.2.2 Welcome Page Patch

This patch resolves an issue with the page width in the Documentation tab of the Welcome Page, and provides additional improvements in the appearance and layout of the main tab of the Welcome page.The provided zip file replaces the existing Welcome Page directory with updated files.
1/16
22778Tool to compile Package Group files( .bpg / .bdsproj / .groupproj) and automatically install the components into IDE. For D5 to XE10.2.

https://sourceforge.net/projects/delphipackageto/
1/17

[Delphi 10.2.2]

2018-01-16

PowerDesigner 版本清單

PowerDesigner Version List
http://www.powerdesigner.biz/EN/powerdesigner/powerdesigner-licensing-history.php

http://www.powerdesigner.biz/EN/powerdesigner/powerdesigner-history.html

還記得遇到好心的陳副總,針對我的疑問後給我建議採用 PowerSoft 旗下的 PowerDesigner。一路用到現在、經過 Sybase 也好、現在的 SAP 也好。現在回頭想想,它幫助我好大啊,讓我有更多的心力應付更廣泛的世界。

感謝,謝謝陳副總,也謝謝 PowerDesigner。

Delphi 版本清單

Delphi/Pascal Version List


還記得第一次與 Turbo Pascal 接觸吧.....
還記得第一次老師展示 Delphi 1.0 beta.....
還記得第一次採用 Delphi 2.0 的工作.....
真的不可思議的回憶!

SQL Server 版本清單

SQL Server Version List

Widnows 版本清單

Windows Version Numbers
https://www.gaijin.at/en/lstwinver.php