避坑指南)
簡介面向Delphi 13.1開發(fā)者的KonopkaControls 8.0.1控件包是擴(kuò)展VCL標(biāo)準(zhǔn)組件庫的第三方界面控件集合能有效補(bǔ)充原生控件在界面表現(xiàn)與交互能力上的不足適合桌面應(yīng)用開發(fā)及快速原型構(gòu)建。壓縮包共2000個文件包含1128個PNG圖片資源、263個HPP頭文件和259個DCU編譯單元另有DFM窗體、PAS源碼、BPL運(yùn)行包、CHM幫助文檔、DPK描述文件及部署腳本等全部內(nèi)容按源碼、庫目錄、幫助、示例與部署工具分類存放便于按需檢索和二次開發(fā)。該控件包包體僅為25.41MB已有89人學(xué)習(xí)與下載使用。資源附完整源代碼、編譯好的運(yùn)行庫、KSVC變更記錄與Demo工程既能直接集成到既有項目也可通過閱讀PAS和DFM文件弄清控件實現(xiàn)機(jī)制。對于希望提升Delphi界面開發(fā)效率或研究VCL控件封裝方式的開發(fā)者該資源具備明確參考價值尤其適合有Object Pascal基礎(chǔ)的入門與進(jìn)階者。1. 為什么我還在裝 KonopkaControls一個 For13.0 的控件包憑什么讓老 Delphi 項目續(xù)命學(xué)習(xí) Delphi 的人通常第一課是擺一個 Button但真正接手一個跑了十多年的老工程時你才會發(fā)現(xiàn)界面上的按鈕、標(biāo)簽頁、狀態(tài)欄早就不全是原生 VCL而是靠第三方控件撐著。KonopkaControls簡稱 KNC就是這類控件里最常見的面孔之一一套包含按鈕、Panel、Tab、樹、列表、狀態(tài)欄等幾十個控件的源碼包體積不大卻能把舊項目的 UI 骨架穩(wěn)穩(wěn)保住。標(biāo)題里 For13.0 的 13指的不是 Delphi 的營銷版本號而是 DCC 編譯器版本號對應(yīng)的是 Delphi 7 時代的產(chǎn)品線而 8.0.1 是 KNC 的發(fā)行版本370 是構(gòu)建序號。這篇我就按實際安裝順序把解壓、編譯、注冊進(jìn) IDE、在數(shù)據(jù)庫 TreeView 和 Panel 圓角場景里跑起來以及最容易翻車的幾個點一次講完。適合正在接手舊工程、又不想被控件初始化問題卡住的人。2. 先拆包KNC 到底是一套什么控件8.0.1 又代表什么拿到KonopkaControls-370-8.0.1-For13.0.zip第一件事不是急著解壓而是先弄明白壓縮包里裝的是什么、版本號怎么讀。KNC 是一個純粹的 VCL 控件集主要服務(wù)于 Win32 傳統(tǒng)界面開發(fā)不做跨平臺不做現(xiàn)代扁平化皮膚但它有一個特殊價值源碼完全開放編譯產(chǎn)物只有 .bpl/.dcp沒有任何第三方圖形庫依賴。2.1 為什么遺留工程離不開這套控件KNC 的歷史可以追溯到 Delphi 5/6/7 時代很多企業(yè)級系統(tǒng)選它不是因為好看而是因為穩(wěn)定。它提供的控件覆蓋了傳統(tǒng)業(yè)務(wù)系統(tǒng)最常見的交互帶狀態(tài)的按鈕、可自繪的面板、多頁簽切換、樹形菜單、狀態(tài)欄、列表視圖、日期選擇等。這些控件在標(biāo)準(zhǔn) VCL 里都能找到同類但 KNC 做了一層封裝讓“統(tǒng)一風(fēng)格”這件事變得容易。另一個現(xiàn)實原因是被迫繼承。很多舊項目在創(chuàng)建時就用 KNC 替代了部分原生控件窗體上遍布TKnCButton、TKnCPanel之類的組件類型。你接手后如果不裝這套控件IDE 連窗體都打不開會直接報“無法解析類名”。所以不管它好不好看先把編譯環(huán)境復(fù)原是接手此類項目的第一優(yōu)先級。如果你是新項目選型我的建議是謹(jǐn)慎一點KNC 適合做傳統(tǒng)管理軟件界面但如果你要的是圓角大卡片、動畫過渡、深色主題那它顯然不是最佳選擇。2.2 版本號里的三重信息370、8.0.1、For13.0壓縮包文件名里藏著三組信息分別對應(yīng)構(gòu)建、發(fā)行和 IDE 兼容標(biāo)記。把它們讀懂你在裝錯版本時就能更快反應(yīng)過來。字段含義影響370構(gòu)建序號表示這是第 370 次構(gòu)建實際使用中幾乎不用關(guān)注8.0.1發(fā)行版本號決定源碼 API 是否變化不要跨大版本混用For13.0DCC 編譯器版本標(biāo)記決定能裝進(jìn)哪一個 Delphi IDE這部分最關(guān)鍵這里的 For13.0 最容易誤解。Borland 的編譯器版本號和產(chǎn)品名并不是同一套數(shù)字Delphi 7 的 DCC 編譯器版本就是 13.0所以 For13.0 實際是說“這套源碼對應(yīng) Delphi 7 的工具鏈”。而標(biāo)題里寫的 Delphi 13.1更像下載站點自己的分類叫法不是 Embarcadero 的官方命名。你安裝時唯一要確認(rèn)的就是本機(jī)編譯器的版本字符串與控件包要求一致否則編譯期會報版本不匹配常見提示是Invalid compiler version或者Package ... was compiled with a different version of Delphi。2.3 解壓后你該看到的目錄結(jié)構(gòu)解壓這類源碼包時我會先看頂層目錄里有沒有Source、Docs、Demo、Bin這四個目錄。KNC 的正常分發(fā)形態(tài)基本遵循這套規(guī)矩具體名稱可能有差異但邏輯一致。目錄作用是否必須Source/Runtime運(yùn)行期控件源碼編譯 .bpl 用必須Source/Design設(shè)計期注冊與屬性編輯器源碼必須Docs幫助文檔、版本變更記錄可選Demo示例工程看你實際需要可選Bin預(yù)編譯的 .bpl/.dcp可選建議自己編譯不要直接拿別人的 Bin 目錄里的 .bpl 往 IDE 里塞。原因很簡單那些預(yù)編譯包可能是 Debug 版或者編譯選項和你機(jī)器不一致輕則 IDE 提示缺少rtl70.bpl重則設(shè)計期直接崩掉。自己從源碼編譯一次整個過程也就幾分鐘后續(xù)排查問題還能直接進(jìn)源碼這是最穩(wěn)妥的路線。3. 把 KonopkaControls 裝進(jìn) Delphi IDEdpk 編譯與設(shè)計期注冊安裝 KNC 的核心不是解壓也不是復(fù)制文件而是把 .dpkDelphi Package 工程編譯成運(yùn)行期包和設(shè)計期包再讓 IDE 在設(shè)計期加載。理解這兩個概念后面所有坑都好解決。3.1 安裝前的環(huán)境準(zhǔn)備路徑、庫目錄、包名解壓路徑最好不要放在帶有空格或者中文的深層目錄里某些老編譯器對路徑處理沒有現(xiàn)代 IDE 那么寬容。我一般習(xí)慣壓在D:\Components\KonopkaControls-8.0.1這類結(jié)構(gòu)下。接下來要做的第一件事是把源碼目錄加進(jìn) Delphi 的庫搜索路徑否則編譯時找不到KncConst.pas這類公共單元。以 Delphi 7 為例打開Tools - Environment Options - Library - Library Path把D:\Components\KonopkaControls-8.0.1\Source\Runtime和D:\Components\KonopkaControls-8.0.1\Source\Design都加進(jìn)去。注意不要只加根目錄因為源碼文件往往分散在子目錄里。如果你用的是 RAD Studio 10.3 之后的版本路徑入口在Tools - Options - Environment Options - Delphi Options - Library同樣追加 Source 目錄。一個容易被忽略的細(xì)節(jié)環(huán)境變量里的BDS指向當(dāng)前 IDE 的根目錄很多構(gòu)建腳本會依賴它。所以在命令行里安裝時建議先確認(rèn)這個變量存在避免后續(xù) dcc32 找不到基礎(chǔ)包。3.2 用命令行編譯運(yùn)行期包和設(shè)計期包命令行編譯的好處是可控、可重復(fù)比在 IDE 里點點點更容易定位問題。先打開一個 CMD 窗口逐行執(zhí)行下面的命令。set KNC_HOMED:\Components\KonopkaControls-8.0.1 set DELPHI_BINC:\Program Files\Borland\Delphi7\Bin cd /d %KNC_HOME%\Source\Runtime %DELPHI_BIN%\dcc32.exe -B -Q KNC_R.dpk if errorlevel 1 goto :err cd /d %KNC_HOME%\Source\Design %DELPHI_BIN%\dcc32.exe -B -Q dclKNC.dpk if errorlevel 1 goto :err echo OK goto :end :err echo Build Failed :end這段腳本先編譯運(yùn)行期包KNC_R.dpk再編譯設(shè)計期包dclKNC.dpk。-B 表示強(qiáng)制全部重建避免增量編譯緩存帶來莫名其妙的問題-Q 是安靜模式只輸出錯誤和警告if errorlevel 1判斷上一條命令是否失敗。文件名的具體寫法以壓縮包里的 .dpk 為準(zhǔn)如果運(yùn)行期包叫KNC_Runtime.dpk就把KNC_R.dpk換掉。這里要特別說明為什么必須先編譯運(yùn)行期包。設(shè)計期包內(nèi)部會requires運(yùn)行期包比如聲明requires KNC_R;如果你還沒有編譯出運(yùn)行期包對應(yīng)的 .dcp/.bpl設(shè)計期包編譯時就會報“找不到 KNC_R”。這也是為什么我不建議在一個 dcc32 命令里同時編譯兩個包去碰運(yùn)氣分開編譯能更早暴露缺口。新版 RAD Studio 里 .dpk 已經(jīng)退居次要IDE 默認(rèn)生成 .dproj 工程。安裝時用 msbuild 更合適msbuild dclKNC.dproj /p:ConfigRelease /p:PlatformWin32ConfigRelease保證生成的是 Release 版運(yùn)行期包避免把調(diào)試符號帶進(jìn)最終發(fā)布PlatformWin32是因為這類 VCL 源碼大多還沒遷移到 Win64如果你在 64 位設(shè)計期環(huán)境里安裝出錯優(yōu)先把 IDE 的 Target Platform 切到 Win32 再測試。3.3 設(shè)計期包安裝后的驗證方法編譯完成后打開 Delphi IDE進(jìn)入Component - Install Packages點Add選擇剛才生成的dclKNC.bpl。如果此時 IDE 提示“Package already exists”說明以前裝過需要先移除舊版本不要強(qiáng)行覆蓋。安裝成功后打開工具面板找到新增的組件頁頁面名稱通常是KNC或者Konopka。最簡單的驗證方式是新建一個 Application往窗體上拖一個 KNC 按鈕和一個 KNC 狀態(tài)欄確認(rèn)屬性編輯器和對象管理器都能正常顯示。如果拖組件時 IDE 崩潰直接進(jìn)第 5 章排查最常見原因是運(yùn)行期包沒先編譯、或者運(yùn)行期包路徑?jīng)]加入環(huán)境變量。4. 在真實項目里用 KNC組件選型、數(shù)據(jù)庫 TreeView 與界面細(xì)節(jié)裝完包只是開始真正讓人頭疼的是“怎么用”。這一章我按現(xiàn)實業(yè)務(wù)場景來聊先挑出值得優(yōu)先認(rèn)識的幾個控件再講數(shù)據(jù)庫驅(qū)動 TreeView 的填充套路最后處理 Panel 圓角和子控件間距這兩個搜索頻率極高的小需求。4.1 值得優(yōu)先使用的幾個 KNC 控件KNC 的控件數(shù)量不少但真正高頻率使用的是下面這幾個控件用途替代原生控件KNCButton帶自繪狀態(tài)的按鈕TButton/TSpeedButtonKNCPanel可自繪面板容器TPanelKNCTabBar多頁簽切換TTabControlKNCStatusBar狀態(tài)欄信息展示TStatusBarKNCList列表視圖TListViewKNCTree樹形結(jié)構(gòu)TTreeView我通常會把 KNCButton 用于工具欄和功能按鈕因為它支持自繪按下狀態(tài)界面看起來更像桌面客戶端。但要注意它不負(fù)責(zé)解決瀏覽器類問題KNC 里沒有 WebBrowser 控件HTML 展示依然要用標(biāo)準(zhǔn) TWebBrowser這是很多新人的預(yù)期偏差先講清楚免得后面找控件找到懷疑人生。4.2 讓 KNC 的樹控件通過數(shù)據(jù)庫驅(qū)動字段映射與刷新業(yè)務(wù)系統(tǒng)里最常見的樹是“部門樹”和“菜單樹”數(shù)據(jù)在數(shù)據(jù)庫里就是一張帶有 PID 的表。給 KNC 樹控件填數(shù)據(jù)時不需要關(guān)注控件內(nèi)部實現(xiàn)只用遞歸在內(nèi)存里搭節(jié)點樹再賦值給控件即可。下面是標(biāo)準(zhǔn)寫法procedure LoadTreeFromDataset( ATree: TTreeView; ADataSet: TDataSet; AIdField, AParentField, ANameField: string); var NodeList: TStringList; I: Integer; Node: TTreeNode; ParentNode: TTreeNode; ParentId: string; begin ATree.Items.BeginUpdate; try ATree.Items.Clear; NodeList : TStringList.Create; try ADataSet.First; while not ADataSet.Eof do begin Node : ATree.Items.Add(nil, ADataSet.FieldByName(ANameField).AsString); Node.Data : Pointer(ADataSet.FieldByName(AIdField).AsInteger); NodeList.AddObject(ADataSet.FieldByName(AIdField).AsString, Node); ADataSet.Next; end; ADataSet.First; while not ADataSet.Eof do begin ParentId : ADataSet.FieldByName(AParentField).AsString; if ParentId then begin I : NodeList.IndexOf(AParentField ParentId); if I 0 then begin ParentNode : TTreeNode(NodeList.Objects[I]); ATree.Items.AddChild(ParentNode, ADataSet.FieldByName(ANameField).AsString) .Data : Pointer(ADataSet.FieldByName(AIdField).AsInteger); end; end; ADataSet.Next; end; finally NodeList.Free; end; finally ATree.Items.EndUpdate; end; end;這段代碼的邏輯分兩遍第一遍先按數(shù)據(jù)庫順序把每條記錄都生成根節(jié)點并把 ID 存到Node.Data第二遍再根據(jù) PID 把記錄移動到對應(yīng)父節(jié)點下。用兩個循環(huán)是為了避免出現(xiàn)父節(jié)點還沒創(chuàng)建時子節(jié)點無處掛靠的問題。BeginUpdate/EndUpdate防止刷新過程中界面閃爍NodeList用字符串做索引是因為在數(shù)據(jù)量不大時字符串查找比遍歷 TTreeNode 更直觀。實際使用時如果表里 ID 是整數(shù)序列建議把AIdField和AParentField做成Integer參數(shù)速度會更好。4.3 Panel 圓角、子控件間距與自繪常見參數(shù)“Panel 控件圓角”和“子控件間距”是 Delphi 開發(fā)者經(jīng)常搜的兩類問題。KNC 面板自身不一定提供圓角屬性但你可以借助 Win32 區(qū)域設(shè)置做統(tǒng)一處理。圓角本質(zhì)上是把窗口客戶區(qū)域切成圓角矩形Windows 自帶的CreateRoundRectRgn就能實現(xiàn)。procedure MakeRoundedPanel(APanel: TPanel; ARadius: Integer); var HRgn: HRGN; begin HRgn : CreateRoundRectRgn( 0, 0, APanel.Width, APanel.Height, ARadius, ARadius); SetWindowRgn(APanel.Handle, HRgn, True); DeleteObject(HRgn); end;CreateRoundRectRgn的四個前參數(shù)是區(qū)域左上角和右下角坐標(biāo)后兩個參數(shù)分別是橢圓寬高也就是圓角半徑。半徑設(shè)成12適合一般管理界面設(shè)成24就是明顯的大圓角。需要注意的是SetWindowRgn接管后窗口系統(tǒng)不再對區(qū)域外做命中測試所以不要把半徑設(shè)到面板寬度的三分之一以上否則觸碰到圓角邊緣時按鈕會“點不中”。子控件間距更推薦用 VCL 自帶的對齊體系解決而不是手動設(shè)置Left/Top。做法是給容器內(nèi)每個子控件設(shè)Align alTop或alLeft再通過Margins屬性控制外間距。KNC 控件繼承自標(biāo)準(zhǔn) VCL 控件所以Margins.Left/Right/Top/Bottom這幾個參數(shù)都有效。這樣窗口調(diào)整大小時間距會自動縮放不會出現(xiàn)控件疊在一起的翻車現(xiàn)場。5. 避坑清單安裝、運(yùn)行與升級最容易翻車的 5 個地方這一章全是血淚經(jīng)驗。KNC 這類老牌第三方控件包坑點不在功能而在環(huán)境適配。下面每一條都按“現(xiàn)象 - 原因 - 解決”來寫。5.1 現(xiàn)象設(shè)計期包明明安裝了IDE 里卻還提示需要安裝有時候你點完 Install PackagesIDE 也顯示加載成功但新建窗體拖 KNC 組件時還是提示類不存在。原因多是 IDE 緩存了舊的設(shè)計期包索引或者安裝的是 64 位設(shè)計期包而工程運(yùn)行平臺是 Win32。解決方法是先用Component - Install Packages把舊包移除關(guān)閉 IDE刪除用戶目錄下的*.bpl緩存的.drc/.dcu文件再重新打開 IDE 安裝。如果還不夠檢查Tools - Options里的 Delphi 庫路徑是否包含 Source 目錄缺少的話編譯器能編譯但設(shè)計期找不到類型聲明。5.2 現(xiàn)象編譯按鈕時報 File not found: KncConst.pas這是源碼包安裝中最常見的報錯。KncConst.pas是 KNC 的公共常量單元它不在某個具體控件目錄里而是放在 Source 根目錄或者公共目錄中。報錯原因基本是庫搜索路徑只加了阻塞的單個子目錄編譯器在其他單元里uses KncConst時根本找不到文件。解決方法是把整個 KNC 的 Source 目錄樹全部加進(jìn) Library Path而不是只加其中一個文件夾。解壓時也盡量保持目錄結(jié)構(gòu)不變不要只復(fù)制部分文件。5.3 現(xiàn)象程序在 IDE 里正常跑雙擊 exe 卻閃退這種問題在 IDE 里看不出來因為 IDE 會自動加載運(yùn)行期包。但脫離 IDE 后exe 動態(tài)鏈接到 KNC 的 .bpl如果目標(biāo)機(jī)器上沒有這些 .bpl程序啟動時就會提示“無法找到 KNC_R.bpl”然后退出。解決方法是發(fā)布時把運(yùn)行期包一起帶上。常見做法是把.bpl文件放在 exe 同目錄或者用 Inno Setup 在[Files]段顯式復(fù)制到 System32。更穩(wěn)妥的方案是在工程里把Runtime Packages設(shè)為Build with runtime packages off這樣生成的 exe 把控件代碼直接鏈接進(jìn)主程序發(fā)布時只帶rtl70.bpl和vcl70.bpl目標(biāo)機(jī)器就算沒裝 Delphi 也能跑。5.4 現(xiàn)象KNC 控件搶焦點Tab 鍵順序亂跳KNC 里的自繪按鈕在獲得焦點時會通過消息機(jī)制改變繪制狀態(tài)。如果你在窗體里混用了原生 TButton 和 KNCButton可能會出現(xiàn)按 Tab 后焦點不進(jìn)入 KNC 控件、或者進(jìn)入后無法正常離開的問題。原因是設(shè)計期包的 Windows 消息處理與原生控件不同而 KNC 的WM_GETDLGCODE必須返回正確的標(biāo)志位否則對話框?qū)Ш竭壿嫊雎运?。解決方法是檢查該控件是否有TabStop屬性確認(rèn)設(shè)為 True同時在Form.OnShow里調(diào)用一次Control.SetFocus再移到別處強(qiáng)制刷新焦點鏈。如果還不行看看是不是把 KNCButton 放進(jìn)了另一個自繪容器容器需要設(shè)置ControlStyle里的csAcceptsControls。5.5 現(xiàn)象從 Delphi 7 遷移到新版 Delphi 后中文顯示亂碼KNC 的源碼寫于 ANSI 時代字符串常量多是用String保存新版 Delphi 默認(rèn) UnicodeString 后字符集轉(zhuǎn)換會出現(xiàn)亂碼尤其表現(xiàn)在狀態(tài)欄和列表列頭上。原因不是控件壞了而是單元文件里沒有指定代碼頁編譯器按當(dāng)前系統(tǒng) ANSI 解析。解決方法有兩個方向一是到項目設(shè)置里把Default codepage設(shè)成 936讓 ANSI 字符串按 GBK 解釋二是把所有界面展示字符串改用 Tnt 控件或者直接改成WideString。如果你只是臨時遷移方法一最快如果想長期維護(hù)建議用TEncoding.UTF8.GetString做一層讀取轉(zhuǎn)換而不是一個控件一個控件去改。6. 用源碼級技巧繼續(xù)壓榨 KNC自定義圓角、多線程刷新與調(diào)試黑匣子最后一章我不寫安裝流程而是說幾個能直接提升手感的小技巧。這些技巧不一定都在 KNC 官方文檔里但它們能幫你把黑匣子變成透明盒子。關(guān)于圓角前面那段MakeRoundedPanel已經(jīng)夠用但如果你不想每個窗體都復(fù)制一遍可以把這段代碼放進(jìn)一個公共單元然后在窗體OnShow里統(tǒng)一循環(huán)處理比如遍歷容器內(nèi)的所有 TPanel 并調(diào)用圓角函數(shù)。這樣做的好處是團(tuán)隊里其他人不需要理解 Win32 API一行公共函數(shù)就能解決所有面板圓角需求。參數(shù)上我會固定成ARadius 12避免不同窗體圓角大小不一致導(dǎo)致界面風(fēng)格分裂。關(guān)于多線程刷新KNC 控件和普通 VCL 控件一樣不能在工作線程里直接修改Caption或Text否則會出現(xiàn)隨機(jī)性閃退。我一般使用Synchronize配合消息隊列把數(shù)據(jù)庫查詢結(jié)果打包進(jìn)自定義消息再在窗口函數(shù)里統(tǒng)一刷新樹控件。你不需要引入額外的庫Delphi 自帶的Winapi.Windows.PostMessage就能做到。注意一點PostMessage的 WParam/LParam 只能傳整數(shù)傳字符串要把字符串地址提前分配好否則消息處理時地址已經(jīng)釋放查起來非常費(fèi)時間。最后說一個調(diào)試習(xí)慣KNC 這類控件源碼里通常有{$IFDEF DEBUG}分支你可以在自己的工程里定義一個KNC_DEBUG條件編譯把控件關(guān)鍵方法的入口和出口打印到日志文件。遇到控件行為詭異時先看日志再斷點不要一上來就翻代碼。這個習(xí)慣幫我省掉過很多次無意義的單步調(diào)試尤其是 5.4 那種焦點問題日志比斷點更能快速定位是哪一層控件吞了消息。希望這些經(jīng)驗?zāi)軒偷侥闵僮呶耶?dāng)年走過的彎路。本文還有配套的精品資源點擊獲取