电竞比分网-中国电竞赛事及体育赛事平台

分享

VFP 的Grid表格透析

 Alkaid2015 2012-07-19
評(píng)論:Grid是VFP很重要的一個(gè)控制 項(xiàng).可不要把VB的Grid來看待喔!
否則你會(huì)用的很沮喪.不管新手或老手.我都建議他.有空多吸收此文章.

VFP 的Grid表格透析

RMH 於 2001-12-17 12:41:05 發(fā)表:
作者 Vlad Grynchyshyn
譯者 RMH

VFP 中的表格 第一部分

什麼是表格和什麼時(shí)候使用它?

表格控制項(xiàng)是一組允許在一個(gè)表格一樣的可捲動(dòng)列表中顯示資料的 VFP 物件. 表格由表格物件自己和一組列組成. 各列必須有一個(gè) header 物件和在表格列中顯示資料的控制項(xiàng). 表格列中的控制項(xiàng)用於顯示和編輯資料. 表格是一個(gè)帶格線條的矩形, 和頂部的標(biāo)題, 捲動(dòng)條和一些其他有用的東西, 如記錄標(biāo)記, 刪除標(biāo)記, 分隔條等.

說實(shí)話, 表格顯示資料不象 Excel sheet 那樣自由. 表格要求用 VFP 記錄源 (別名) 來顯示一些東西. 要在同一列的不同行中顯示不同類型的資料是困難的. 表格中的所有行的高度都是相同的, 並且表格中一列中的所有行的寬度也是相同的. 當(dāng)然還有一些其他相當(dāng)奇怪的和不可思議的限制, 除非我們記住表格是一個(gè)真實(shí)的基於早期版本的 FoxPro 的流覽視窗來編寫的控制項(xiàng). 這一事實(shí)回答了許多關(guān)於表格的奇怪的東西和行為的 &為什麼&. 儘管有許多的麻煩, 表格還是相當(dāng)有用的控制項(xiàng)並在受到 VFP 程式師的歡迎, 因此也有許多的處理和方案來打破表格的限制並用它來產(chǎn)生更好的效果. 局限或奇怪的東西將不再是決定是否使用表格的關(guān)鍵.

表格控制項(xiàng)對(duì)於在一個(gè)簡(jiǎn)潔的表單中顯示(流覽)資料是有用的 (每頁顯示大量資料). 表格對(duì)於搜索和定位資料比彈式功能表或整頁的下拉清單好. 持久穩(wěn)固的使用表格作為定位資料不是一個(gè)好的主意, 因?yàn)楸砀裢ǔ?zhàn)用許多空間. 許多應(yīng)用程式有一個(gè)表格作為一個(gè)主要的控制表單 - 複雜的帶有許多功能的可顯示子表單來進(jìn)行資料編輯的表格.

表格對(duì)於所有類型的規(guī)則的唯讀資料是有用的. 使用表格進(jìn)行資料編輯不是一個(gè)好主意. 在表格中進(jìn)行資料編輯的好處僅僅是易於組織和管理應(yīng)用程式. 在表格中進(jìn)行資料編輯時(shí)如果要打破 VFP 表格的限制會(huì)有許多問題. 但是, 例如發(fā)票或訂單表單的行項(xiàng), 使用表格來進(jìn)行編輯是更好的, 用戶也會(huì)感到更舒服. 這樣的表單是一個(gè)例外; 不要僅僅因?yàn)楸砀駥?duì)於編輯資料時(shí)的簡(jiǎn)單就把它放在每一個(gè)表單中. 當(dāng)你這樣做時(shí), 你會(huì)很快發(fā)現(xiàn)自己處於麻煩之中, 因?yàn)楸砀袷侨绱搜}雜且易於失去控制的控制項(xiàng). 這將要求你付出更多的努力來指出問題, 找出解決方案和修正更多的問題. 通常, 資料編輯使用文本框顯示欄位和帶有定位,保存/恢復(fù)和一些其他特定按鈕工具欄來移動(dòng)記錄的的表單. 為表格生成一個(gè)這樣的表單是花費(fèi)大量時(shí)間的一種方法. 當(dāng)然, 如果你有時(shí)間並喜歡玩耍怪異的東西, 表格是一種消磨時(shí)光並找出方案和處理辦法的最好的控制項(xiàng).


表格自動(dòng)進(jìn)行列的重綁定

表格中最常出現(xiàn)的奇怪的東西是列的 control sources 的自動(dòng)改變. 你會(huì)發(fā)現(xiàn)突然表格列顯示了其他的不是你在列的 ControlSource 屬性中指定的資料. 另外, 列中顯示的資料的順序不是你重新安排過的順序. 為什麼會(huì)這樣?

======================================================
因?yàn)楸砀竦?RecordSource 屬性在設(shè)計(jì)時(shí)被改變. 在表格的 RecordSource 屬性被改變後所有的 ControlSource 值被清除了. 在此情況下製作一個(gè)這些值的備份, 通常是在列的 Comment 屬性中.

因?yàn)楸砀竦?RecordSource 屬性在運(yùn)行時(shí)被改變. 好了, 如果你要這樣做, 保存所有的 control sources, 然後恢復(fù)它們.

可能的原因是表格被重建了. 關(guān)於這一點(diǎn)將在下一章中說明.
======================================================

該行為的副作用並不嚴(yán)重. 通常顯示的資料沒有改變, 除非表格有一些複雜的功能和結(jié)構(gòu)體系. 例如, 列值顯示的是運(yùn)算式 - 運(yùn)算式將丟失並只顯示欄位. 對(duì)於用戶來說在表格列中顯示 ID 欄位 (主關(guān)鍵字段)也不是一件好事. 最危險(xiǎn)的是當(dāng)你在表格中有一些不同的控制項(xiàng)時(shí). 當(dāng)列中有一個(gè)核取方塊控制項(xiàng)時(shí), 但表格決定使用字元型欄位作為該列的 control source 時(shí), VFP 將顯示一個(gè)關(guān)於資料類型失配的錯(cuò)誤: &控制項(xiàng)不支援該資料類型&.

無論是上述何種情況, 當(dāng)以任何方式改變 record source 時(shí), 都有消除表格重建的方案.

以下示例代碼用列的 Comment 屬性保存 control sources 並恢復(fù)它們.

&& 備份各列的 ControlSource
with {grid}
local nColumnIndex
for m.nColumnIndex = 1 to .ColumnCount
.Columns(m.nColumnIndex).Comment = .Columns(m.nColumnIndex).ControlSource
endfor
endwith

&& 恢復(fù)各列的 ControlSource
with {grid}
local nColumnIndex
for m.nColumnIndex = 1 to .ColumnCount
if !empty(.Columns(m.nColumnIndex).Comment)
.Columns(m.nColumnIndex).ControlSource = .Columns(m.nColumnIndex).Comment
endif
endfor
endwith

上面代碼中的 {grid} 是表格物件的引用(如:Thisform.Grid 或
Thisform.Pageframe1.Page1.Grid1).

把這些代碼放到表格類的一個(gè)方法中是一個(gè)不錯(cuò)的主意. 這在設(shè)計(jì)時(shí)因改變 record source 而造成 ControlSource 值丟失時(shí)在表格類的 Init 方法中調(diào)它來恢復(fù) control sources 也是一種好辦法.

重大注意: 在完全恢復(fù) control sources 前不要執(zhí)行任何表單上的可視控制項(xiàng)或表格的 refreshing. 否則如果你在表格列中使用了自定義控制項(xiàng)時(shí)你將會(huì)遇到一個(gè)錯(cuò)誤資訊 &控制項(xiàng)不支援該資料類型&. 這是因?yàn)檎缜懊嫣峒暗牧兄锌赡苁褂玫拇騺y了的 control sources 中的不正確的欄位類型造成的.

表格自動(dòng)重構(gòu)

你是否發(fā)現(xiàn)過你的表格的行為不再象你在設(shè)計(jì)時(shí)設(shè)計(jì)的那樣的情況? 列中的自定義控制項(xiàng)丟失? 列, 標(biāo)題或控制項(xiàng)事件中的代碼不再運(yùn)行? 表格的重構(gòu)行為會(huì)完全移去所有的表格控制項(xiàng)和列並用默認(rèn)的 VFP 控制項(xiàng)和屬性設(shè)置再次重建表格. 這造成所有列物件中的所有方法, 屬性設(shè)置和物件丟失. 列的 CurrentControl 屬性重置為默認(rèn)的文本框控制項(xiàng). 自定義標(biāo)題丟失. 通常這是一種不知道表格發(fā)生了什麼情況的災(zāi)難. 下面說明種種原因及解決辦法.

1. 表格自己重構(gòu)總是發(fā)生在 RecordSource 別名被關(guān)閉後. 如果這是一個(gè)視圖, 通常在你 requery 視圖時(shí)重構(gòu)不會(huì)發(fā)生. 如果它是一個(gè) SQL 語句, 當(dāng)你指定另一個(gè) SQL 語句或關(guān)閉表格使用的用於保存查詢結(jié)果的別名時(shí)重構(gòu)就會(huì)發(fā)生. 當(dāng)你使用 SQL Pass-Through 來再次查詢資料到表格的 record source 使用的別名時(shí)重構(gòu)也會(huì)發(fā)生, 即使你任然使用的是視圖.

要避免在刷新表格的 record source 的重構(gòu), 你需要在上述的任何表格 record source 刷新前, 指定一個(gè)空串 (不是一個(gè)空格 - " ", 而是空串 - "") 到 Record source. 檢查你的代碼是否正確, 你是否以正確的順序這樣做了, 或其他東西沒有打亂正確的 refreshing 處理順序. (你可以在表格的 BeforeRowColChange 事件中放入 SET STEP ON 或設(shè)置跟蹤中斷點(diǎn)來跟蹤代碼). 在 record source 刷新後, 再次指定 record source 到表格. 在此情況下重構(gòu)不會(huì)發(fā)生, 但是, 因?yàn)橹付?record source , 列會(huì)發(fā)生自動(dòng)重綁定. 以下是如何用一小點(diǎn)代碼來修正這種情況的示例.

* 下麵保存 control sources
...........
{grid}.RecordSource = ""
* 執(zhí)行 record source 的刷新
...........
* 恢復(fù) record source
{grid}.RecordSource = "{RecordSourceName}"
* 下麵恢復(fù)列的 control sources
...........

在上面代碼中 {grid} 是表格物件的引用, {RecordSourceName} 是用於 record source 的別名或 SQL 語句. 通常情況下使用 SQL 語句作為 record source 別名會(huì)使列的 ControlSource 改變, 因?yàn)?VFP 總是改變它來表示為 &Alias.FieldName&格式而不管在設(shè)計(jì)時(shí)只指定了欄位名. 以上方法在改變表格的 SQL 語句時(shí)也是有用的.

重要注意: 在語句 &RecordSource=""& 後到完全恢復(fù) control source 之間, 不要執(zhí)行任何表單上的可視控制項(xiàng)或表格的 refreshing. 否則如果你在表格列中使用了自定義控制項(xiàng), 你將可能收到一個(gè) &控制項(xiàng)不支援該資料類型& 錯(cuò)誤.

另一種避免表格重構(gòu)的方法是使用表格的 BeforeRowColChange 事件. BeforeRowColChange 事件在每次表格將要重構(gòu)時(shí)被激發(fā). 它在包括表格別名關(guān)閉時(shí), SQL Pass-Through 游標(biāo)重獲取等時(shí)發(fā)生. 無論表格是否可見, 具有焦點(diǎn)及表格的配置. 最令人驚奇的是放置 NODEFAULT 到該事件中以使資料改變時(shí)避免表格重構(gòu). 但是, 在此情況下表格會(huì)顯示不可思議的錯(cuò)誤行為.

thisform.GridRefreshing = .T. && 告訴所有表格控制項(xiàng)
&& 將重新獲取表格資料
... 執(zhí)行資料查詢
thisform.Grid.RecordSource = thisform.Grid.RecordSource
thisform.Refresh && 或表格刷新
DOEVENTS && 如果需要 - 只測(cè)試不要該命令
&& 在此時(shí)表格停止自己重構(gòu)>
thisform.GridRefreshing = .F.

在表格類的 BeforeRowColChange 事件中放入以下代碼:

if PEMStatus(thisform,"GridRefreshing",5) AND thisform.GridRefreshing
nodefault
return
endif

如果你放置上面代碼到表格類中這樣該功能一般性的, 包括使 GridRefreshing 屬性作為你的表格類的屬性. 有時(shí)要求設(shè)置焦點(diǎn)到表格外然後再設(shè)置焦點(diǎn)回到表格, 因?yàn)楸砀裰械漠?dāng)前單元會(huì)在使用該方法來避免表格重構(gòu)時(shí)顯示星號(hào) (&*******&).

不幸的是, 沒有辦法知道 BeforeRowColChange 事件被調(diào)用的原因是因?yàn)樗闹貥?gòu)還是移動(dòng)表格單格的焦點(diǎn)或是表格獲得焦點(diǎn)時(shí). 就象在示例中一樣使用一個(gè)標(biāo)記. 如果你有時(shí)間, 你也可以生成一個(gè)使用透明的形狀控制項(xiàng)複蓋在表格上的表格類來捕捉所有滑鼠事件. 採(cǎi)用該方法表格將可以知道表格的 BeforeRowColChange 激發(fā)的原因. 該方法也可以捕捉 KeyPress 事件 (最好是設(shè)置表單的 KeyPreview=.T.). 最後, 表格應(yīng)該放到容器中來捕捉表格得到焦點(diǎn)時(shí)刻 (BeforeRowColChange 也將在表格得到焦點(diǎn)時(shí)激發(fā)).

兩種方法都有一個(gè)重大的缺點(diǎn): 要求在所有造成重構(gòu)的地方放置代碼. 當(dāng)這樣的地方位於多個(gè)表單和類中時(shí), 如, 用 SQL Pass-Through 功能重新查詢一些別名的時(shí), 要定位所有的這些地方是困難的並且它們要求一個(gè)表格的引用來避免重構(gòu). 它有時(shí)也會(huì)造成不希望的列的自動(dòng)再綁定. 表格常用於顯示視圖中的動(dòng)態(tài)資料, 因此它在 requiry 時(shí)會(huì)被其他的資料刷新. 表格的重構(gòu)在視圖 requiry 時(shí)不會(huì)發(fā)生. 但是, 當(dāng)移動(dòng)(升級(jí))應(yīng)用程式到使用遠(yuǎn)端視圖時(shí)程式師常常決定使用 SQL Pass Through 功能來處理資料. 在這樣的情況下各個(gè)被 SQL Pass-Through 使用的別名的 requery 將造成重構(gòu). 因此, 程式師在這裏的主要錯(cuò)誤也只是 requery 視圖並象以前一樣放入代碼到這裏. 它是一條單一的命令, 因此程式師常常在許多地方放入這樣的命令而沒有注意到這樣做的不正確之處. 在升級(jí)到使用 SQL Pass-Through 功能時(shí)所有 requery 語句應(yīng)該用適當(dāng)?shù)拿钐鎿Q. 另外, 在發(fā)現(xiàn)重構(gòu)行為時(shí), 程式師開始找所有該別名 requery 的地方. 它可能交叉地存在于表單和類中的多個(gè)地方, 這就成為一個(gè)大的問題. 訣竅: 放置資料 requery, 關(guān)閉, 打開和重打開 (以及其他所有與資料在關(guān)的動(dòng)作) 在一個(gè)地方 - 類的方法或函數(shù)中, 並用該類的物件引用來調(diào)用適當(dāng)?shù)墓δ? 總是假設(shè)所有資料功能會(huì)在今後的一些附加的代碼中進(jìn)行 requery, 即使當(dāng)它只是一個(gè)簡(jiǎn)單的視圖的 requery. 使用這種方法將有助於你在你需要修改某些東西而查找所有資料動(dòng)作的地方時(shí)節(jié)約時(shí)間. 表格重構(gòu)是當(dāng)其出現(xiàn)時(shí)你不能避免的這種情況之一. 例如, 創(chuàng)建一個(gè)具有所有必需的, 處理它顯示的資料的方法的表格類. 然後使用表格類的物件引用. 這樣在所有地方的資料刷新請(qǐng)求將成為一個(gè)單一的功能調(diào)用. 好了, 也許你很少創(chuàng)建一個(gè)有如此需求的應(yīng)用程式…

因表格重構(gòu)行為的失敗, 程式師有時(shí)創(chuàng)建並維護(hù)表格使用的 Record Source 的游標(biāo), 然後刷新用刪除和複製來刷新這樣的游標(biāo). 在你已經(jīng)有了這樣的游標(biāo)的情況下, 從游標(biāo)中刪除所有的資料並再次添加它們是不困難的.

2. 重構(gòu)發(fā)生在表格初始化且 record source 屬性為空或 record source 不存在時(shí) (別名沒有打開). 在此情況下表格自己重構(gòu)並使用當(dāng)前存在的別名作為 record source (或者在當(dāng)前工作區(qū)中沒有打開表時(shí)保持為空, 但所有的列都不存在了). 如果你需要打開 record source 在一些其他的事件中(不是表單的 Load 方法中, 在表格初始化前), 使用下面的技術(shù).

在表單的 Load 事件中創(chuàng)建一個(gè)空的與表格使用的 record source 結(jié)構(gòu)相同的游標(biāo); 表格的 record source 屬性將使用該空的游標(biāo)別名. 然後, 當(dāng)你打開真正的資料時(shí), 指定一個(gè)空的串到表格的 record source, 打開資料然後再次指定真正的資料別名到表格的 record source. 別一種處理方法是放置一個(gè)不可見的在它的 INIT 事件中創(chuàng)建空的游標(biāo)的自定義控制項(xiàng). 但是, 要保證該控制項(xiàng)的 Init 事件在表格的 INIT 之前激發(fā), 否則將會(huì)發(fā)生重構(gòu). 第二種方法是在運(yùn)行時(shí)添加表格到表單. 創(chuàng)建一個(gè)表格類並不在設(shè)計(jì)時(shí)把它放入表單. 在 Init 事件代碼中放入一個(gè) AddObject 方法調(diào)用來在要使用的別名準(zhǔn)備好後添加表格到表單.

3. 表格的自己重構(gòu)在列數(shù)改變?yōu)榱慊?-1 發(fā)生. 我希望你不要這樣做(指設(shè)置 ColumnCount 為 0 或 -1), 你這樣做了嗎? Wink 總之, 它可以用於簡(jiǎn)單的可以打開任何表並在表格中流覽它們的管理性表單. 但是, 由於重構(gòu)的原因, 這樣的表格具有很大的功能限制, 或者所有的表格功能將放入到類中並在運(yùn)行時(shí)的重構(gòu)後添加到表格.

4. 表格的自己重構(gòu)將在 record source 超出範(fàn)圍時(shí)發(fā)生. 這通常發(fā)生在當(dāng) record source 指定在一個(gè)資料工作期, 但表格確初始化在另一個(gè)資料工作期中時(shí), 這樣當(dāng)它試著刷新自己時(shí), 被另一個(gè)資料工作期使用的 record source 對(duì)於當(dāng)前資料工作期來說是不存在的. 另一種情況是當(dāng)程式師使用大量的資料工作期並在其間切換時(shí)發(fā)生.

重構(gòu)不能以用附加的表格列的引用來避免它 - 列與表格是分離的. 另外, 讓表格使用已經(jīng)在類中定義的列也不能避免重構(gòu) - 這是當(dāng)在表單上使用容器類時(shí)容器類的子物件不能被刪除這一規(guī)則的例外.

別一種流行的消除表格重構(gòu)問題的方法是動(dòng)態(tài)地創(chuàng)建表格. 用你的所有列定義代碼創(chuàng)建一個(gè)自定義表格類. 當(dāng) requery 資料時(shí), 從表單中移去表格控制項(xiàng), requery 資料, 然後在運(yùn)行時(shí)再次添加表格到表單並設(shè)置它的位置. 這要求首先處理表格的添加, 設(shè)置一些表格的屬性等等. 但是, 該方法是不好的, 因?yàn)樾枰獮楸韱紊系母鞅砀駝?chuàng)建類, 知道類的名字, 和表格都要有自己的處理程式 - 不可避免地許多代碼.

也可以在運(yùn)行時(shí)完全用代碼創(chuàng)建表格物件並用自定義控制項(xiàng)裝配它. 但是, 在表格自己重構(gòu)後, 你需要再次添加這些自定義控制項(xiàng)到表格中. 該方法用於表格重構(gòu)不可避免的情形, 例如, 在管理性程式中 - 要在同一個(gè)表格中顯示任何表的內(nèi)容, 但也需要一些象使用編輯框來編輯備註欄位和單擊列頭排序這樣的功能時(shí).

不管表格如何捲動(dòng)鎖住某列使其總是可見的

在需要鎖住某列而使表格捲動(dòng)時(shí)都可見時(shí), 要記住的第一件事是使用 split 表格. 當(dāng)用戶捲動(dòng)表格時(shí)左邊的列總是可見的. 但是, 這讓用戶看起來感到相當(dāng)古怪, 額外的捲動(dòng)條… 等等等等.
表格有一個(gè)很好的屬性 LeftColumn, 它是當(dāng)前可視的最左邊列的列序號(hào). 當(dāng)表格橫向捲動(dòng)時(shí), 該值會(huì)改變. 我們可以在 Scrolled 方法中用它來鎖住某列:

if nDirection>3
this.Columns(1).ColumnOrder = this.LeftColumn
endif

另外, 對(duì)於用鍵盤捲動(dòng)表格, 在 AfterRowColChange 添加一行:
this.Columns(1).ColumnOrder = this.LeftColumn
這樣第一列總是顯示在表格的最左邊!
 

表格訣竅

我所要說的第一個(gè)訣竅不僅僅是相對(duì)於表格的. 搜索 UT 站點(diǎn)關(guān)於表格的資訊. 你會(huì)找到許多有用的東西! 而且總是可以在這裏提出任何問題.
在使用 SQL SELECT 語句作為 record source 時(shí), 總是在語句的後面添加 &INTO CURSOR …&, 否則將會(huì)在表單載入時(shí)或該查詢運(yùn)行時(shí)顯示一個(gè)流覽視窗.

可以使用運(yùn)算式作為 column source! 只需使列成為唯讀的並在列的 ControlSource 中寫入要顯示的運(yùn)算式. 可惜的是, 表格中所有行中顯示的運(yùn)算式的結(jié)果只能是相同資料類型; 否則你會(huì)得到奇怪的格式, 星號(hào)或一些其他奇怪的結(jié)果. 對(duì)於不同資料類型最好在表格列中使用容器…

要添加新控制項(xiàng)到表格列中, 在屬性視窗的下拉式列示方塊中選擇該列, 單擊正在編輯的表單的標(biāo)題來選擇它, 選擇所需的控制項(xiàng)並拖動(dòng)它到表格中. 要移去該控制項(xiàng), 在屬性視窗中選擇該控制項(xiàng), 單擊表單的標(biāo)題來選擇表單, 然後按下 &Del& 按鈕.

在沒有橫向表格線時(shí), 當(dāng) Sparse=.F. 或 HighlightRow=.F. 時(shí), 游標(biāo)所在的單格會(huì)顯示一個(gè)灰色的框. 放置一個(gè)白色的 shape 到表格下可以消去灰色的框.

可以在列的 MouseMove 事件中設(shè)置 mouse pointer 來改變滑鼠游標(biāo).


表格警告

本章為程式師提供關(guān)於表格的警告. 有很多…
*********

記住在設(shè)計(jì)時(shí)改變 record source 屬性時(shí)需要記住各列的 control source. 因?yàn)樵?record source 改變後它們都會(huì)被清除! 在列的 Comment 屬性中創(chuàng)建一個(gè) ControlSource 屬性的拷貝是一個(gè)不錯(cuò)的辦法.

在表格被始化前, 不要讓它的 Record Source 屬性為空.

表格列中的控制項(xiàng)的事件和屬性在列的 sparse 屬性為 .T. 時(shí)僅作用於當(dāng)前單格. 當(dāng) Sparse 為 .F. 時(shí), 屬性用於顯示, 但事件也僅作用於當(dāng)前單格.

Scrolled 事件只被 scrollbars 激發(fā). 當(dāng)表格是被鍵盤捲動(dòng)或在特定情況下以編程方式捲動(dòng)時(shí), Scrolled 事件不激發(fā).

RelativeRow 和 ActiveRow 屬性僅在表格具有焦點(diǎn)時(shí)可用. 在當(dāng)前記錄超出可見的記錄時(shí) ActiveRow 總是為零.

*********



VFP 中的表格 第二部分


在該部分中, 我將討論關(guān)於如何使表格開發(fā)更快速的列和列頭的竅門. 也將討論關(guān)於確定表格列頭和單格的確切位置. 象第一章中一樣, 我也包括了一些訣竅和警告.

表格列和列頭的竅門 - 如何使表格開發(fā)更快速

許多時(shí)候當(dāng)你試圖創(chuàng)建一個(gè)簡(jiǎn)單的應(yīng)用程式來檢查是否有些事可以用表格來做或它將看起來是什麼樣的時(shí)候, 你會(huì)發(fā)現(xiàn)需要設(shè)置許多東西來使表格得到需要的外觀. 指定 RecordSource, 為各列指定 ControlSource, 指定各列頭的 caption 等等. 有時(shí)你又需要添加一些功能到表格中, 如象雙擊一個(gè)行以彈出別一個(gè)視窗 - 這也要求添加代碼到表格各列中的控制項(xiàng)中. 當(dāng)只有一個(gè)只有少量列的表格在你的應(yīng)用程式中時(shí)這不成問題. 但是, 當(dāng)你的應(yīng)用程式中有大量表格功表格有很多列時(shí), 你會(huì)很快被各表格和列中的這些事情弄得疲憊不堪.

為表格和表格中的控制項(xiàng)定義類是容易的. 該方法得到了廣泛的應(yīng)用. 但是, 還有更好的辦法來組織你的為表格使用的 OOP 的基類, 這將使得你的應(yīng)用程式的表格編程有一個(gè)好的基礎(chǔ).

首先, 定義一個(gè)提供所有功能和抽象方法的表格基類範(fàn)本. 注意你應(yīng)該保留你的類的 ColumnCount 屬性為預(yù)設(shè)值, 不要在表格基類中定義任何列數(shù), 否在在類物件被實(shí)例時(shí)將很難處理它們. 調(diào)整表格的外觀為你的應(yīng)用程式中最常用的情況. 例如, 在我的應(yīng)用程式中通常我不在表格中顯示記錄標(biāo)記 (Record Marks) (它們?cè)诒砀癫粨碛薪裹c(diǎn)且當(dāng)前記錄以另一種顏色高亮顯示時(shí)會(huì)不正確地刷新) 和刪除標(biāo)記 (表格通常是唯讀的控制項(xiàng)).

然後, 定義用於表格列中的控制項(xiàng). 通常它是一個(gè)文本框, 但你可能也想使用核取方塊, 下拉式列示方塊, 編輯框 (用於顯示備註欄位內(nèi)容) 以及, 也許是一些其他不常用的自定義控制項(xiàng). 你將只在表格內(nèi)部使用這些控制項(xiàng)而決不會(huì)在其他地方使用這些控制項(xiàng). 在這些類中你可以更好地組織所有表格列中需要的功能. 以調(diào)用表格基類中的抽象方法的方式來這樣做. 例如, 要調(diào)用雙擊方法, 在各表格列使用的類的 DblClick 事件中放入以下代碼:

This.parent.parent.eventDblClick(this.parent)

在以上示例代碼中 eventDblClick 是表格基類中的一個(gè)抽象方法 - 除接受單一參數(shù)的 &lparameters& 語句外它不包含其他代碼 (該參數(shù)是被雙擊的列的對(duì)象引用). 現(xiàn)在我們將從其中得到什麼呢? 沒有這個(gè)我們不得不為表格用雙擊代碼在表單上定義新的方法, 然後從表格各列中的控制項(xiàng)的 DblClick 事件中添加該方法調(diào)用. 當(dāng)你有許多的列時(shí), 這是相當(dāng)令人厭煩的. 採(cǎi)用這種新的方法你只需要在你的表格基類的 eventDblClick 方法中寫你的代碼. 好了, 這也要求放置你自己的類到列中來代替默認(rèn)的文本框, 但這太容易了. 放入以下代碼到你的表格基類的 Init 方法中:

LOCAL liColIndex
FOR liColIndex=1 TO this.ColumnCount && 遍曆所有的列
WITH this.Columns(liColIndex)
IF upper(.CurrentControl) == &Text1& && 只替換使用默認(rèn)文本框的列
.Text1.Visible = .F.
.AddObject(&Text2&,&{GridTextBoxClass}&)
.Text2.Visible = .T.
.CurrentControl = &Text2&
.RemoveObject(&Text1&)
.Text2.Name = &Text1&
ENDIF
ENDWITH
ENDFOR

上述代碼中 {GridTextBoxClass} 是你的表格文本框類的名字. 注意如果你的保存該類的類庫沒有載入記憶體, 你將需要使用 NewObject 方法來代替 AddObject.

在進(jìn)行了上述簡(jiǎn)單的嘗試後, 讓我們來添加更多的東西. 定義更多的被表格列中的控制項(xiàng)調(diào)用的事件. 添加更多的你可能用於表格中的類, 然後用添加與列的 control source 的資料類型相適應(yīng)的類的控制項(xiàng)來增強(qiáng)替換默認(rèn)控制項(xiàng)的代碼(你可以用 type 函數(shù)來檢查列的 control source 的資料類型, 如: type(.ControlSource)). 通常核取方塊用於邏輯欄位而編輯框用於備註欄位.

現(xiàn)在我們的表格更易於在表單上使用了, 但是... 表格列頭怎麼辦? 一個(gè)好辦法是為表格列頭寫一些代碼來自動(dòng)地用列的 control source 來寫入列頭的 caption 屬性. 例如, 在檢查了 ControlSource 是欄位而不是運(yùn)算式後用 DBGETPROP(.ControlSource, &FIELD&,&Caption&) 來從資料庫中獲取欄位的 caption. 表格的 Init 中的示例代碼如下:

LOCAL liColIndex, lcCaption
FOR liColIndex=1 TO this.ColumnCount && 遍曆所有的列
WITH this.Columns(liColIndex)
&& 檢查列頭是不沒有被開發(fā)者設(shè)置
IF upper(.Header1.Caption) == &HEADER1&

&& 檢查 control source 是否是真正的欄位而不是運(yùn)算式
IF &.& $ .ControlSource AND ;
FSIZE(substr(.ControlSource,at(&.&,.ControlSource)+1),.parent.RecordSource) > 0

&& 檢查別名是不是在 VFP 的資料庫中
IF !empty(cursorgetprop(&Database&,.parent.RecordSource)
lcCaption = DBGETPROP(.ControlSource,&FIELD&,&CAPTION&)
ELSE
lcCaption = &&
ENDIF
IF empty(lcCaption)
&& 沒如沒有其他情況只指定欄位名
lcCaption = substr(.ControlSource,at(&.&,.ControlSource)+1)
ENDIF
.Header1.Caption = lcCaption
ENDIF
ENDIF
ENDWITH
ENDFOR

這是最簡(jiǎn)單的方法. 為了正確用 DBGetProp 獲取當(dāng)前資料庫設(shè)置要求當(dāng)前有一個(gè)打開的資料庫也是必須的.
你可能做得更多. 例如, 在使用欄位名作為列頭的 caption 時(shí), 對(duì)它們進(jìn)行一些複雜的轉(zhuǎn)換以使它們看起來更好一些, 對(duì)欄位名使用命令約定. 作為命名約定的示例如下: 在資料庫中所有除主關(guān)鍵字段外的欄位以資料類型字元為首碼. 主關(guān)鍵字中用 &ID& 來標(biāo)識(shí)它. 因此我們可以從欄位名中移去第一個(gè)字元作為列頭的 Caption(當(dāng)欄位名中不包含 &ID_& 或 &_ID& 或它本身來是 &ID& 時(shí)). 然後用 STRTRAN 替換所有下劃線為空格並用 &PROPER()& 函數(shù)來獲得最終結(jié)果. 採(cǎi)用該方法 &cCust_Name& 將在列頭中顯示為 &Cust Name&. 你可以為欄位使用其他的命名約定, 看看是否可以改進(jìn)列頭的顯示. 如果你有 StoneField Database Toolkit (注:即眾所周知的 SDT) 或你自己的資料這典, 你可以查詢資料字典來獲得欄位的 caption 和任何其他的你想應(yīng)用於列頭的和用於顯示資料的欄位屬性. 如果欄位來自視圖, 你可以從表中獲取它的源欄位來獲取它的屬性. 你可以從視圖欄位上用 "DBGETPROP(.ControlSource, &Field&, &UpdateName&)" 來從表中獲取欄位名. 這僅適用于可更新欄位, 作為運(yùn)算式生成的欄位用上述方法返回的結(jié)果將是一個(gè)空串.

好了, 對(duì)於列頭的 captions - 你可以在表格基類的 Init 中設(shè)置它們?nèi)会嵬羲鼈? 但是, 還有一些有用的捕捉列頭的 Click 和其他事件事情. 在易於創(chuàng)建表格列控制項(xiàng)類時(shí), 創(chuàng)建 header 類是相當(dāng)難處理的. 你可以用相似的方法在你的表格基類中為列頭組織事件系統(tǒng); 但是, 你可以在程式檔中用 DEFINE CLASS 結(jié)構(gòu)以簡(jiǎn)單的方法定義你自己的 header 類, 如下所示:

DEFINE CLASS MyHeaderClass AS HEADER
PROCEDURE DblClick
This.parent.parent.eventHeaderDblClick(this.Parent)
ENDPROC
ENDDEFINE

如果你熟悉 VCX 檔結(jié)構(gòu), 你可以在 VCX 庫中添加兩條記錄並指定其基類為 header 來創(chuàng)建 header 類. 但是這樣一來, 你除了象編輯 VFP 表一樣編輯它以外, 將不能再以其他方式編輯它. 因此最好還是首先在 PRG 檔中定義它用於開發(fā)和調(diào)試. 最後, 當(dāng)它測(cè)試完成後, 如果需要的話你可以從 PRG 中移運(yùn)它們到 VCX 中. 為什麼你會(huì)要它們?cè)?VCX 檔中的理由是簡(jiǎn)單的 - 你可以在設(shè)計(jì)時(shí)從控制項(xiàng)工具欄拖放它到表格列中, 這樣將在設(shè)計(jì)時(shí)以新的 header 類替換默認(rèn)的 header. 我不知道還有其他的理由了... 對(duì)於運(yùn)行時(shí), 以下代碼需要添加到前述的遍歷代碼中 (改變默認(rèn)控制項(xiàng)為你自己的控制項(xiàng)). 也為列頭類添加它們:

IF upper(.Header1.Class) == &HEADER& && 僅替換默認(rèn)的 header 類
lcOldCaption = .Header1.Caption && 保存 caption 以應(yīng)用它們到新的 header
.AddObject(&Header2&,&MyHeaderClass&) && 這將自動(dòng)移除原有的 header
.Header2.Name = &Header1&
.Header1.Caption = lcOldCaption
ENDIF

你可能也想複製原表格列頭的其他屬性到你用新類創(chuàng)建的新的列頭物件. 也請(qǐng)注意到不需要移除原有的列頭因?yàn)?VFP 會(huì)自動(dòng)移除它 - 來保持列中只有一個(gè)列頭. 對(duì)於 AddObject 定義列頭類的 PRG 檔必須事先用 SET PROCEDURE TO ?ADDITIVE 命令載入記憶體, 但是你可以決定用列的 NewObject 方法.

在一般情況下, 建議把替換表格中的控制項(xiàng)為你自己的類的代碼移動(dòng)到一個(gè)單一的表格的方法中. 為什麼需要這樣呢 - 表格重構(gòu). 少數(shù)場(chǎng)合下表格重構(gòu)是必需的. 在表格重構(gòu)後你可以調(diào)用表格的方法來再次設(shè)置你自己的類, 因此即使當(dāng)你在相同的表格控制項(xiàng)中顯示不同的資料時(shí), 它仍然會(huì)以相同的方式運(yùn)行. 這是因?yàn)? 儘管進(jìn)行了重構(gòu), 事件的主要代碼是在表格控制項(xiàng)的方法中, 因此在重構(gòu)後它們不會(huì)丟失. 更多的是, 在你運(yùn)行了替換表格控制項(xiàng)為你自己的類的代碼後, 表格仍然具備所需的功能. 因此代替用不同的資料源創(chuàng)建多個(gè)表格類在同一個(gè)表單中來顯示這些不同資料源, 你現(xiàn)在可以放置一個(gè)單一的表格並用不會(huì)丟失太多功能的方法來重構(gòu)它; 然後為每一個(gè) data source 來創(chuàng)建新的表格類並增強(qiáng)不同數(shù)量的 record sources.

不好的事情是列頭的 Click 事件也會(huì)在表格列 resize 或移動(dòng)時(shí)發(fā)生. 列具有 Moved 和 Resized 事件, 但是... 當(dāng)你用自己的列頭類替換了默認(rèn)的列頭類後, 很難使用列的這些事件, 而且它們的運(yùn)行速度會(huì)變慢. 因此看來使用列頭類並不好. 但是有好的解決辦法. 在你的新的列頭類中比較 OldColumnWidth 和 OldColumnOrder 屬性. 然後在列頭的 Init 中設(shè)置它們?yōu)榱械?Width 和 ColumnOrder 屬性的初始值. 在 Click 事件代碼中比較你的列頭類的當(dāng)前的 Width 和 OldColumnWidth 屬性值及 ColumnOrder 和 OldCoumnOrder 屬性值. 當(dāng)值與原值不同時(shí), 則列是 resized 或 moved 了的. 在 resized 時(shí), 只用當(dāng)前列的 OldColumnWidth 來更新它. 當(dāng)列 moved 時(shí) - 你需要為列頭更新表格中的所有列的 OldColumnOrder 屬性. 好了, 在一定情況下列的寬度和列序現(xiàn)在可以用編程方法來修改了, 最好是不要直接這樣做, 而是使用表格類中的一個(gè)自定義方法調(diào)用, 例如, &ChangeColumnWidth(ColumnNumber)&, 或 &ChangeColumnOrder(ColumnNumber)&. 採(cǎi)用此方法你可以保證 OldColumnWidth 和 OldColumnOrder 總是與列的設(shè)置同步. 而且現(xiàn)在你有機(jī)會(huì)用上述方法在列移動(dòng)或 resize 時(shí)來激發(fā)你的表格類的自定義方法中的代碼了 - 沒有真正接觸到任何列的方法!

為了加快輸入到列的 ControlSource 中, 你可以用簡(jiǎn)單的方法. 在你的表格類中定義一個(gè)屬性 (ControlSourceList) 它是用於保存各列使用的欄位名的以分號(hào)分隔的列表. 使用分號(hào)而不是逗號(hào)是因?yàn)槟憧赡軙?huì)在一些 control source 中使用包含逗號(hào)的運(yùn)算式. 在 Init 事件中分解該串到各屬性中(如果它不為空), 然後用串中的列表中的項(xiàng)填充各列的 ContolSource 屬性. 注意表格的任何刷新將不會(huì)先於該操作, 因?yàn)楸砀竦某跏蓟谀J(rèn)情況下使用欄位在表中的物理順序 (列的重綁定 - 參見文章前面的描述). 當(dāng)你的表格中有自定義控制項(xiàng)時(shí), 可能列會(huì)使用對(duì)於該控制項(xiàng)來說是不正確的欄位類型並在刷新時(shí)造成不正確的資料類型錯(cuò)誤 (Refresh 極少在表格的 Init 事件中發(fā)生, 但是, 在複雜的類代碼中有時(shí)可能會(huì)發(fā)生). 好了, 在設(shè)計(jì)時(shí)屬性表中的屬性長(zhǎng)度限制是 255 字元, 因此在表格有許多列時(shí)將不能這樣做. 在此情況下當(dāng)你在表單中使用表格時(shí)可以在表格的 Init 中用代碼來指定長(zhǎng)的串到屬性, 然後用 DODEFAULT() 來調(diào)用父類代碼. 你也可以在串中不用欄位名前的別名來節(jié)約空間; 別名將被表格自動(dòng)指定. 任何情況下, 在 ControlSource 中的運(yùn)算式中的欄位名必須包括別名.

最後, 說說不同類型的 record source. 當(dāng) record source 是別名時(shí), 沒有問題. 但是當(dāng)你打開一個(gè)以表名以數(shù)值開始的表 record source 時(shí), VFP 會(huì)以另一個(gè)別名來打開它. 表格中的 RecordSource 中的 SQL SELECT 語句也有類似情況. 幸運(yùn)的是, 有一種簡(jiǎn)單的方法來檢查表格使用的別名的正確性. 別名可以用任何列的 ControlSource 屬性來檢查. 表格會(huì)自動(dòng)添加 "{alias}." 到所有列中的 control sources 欄位前. 你可以從列的 control sources 取出別名(當(dāng)它們是簡(jiǎn)單格式而不是複雜運(yùn)算式時(shí)) . (如何檢查 control source 是否是一個(gè)欄位引用我已經(jīng)說過了.) 好了, 當(dāng)要用表格顯示一些用 SET RELATION aliases 關(guān)聯(lián)的表時(shí), 這將不能工作, 但是, 顯示在表格中的關(guān)聯(lián)表通常使用別名作為 record source 類型. 那麼, 在我們的程式中用一個(gè)自定義屬性 (MainAlias) 來代替 RecordSource, 用它來保存表格使用的真實(shí)的別名. 我們也還定義了 RecordSource_Assign 代碼來捕捉 record source 的改變並更新我們的自定義屬性.


確切定位表格列頭和單格

表格中的控制項(xiàng)的功能可能與表格外的控制項(xiàng)的功能不同. 事實(shí)上關(guān)於這一點(diǎn) VFP 有一些冗餘, 致使一些功能在表格中不能正確運(yùn)行. 我不打算在這裏一一列舉, 但將說明一種好的方案 - 把控制項(xiàng)放在表格外並在表格單格上顯示它來進(jìn)行資料編輯. 採(cǎi)用該方法控制項(xiàng)將象單獨(dú)的控制項(xiàng)一樣編輯資料, 而不會(huì)象表格列中的控制項(xiàng)一樣有一些功能不能使用. 現(xiàn)在對(duì)每一列採(cǎi)用此方法而你將不再受到表格的限制. 但是, 問題是複蓋在表格單格上的控制項(xiàng)的相對(duì)位置. 這就要求計(jì)算表格中的單格的準(zhǔn)確位置以便用你的控制項(xiàng)來複蓋它. 這個(gè)事情不太好做, 列序可能改變了, 表格可能捲動(dòng)了, 僅獲得焦點(diǎn)的表格可以計(jì)算行號(hào)等等. 在這裏我說明一種如何計(jì)算位置的一般方法.

另一個(gè)要求計(jì)算準(zhǔn)確位置的理由是放置排序標(biāo)記到表格列上來指明表格是按哪一個(gè)列進(jìn)行排序的. 對(duì)於這一點(diǎn)隻要求計(jì)算列的位置.

位置的計(jì)算將在表格捲動(dòng)後, 一些列寬改變後, 一個(gè)列移動(dòng)後, 記錄標(biāo)記和刪除標(biāo)記的可視狀態(tài)改變後, 列高和行高改變後, 表格被分割後 (表格的 Partition 屬性設(shè)置為非零值). 你可以捕捉這些事件:

* 表格捲動(dòng)在 Scrolled 事件中, 如果是用鍵盤產(chǎn)生的自動(dòng)捲動(dòng)在 AfterRowColChange 事件中. 因列移動(dòng)造成的捲動(dòng) - 捕捉列移動(dòng)或改變用 LeftColumn 屬性 (保存它們的原值). 其他原因造成的自動(dòng)捲動(dòng)則相當(dāng)少見.
* 列的移動(dòng)和寬度調(diào)整在列的事件中或用本文中前面描述過的方法
* 記錄標(biāo)記和刪除標(biāo)記的改變, 以及捲動(dòng)欄的以編程方式的改變; 你可以在表格類中定義相應(yīng)屬性的 Assign 方法來捕捉它們
* Partition, RowHeight 及 HeaderHeight 改變 - 在表格的 MouseUp 事件中用類似於前述的列頭方法來捕捉它們 - 保存原值到表格屬性中並在 MouseUp 事件中用新值比較它們以確定是否發(fā)生了變化.

因?yàn)橐谠S多地方計(jì)算來更新控制項(xiàng)的位置, 在計(jì)算列的位置時(shí)應(yīng)該盡可能地快; 否則在表格中的列數(shù)及計(jì)算位置的控制項(xiàng)很多時(shí)會(huì)比較慢.

以下代碼中的演算法檢查列的大小和右邊沿. 它是最快的並經(jīng)長(zhǎng)期使用的方法. 它計(jì)算表格除 split 外的所有表格設(shè)置. 代碼中的注釋說明了演算法.

Procedure ColumnRightPosition
* returns position in pixels of right edge of column relative to the grid rectangle area
* returns 0 if column is outside of visible area of grid
* parameters:
* toColumn - reference to the column for which position should be calculated
lparameters toColumn

with this && "this" here is a grid reference
local lnIndex, lnColumn, lnColumnCountToLeft, lnPosition, lnVAreaWidth
local array laColumnsOrder(.ColumnCount)
&& fill array by column order numbers with column indexes in a single
&& array column. Note that we get only columns that could be visible and skip
&& columns that are not visible for sure (that have ColumnOrder= .LeftColumn
m.lnColumnCountToLeft = m.lnColumnCountToLeft + 1
m.laColumnsOrder[m.lnColumnCountToLeft] = ;
.Columns(m.lnColumn).ColumnOrder * 10000 + m.lnColumn
endif
endfor
m.lnPosition = 0

if m.lnColumnCountToLeft >0
&& truncate array from rest of unnessesary values
dimension laColumnsOrder(m.lnColumnCountToLeft)

&& Main trick here - use asort() VFP function. It will quickly
&& change order in array from column index to the order by
&& ColumnOrder. This will help us to reference each column
&& after LeftColumn in their visibility order.
asort(laColumnsOrder)

&& scan columns in the visibility order and increment width (result)
&& until we reach our column or right edge of the grid.
m.lnVAreaWidth = .Width - iif(.RecordMark, 10, 0) + ;
iif(.DeleteMark, 8, 0) + ;
iif(.ScrollBars>1,sysmetric(5),0) && Width of the visible portion of grid
&& note we now go through array of columns starting from left visible in order
&& of columns, i.e. first value appropriate to column .LeftColumn
for m.lnIndex = 1 to m.lnColumnCountToLeft
m.lnColumn = m.laColumnsOrder[m.lnIndex] % 10000
m.lnPosition = m.lnPosition + .Columns(m.lnColumn).Width + 1
if m.toColumn = .Columns(m.lnColumn) or m.lnPosition > m.lnVAreaWidth
&& so we will no need to scan remained columns
exit
endif
endfor

&& if we reached required column
if m.toColumn = .Columns(m.lnColumn)
&& if the column is last, its width is cut to fit into grid visible area
m.lnPosition = min(m.lnVAreaWidth,m.lnPosition)
if m.lnPosition > 0
&& add left grid edge controls width if they are shown
m.lnPosition = m.lnPosition + iif(.RecordMark, 10, 0) +;
iif(.DeleteMark, 8, 0)
endif
else
m.lnPosition = 0
endif
endif
endwith
m.toColumn = &&
return m.lnPosition

當(dāng)前表格行的位置只有在表格獲得焦點(diǎn)時(shí)可以檢查到. 這是因?yàn)槲覀冎荒苡帽砀竦?RelativeRow 屬性來計(jì)算它, 當(dāng)表格不擁有焦點(diǎn)時(shí)計(jì)算值總是零. 這種方法更簡(jiǎn)單且更適於放入單個(gè)的運(yùn)算式:

* 計(jì)算表格行相對(duì)於表格矩形區(qū)域的 top 位置, 單位是 pixels
* 如果位置在可視的區(qū)域外則返回零
Local lnRow
m.lnRow = this.RelativeRow
m.lnRow = this.RelativeRow
if m.lnRow=0
return 0
esle
return this.HeaderHeight + this.RowHeight*(this.RelativeRow-1)
endif

注意為了得到正確可靠的值, 處理中的表格獲得焦點(diǎn) RelativeRow 屬性應(yīng)該訪問兩次.
下圖顯示了表格屬性及它們的尺寸. 有助於更好地理解示例代碼.


表格訣竅

表格的 Format 屬性設(shè)置為 &Z& 時(shí), 當(dāng) Sparse=.T. 時(shí)不顯示零值

是不是對(duì)表格列中顯示的備註欄位為 &Memo& 感到很厭倦? 你可以用類似於 "PADR({MemoField},200)" 這樣的運(yùn)算式來顯示備註欄位. 也可以在列的 sparse 屬性設(shè)置為 .F. 時(shí)在列中用 EditBox 來允許對(duì)它們進(jìn)行編輯, EditBox 可以比用運(yùn)算式作為列的 ControlSource 時(shí)顯示更多的文本. 在表格中的 EditBox 中捲動(dòng)欄僅在表格的 RowHeight 屬性大於三行文本的高度時(shí)才會(huì)顯示.

在表格可以完整地顯示所有列而沒有橫向捲動(dòng)欄時(shí), 你可以設(shè)置所有列的寬度小一些來讓所有的列完全顯示在顯示區(qū)中以避免當(dāng)最後一列得到焦點(diǎn)時(shí)表格的自動(dòng)捲動(dòng).


表格警告

在使用行緩存時(shí)刷新表格會(huì)造成資料的自動(dòng)提交(Update). 這是因?yàn)樵?VFP 內(nèi)部, 表格會(huì) scan 別名中的要顯示的記錄, 這會(huì)造成記錄指標(biāo)的移動(dòng), 並因此造成資料的自動(dòng)提交. 在用表格工作時(shí), 別名應(yīng)該使用表緩存模式.

在特定情況下, 當(dāng)列的 ControlSource 返回的字元長(zhǎng)於 200 字元且列的 sparse 屬性設(shè)置為 .T. 時(shí)表格會(huì)崩潰或不正常運(yùn)行. 在此情況下用類似於 "PADR(...,200)" 這樣的表達(dá)工作為運(yùn)算式來顯示資料. 如果要在這樣的情況下編輯資料, 在表格列中用 EditBox 來代替 TextBox 並設(shè)置列的 Sparse 屬性為 .F.. 當(dāng) SET DELETED 為 ON 時(shí), 被刪除的記錄只有在記錄指標(biāo)移動(dòng)後才會(huì)不顯示出來. 在刪除後如果記錄指標(biāo)在被刪除的記錄上時(shí), VFP 保持記錄的可見, 包括在表格中.


VFP 中的表格 第三部分


在該部分中, 我將描述關(guān)於單擊列頭時(shí)表格排序, 表格的排序標(biāo)記 (指示符) 和雙擊列頭時(shí)讓表格列的寬度與列中的資料寬度一致. 按照慣例, 將包括一些訣竅和警告.

單擊列頭排序

上面提到的單擊列頭排序表格和列表中的項(xiàng)是很流行的做法. 該功能在有成百上千的行列表中搜索某些東西時(shí)特別有用. VFP 的表格沒有內(nèi)置該功能, 但可以編程方式來實(shí)現(xiàn).

有多種現(xiàn)存的方法來達(dá)到此目的. 我們將不包含所有這些方法, 而只是說明最常用對(duì)於創(chuàng)建具有排序功能的表格類有用的方法. 這意味著描述的方法中將去掉它們中太複雜和不常用的部分. 建議將此功能作為你的應(yīng)用程式中的表格基類的一部分這樣你可以在多個(gè)應(yīng)用程式中重用它們.

首先, 準(zhǔn)備一個(gè)列頭的 Click 事件來在正確的時(shí)候激發(fā)排序功能. Click 事件也會(huì)在列重調(diào)大小和移動(dòng)時(shí)激發(fā), 因此你需要使用一些特殊的類似于本文前面描述過的方法 - 使列頭類跟蹤列的重調(diào)大小和移動(dòng)來區(qū)別它只是在單擊列頭. 當(dāng)然, 這將要求表格中所有其他的東西支持並維護(hù)用新的列頭類替換默認(rèn)的列頭類. 無論如何如果你想在任何表格中使用排序功能的話這是必需的. 當(dāng)列是可調(diào)整大小的時(shí)候, 單擊 resize 區(qū)域而不調(diào)整列的寬度也會(huì)被捕捉到 - 因此在滑鼠的游標(biāo)是 resizeg 箭頭是對(duì)列進(jìn)行排序不是一個(gè)好的辦法, 這會(huì)把用戶搞糊塗 (我們也為本文稍後論及的另一個(gè)功能保留這一點(diǎn)). 表格列頭的 resize 區(qū)域是 11 象素寬 (列頭線的左邊 6 象素 4 象素右邊). 當(dāng)你單擊 resize 區(qū)而沒有進(jìn)行 resize 時(shí), 僅管滑鼠此時(shí)已經(jīng)在下一個(gè)列頭上, 滑鼠和單擊事件僅在可調(diào)寬度的列頭區(qū)被激發(fā). 在示例中你可以看到在列頭類中是如何進(jìn)行跟蹤的 - 使用 MouseUp 事件我們得到滑鼠指標(biāo)的座標(biāo)並檢查它是否只是在調(diào)整區(qū)域進(jìn)行了單擊. 如果是, 設(shè)置特殊的標(biāo)誌這樣在 Click 和 DblClick 事件中我們可以檢查滑鼠是否是在調(diào)整區(qū).

一但純粹的單擊被從所有其他動(dòng)作中分離出來後, 執(zhí)行排序. 它由三部分組成 - 排序管理器, 排序常式和排序後的表格刷新.

排序管理器跟蹤當(dāng)前要排序的是哪一個(gè)列, 為排序定義屬性關(guān)保存一個(gè)當(dāng)前排序的狀態(tài). 要這樣做, 使用了以下屬性:

Grid.SortedColumn - 包含當(dāng)前排序列的索引如果沒有排序它將是零. 索引將用於快速跟蹤哪一個(gè)列是最後排過序的並在另一個(gè)列被為排序而單擊時(shí)關(guān)閉該列的排序 (儘管這不是必需的 - 排序列的索引將在一段簡(jiǎn)單的遍曆所有列並檢查 SortingState 屬性值的代碼中被檢查).
Grid.lAllowSorting - 預(yù)設(shè)值為 .T. - 允許廢止對(duì)整個(gè)表格的排序.
header.lAllowSorting - 預(yù)設(shè)值為 .T. - 允許廢止對(duì)該列的排序.
Header.SortingState - 0 - 未排序, 1 - 按昇冪排序, 2 - 按降冪排序. 排序管理器將捲動(dòng)這些值並在需要時(shí)調(diào)用排序常式.
Header.DefaultSorting - 該屬性決定在列頭被單擊時(shí)使用什麼樣的排序. 例如, 首選的日期排序在默認(rèn)情況下是用降冪, 因此你可能想改變?cè)搶傩詮哪J(rèn)的 1 到 2 - 這將得不斷單擊列頭時(shí)的排序的排序順序由 {無排序}->{昇冪}->{降冪} 變?yōu)?{無排序}->{降冪}->{昇冪}.
Header.lEventSwitchOffSorting - 一個(gè)事件 - 如果任何排序的特定處理造成了速度緩慢或錯(cuò)誤, 關(guān)閉排序的屬性並去掉所有附加的索引. 它在以 Grid.SetAll("lEventSwitchOffSorting",.T.) 的格式調(diào)用被列頭中的該屬性的 _Assign 方法跟蹤. 該屬性在表格類中, 當(dāng) SetAll 函數(shù)被調(diào)用時(shí)表格的列頭還沒有準(zhǔn)備好的情況下也是需要的 (否則該屬性未找到任何控制項(xiàng)時(shí), VFP 有時(shí)會(huì)出現(xiàn)錯(cuò)誤提示).
在最複雜的情況下列會(huì)從列格中移去, 因此 SortedColumn 屬性可能要求在進(jìn)行這種處理後進(jìn)行更新. 在這樣的情況下, 建議使用表格中的一個(gè)方法來掃描所有的列, 並用正確的, 包含非零排序狀態(tài)的列的索引來更新 SortedColumn 值. 好了, 你可以用另一種方法來跟蹤當(dāng)前排序列的索引 - 使用一個(gè)返回當(dāng)前排序列的索引的簡(jiǎn)單的迴圈.

排序常式是該功能的主要部分. 有多種方法來實(shí)現(xiàn)表格中的資料的排序: - 保存資料到一個(gè)陣列並在表格中顯示陣列內(nèi)容, 用 asort() 函數(shù)對(duì)陣列進(jìn)行排序. 該方法有在些時(shí)候比較慢且限制了顯示的記錄數(shù)是陣列元素的最大值. - 為排序而用另一個(gè)選項(xiàng)重新查詢視圖或 SQL Pass-Through 結(jié)果集. 對(duì)於大的資料集來說這會(huì)比較慢, 但這是最簡(jiǎn)單的多列排序方法. - 使用在運(yùn)行時(shí)為結(jié)果集創(chuàng)建的索引或已存在的索引. 該方法最快因?yàn)樗槐4嫒魏沃虚g的結(jié)果到記憶體中且不要求再次從伺服器查詢資料, 但要求編寫一些代碼.

在本文中我們只討論使用索引的方法. 其他的方法也可以用替換排序常式中的資料集來實(shí)現(xiàn), 且該方法在排序後刷新表格.

在排序常式中使用了以下屬性:

Header.CurrentTag - 該屬性用於保存一個(gè)用於該列排序的標(biāo)識(shí)名.
Header.SortingExpression - 當(dāng)該屬性不為空時(shí), 它的運(yùn)算式將用於排序. 當(dāng)它為空時(shí), 排序常式將用列的 ControlSource 來創(chuàng)建排序運(yùn)算式.
Header.SortingTag - 如果該屬性非空, 該屬性中的索引標(biāo)識(shí)名將被認(rèn)為已存在於 record source 中. 為什麼要在該欄位已經(jīng)存在一個(gè)索引時(shí)還要在運(yùn)行時(shí)創(chuàng)建一個(gè)索引標(biāo)識(shí)呢?
最後兩個(gè)屬性給了排序編程以更多的選擇. 例如, 當(dāng)一個(gè)表格包含 First Name 和 Last Name 兩個(gè)列時(shí), 無論是哪一個(gè)列被單擊了, 按 First Name + Last Name 來排序是一個(gè)不錯(cuò)的想法. 當(dāng)然, 它可能要求額外的排序指示器和代碼, 當(dāng)默認(rèn)的排序常式不能適當(dāng)?shù)嘏判蛄袝r(shí)最好保持該屬性為空. 我們將在稍後討論多列排序.

排序常式檢查是否 SortignTag 或 CurrentTag 屬性非空, 並使用這些標(biāo)識(shí)來排序. CurrentTag 在列物件的生存期間不會(huì)被清除, 或在要求刪除該臨時(shí)標(biāo)識(shí)的特殊情況出現(xiàn)前不會(huì)被清除 (這會(huì)在設(shè)置 lEventSwitchOffSorting 屬性為 .T. 時(shí)發(fā)生). 這是可重用排序 - 我們只創(chuàng)建索引一次 (這會(huì)花一些時(shí)間), 保存創(chuàng)建的索引標(biāo)識(shí)名到該屬性中, 然後在下一次需要排序時(shí)只需重用它而不再花時(shí)間. 當(dāng) CurrentTag 屬性為空時(shí), 常式將為記錄源創(chuàng)建一個(gè)索引標(biāo)識(shí)並保存標(biāo)識(shí)名到該屬性中.

當(dāng)指定了索引運(yùn)算式時(shí), 常式將只使用它而不進(jìn)行驗(yàn)證 (但基本的錯(cuò)誤跟蹤仍然會(huì)進(jìn)行). 否則排序常式將試圖檢查所有的詳情來創(chuàng)建運(yùn)算式, 包括 NULL 值和 SET COLLATE 設(shè)置. 忘住最大索引運(yùn)算式長(zhǎng)度為 240, 但在 SET COLLATE 設(shè)置為非 "MACHINE" 時(shí)下降到 120. 索引不接受 NULL 值, 因此我們添加 NVL() 函數(shù)來假定沒有 null 值出現(xiàn)在結(jié)果集中. 對(duì)於長(zhǎng)字串和備註欄位我們用 PADR() 函數(shù). 最後, 當(dāng) ControlSource 值包含運(yùn)算式而不是欄位時(shí), 我們對(duì)字元值使用 PADR() 來保證運(yùn)算式結(jié)果的長(zhǎng)度總是相同的. 當(dāng)然, 我們不能對(duì)通用欄位排序.

對(duì)於不同的記錄源進(jìn)行索引還有一個(gè)重大區(qū)別. 對(duì)於視圖, 游標(biāo)和 SQL Pass-Through 游標(biāo), 我們可以創(chuàng)建結(jié)構(gòu)化索引標(biāo)識(shí)而不會(huì)有任何問題, 因?yàn)檫@些索引標(biāo)識(shí)產(chǎn)生的檔會(huì)在記錄源關(guān)閉時(shí)自動(dòng)從磁片刪除. 但是, 帶表緩存的視圖不能用 INDEX 命令創(chuàng)建索引. 我們可以快速地臨時(shí)切換到行緩存, 對(duì)視圖進(jìn)行索引, 然後再次設(shè)置緩存為表緩存. 但是如果視圖包含未提交的修改,改變緩存模式會(huì)造成錯(cuò)誤. 好了, 你可以告訴用戶該表單上的該表格在資料被修改且未保存時(shí)不能排序, 或者只在打開視圖且用各列頭的 SortingTag 值來創(chuàng)建索引, 然後設(shè)置緩存模式為表緩存.

為一個(gè)表創(chuàng)建結(jié)構(gòu)化索引不是一個(gè)好的辦法, 因?yàn)?ControlSource 中的運(yùn)算式可能包含對(duì)其他欄位的引用並有可能是關(guān)聯(lián)表中的欄位, 因此在我們創(chuàng)建索引後, 其他打開表的用戶將因此而發(fā)生錯(cuò)誤. 另外, 創(chuàng)建結(jié)構(gòu)化索引要求對(duì)表的獨(dú)佔(zhàn)使用權(quán). 對(duì)於這種情況, 我們使用保存在臨時(shí) CDX 檔中的非結(jié)構(gòu)化索引標(biāo)識(shí), 並以相同的名字作為索引標(biāo)識(shí). 這會(huì)產(chǎn)生一些其他可能的問題. 在資料工作期中包含了打開了非結(jié)構(gòu)化索引的別名時(shí), "BEGIN TRANSACTION" 不能啟動(dòng)事務(wù)處理. 在該命令前你必須關(guān)閉所有非結(jié)構(gòu)化索引. 另外, 最好是不要把包含上萬條記錄的資料集整個(gè)顯示給用戶. 準(zhǔn)備一個(gè)篩選條件並從大的表中只選擇一個(gè)小的資料子集到表格中. 這樣會(huì)使速度更快, 並沒有直接訪問表的問題, 如象這種情況下的非結(jié)構(gòu)化索引標(biāo)識(shí)問題. 另外, 通過網(wǎng)路訪問來索引大的表速度會(huì)相當(dāng)慢. 總之, 在你必須對(duì)表進(jìn)行排序時(shí), 盡可能地試著使用已經(jīng)存在於表中的索引標(biāo)識(shí), 然後關(guān)閉表中沒有索引標(biāo)識(shí)的列的排序. 另一個(gè)方案是 - 在開始事務(wù)處理前, 使用 Grid.SetAll("lEventSwitchOffSorting",.T.) 這樣表格將刪除可能存在的非結(jié)構(gòu)化索引. 這將要求特別關(guān)注從使用表格的表單中調(diào)用的子表單中的事務(wù)處理.

當(dāng)記錄源中包含 1000 以上的記錄時(shí), 我們用列頭的 DispSortingMessage 方法來顯示資訊, 在默認(rèn)情況下在排序期間顯示一個(gè)簡(jiǎn)單的 "WAIT WINDOW" 資訊. 你可能準(zhǔn)備用進(jìn)度條來顯示索引進(jìn)度或用一些其他方法來指明索引(排序)進(jìn)度. 另外, 也可以在索引運(yùn)算式中用自定義函數(shù)調(diào)用來顯示排序進(jìn)程: 函數(shù)將被每一個(gè)被索引的記錄調(diào)用, 函數(shù)中的代碼更新圖形化的進(jìn)度條 (在此情況下會(huì)稍稍降低索引速度).

當(dāng)以該方法對(duì)視圖, 游標(biāo)或 SQL Pass-Through 游標(biāo)進(jìn)行排序時(shí), 排序速度是令人驚異的. 當(dāng)表在本地磁片上時(shí)對(duì)表排序上很快的, 但通常通過網(wǎng)路訪問資料庫和它的表時(shí)速度是慢的. VFP SELECT 語句的結(jié)果游標(biāo)也會(huì)被排序. 但是, 對(duì)結(jié)果集使用 NOFILTER 選項(xiàng), 否則因?yàn)橹苯油ㄟ^網(wǎng)路訪問表, 索引時(shí)會(huì)變慢 (另外使用非結(jié)構(gòu)化索引不是一個(gè)好的辦法, 已在前面描述).

在排序後需要正確地刷新表格. 有這樣的情形, 改變排序的方向會(huì)給表格行顯示帶來強(qiáng)烈影響. 例如, 以昇冪排序, 將當(dāng)前排序結(jié)果的第一條記錄放在第一行, 然後再次按降冪排序. 通常表格在這種情況下只顯示單一的行 - 降冪排序的最後一行, 在它的下面是空白的. 用戶在任何情況下可以使用捲動(dòng)欄來使表格返回到好看的狀態(tài), 但是, 最奇怪的事情是, 表格常常在記錄源中的所有的行都可以顯示下時(shí)顯示該行為. 假設(shè)這樣的情形: 當(dāng)你看表資料源中所有的行都顯示在表格中, 單擊排序, 這時(shí)你只看到一行... 這通常會(huì)把用戶搞糊塗 - 為什麼在所有內(nèi)容都顯示得下時(shí)表格會(huì)捲動(dòng)? 要修正表格的該行為, 我們使用一個(gè)表格的額外的刷新: 設(shè)置記錄指標(biāo)到當(dāng)前排序結(jié)果中的第一條記錄, 然後再返回到當(dāng)前記錄. 這假定當(dāng)前記錄是在表的當(dāng)前的可視區(qū)內(nèi)或所有行都顯示在表格中. 這也可有更多的改進(jìn) - 當(dāng)記錄在新排序的記錄源的尾部時(shí), 用該方法顯示最後一條記錄(當(dāng)表格底部有一部分沒有記錄的空白區(qū)域時(shí)). 要這樣做, 萬一記錄是在表格的底部, 移動(dòng)記錄指標(biāo)到頂部, 再移動(dòng)它回到表格當(dāng)前可視區(qū)中的記錄數(shù)減半的位置, 然後設(shè)置記錄指標(biāo)回到希望的位置. 注意這只在記錄指標(biāo)在接近最後一條記錄時(shí)有用. 向後和向前移動(dòng)記錄指標(biāo)在特定的記錄源中可能會(huì)比較慢, 特別是有很多的記錄或設(shè)置了篩選的記錄源時(shí), 因此這不是一個(gè)好的通常情況下的辦法.

以上方法對(duì)一按單一的列排序是好的. 當(dāng)你想按多列進(jìn)行排序時(shí), 你需要從當(dāng)前加入到排序運(yùn)算式中的且排序狀態(tài)非零的所有列中收集所有的排序運(yùn)算式. 這是必需的, 因?yàn)橛脩艨赡荜P(guān)閉了該部分列的排序以保證其他列的正確排序. 在此情況下, 重使用已存在的索引標(biāo)識(shí)是非常困難的, 因?yàn)閱蝹€(gè)的列現(xiàn)在使用一些排序運(yùn)算式. 通常這要求轉(zhuǎn)換所有的運(yùn)算式為字元型並限制它們的長(zhǎng)度不大於 240 字元 (或在使用了 比較序列時(shí)為 120 字元). 另外, 很難實(shí)現(xiàn)此種情況下的某列要求降冪排列而其他列使用昇冪排列的要求. 這要求以降冪轉(zhuǎn)換運(yùn)算式的值. 對(duì)於不同的資料類型轉(zhuǎn)換是不同的. 例如, 數(shù)值型的值委容易用 "-" 操作符進(jìn)行轉(zhuǎn)換. 在我們的情況中將只處理字元型的值. 在此情況下我們需要改變 "A" 為 "z" 等等. 你可以用 chrtran() 函數(shù)來快速地比較兩個(gè)串. 例如:

"Control" > "Binding"
chrtran("Control", cAllChars, cAllReverseChars) < chrtran("Binding", cAllChars, cAllReverseChars)

cAllChars and cAllReverseChars are prepared by following way:

cAllChars = ""
cAllReverseChars = ""
for nCharIndex = 0 to 255
cAllChars = cAllChars + chr(nCharIndex)
cAllReverseChars =cAllReverseChars + chr(255-nCharIndex)
endfor


因此, 例如, 索引運(yùn)算式為兩列 Last name 和 First Name, 當(dāng)按 Last Name 昇冪排列且 First Name 降冪排列時(shí), 將看起來如下:
NVL(LastName,space(35)) + chrtran(NVL(FirstName,space(35)), cAllChars, cAllReverseChars)


在使用了比較序列的情況下(譯者注:即 SET COLLATE 設(shè)置為非 "MACHINE" 時(shí)), 問題會(huì)更複雜. 字元的排列與字元碼的序列不匹配, 因此串 cAllChars 和 cAllReverseChars 將以不同方式組成. 要改正這一問題, 創(chuàng)建一個(gè)只有一個(gè)字元欄位的表並用所有的字元填充它. 按使用的比較序列索引該表, 用已排序的表讀取所有字元到這些串中 - 這將保證這些串中的所有字元以正確的順序排列. 當(dāng)串字元不是單字節(jié)時(shí), 這些串變得太長(zhǎng)(用於索引運(yùn)算式中), 速度也因 chrtranC() 而慢下來. 好了, 按不同的排方向排序在使用視圖或查詢語句時(shí)可能用相當(dāng)簡(jiǎn)單的方法實(shí)現(xiàn) - SELECT 語句的 ORDER BY 子句允許為各排序元素指定排序方向.
以下是一小點(diǎn)按多列排序的不同模式.

所有列交叉的共同排序, 如, First Name + Last Name - 排序一個(gè)列造成按組中的所有列排序
修正: 單擊列來排序它. 單擊第二個(gè)列添加排序到第一個(gè)排序中. 在此後單擊第一個(gè)列會(huì)只剩下第二個(gè)列的排序.
級(jí)聯(lián): 兩個(gè)或更多的列加入到排序中, 當(dāng)一個(gè)列(主列)排序時(shí), 第二個(gè)列將與主列同時(shí)排序, 當(dāng)主列未排序時(shí), 單擊第二列致使主列自動(dòng)排序. 在三列情況下第三列的單擊致使第二列和第一列(主列)自動(dòng)排序. 有時(shí)複雜的模式允許一個(gè)以上的二級(jí)列.
動(dòng)態(tài): 與修正相似, 但所有列加入到一個(gè)單一的排序處理中, 因此用戶可以組織任何排序列的組合.
這可以用象 "cMultipleSortingColumns" 這樣額外的列頭屬性來實(shí)現(xiàn), 該屬性是一個(gè)用於組合排序的列索引列表的串, 級(jí)聯(lián)排序模式也要靠它來排序 (優(yōu)先排序號(hào) - 在排序中哪一個(gè)是主列, 哪一個(gè)是次列等等). 對(duì)於動(dòng)態(tài)排序不需要額外的屬性, 只需要另一個(gè)排序管理.
多列排序也要求特殊的方法來顯示排序指示符. 在此情況下要求一次顯示多個(gè)控制項(xiàng)來指出所有列的排序, 和當(dāng)前排序的列. 對(duì)於動(dòng)態(tài)排序指示器的數(shù)量將與表格中可以排序的列數(shù)匹配.

在示例中沒有如此複雜的多列排序的東西, 但是這裏討論的關(guān)於排序的大多數(shù)的東西.

表格排序標(biāo)記 (指示器)

有多種方式向用戶顯示表格是按哪一列排序的. 最簡(jiǎn)單的方法是改變列頭 caption 的背景色和前景色, 或者添加一個(gè)特殊的類似於箭頭的字元到列頭的 caption 來指出排序的列 (通常是字元 "^" (昇冪) 和 "v" (降冪). 這種做法看起來是可接受的但不是最好的. 可以用一個(gè)單獨(dú)的控制項(xiàng)來指出排序狀態(tài), 但這並不是一件容易的事. 好了, 在表格列頭的 header 上放置類似於箭頭的控制項(xiàng)就象放置控制項(xiàng)到表格單格上一樣容易 - 所有尺寸也適用於這裏 (參見 VFP 中的表格第二部分). 但是, 在許多應(yīng)用程式中你可以在列頭內(nèi)看見一個(gè)小的箭頭. 在 VFP 是難於實(shí)現(xiàn)這樣的東西, 並且這要求一些特殊的方法. 這裏有兩種方法.

第一種是用 "DEFINE WINDOW ... IN WINDOW ... NAME ..." 命令使用單獨(dú)的視窗定義並用 Windows API 函數(shù) SetWindowRgn() 來改變視窗形狀來使它看起來象一個(gè)箭頭, 並在箭頭下面使用一組線段控制項(xiàng)來顯示一些東西以模仿凹凸效果. 第二種方法是放置一個(gè)通常的透明容器控制項(xiàng)到表格面上並用一組線段來生成箭頭. 也可以用線段控制項(xiàng), 簡(jiǎn)單的在表單方法中在表頭上上繪出箭頭, 但這要求更多的努力來刷新這樣的排序指示器.

在表格列頭上放置一些東西的問題是非常困難的 - 表格列頭的自己重繪(redraw) 總是在其他 VFP 控制項(xiàng)的上面, 即使你用了 ZOrder 方法來放置你的控制項(xiàng)到所有其他控制項(xiàng)的上面. 有報(bào)告說在 W2K 作業(yè)系統(tǒng)上運(yùn)行 VFP 的應(yīng)用程式時(shí)不會(huì)出現(xiàn)該行為; 在 Windows NT 下它總是這樣. 一但特定的致使列頭刷新的事件發(fā)生時(shí), 表格列頭將在其他控制項(xiàng)的上面自己繪製. 以下是這些事件和要求刷新排序指示器的事件的清單:

列頭的 click (在任何情況下) 和 right click (奇怪, 但的確在右擊時(shí)表格列頭移動(dòng)到其他任何控制項(xiàng)).
表格的 refresh() 方法調(diào)用
改變當(dāng)前單元 (AfterRowColChange 事件)
表格移動(dòng)或重調(diào)大小或表格列頭被移動(dòng)或調(diào)整大小後
表格捲動(dòng)
列頭的高度調(diào)整後 (在表格的 MouseUp 事件中捕捉它)
表格失去焦點(diǎn)時(shí) - 放置表格到容器中來捕捉它
好了, 所有這些東西, 正如你可以看到的一樣, 除最後一個(gè)外其他都可以用簡(jiǎn)單的方法捕捉. 在表格列頭刷新後表格失去焦點(diǎn)不會(huì)激發(fā)任何表格事件. 它可以把表格放入一個(gè)容器中來捕捉它, 在一些情況下這樣做並不好, 尤其對(duì)於一般的表格類.

如果不會(huì)造成模式視窗的衝突, 視窗化的指示器是比較好的. 在一些情況下, 在模式表單中的另一個(gè)用 "DEFINE WINDOW ... IN WINDOW" 定義的窗口會(huì)造成許多方面的影響. 特別困難的是這樣的視窗不激發(fā)任何事件不能正確地被滑鼠單擊 (表格列頭的排序指示器需要滑鼠單擊). 對(duì)於無模式表單這樣的視窗獲得焦點(diǎn), 也不是好事情, 因?yàn)闀?huì)激發(fā)許多事件而且這需要特殊的處理.

在代碼中從多個(gè)地方刷新排序指示器, 使用了單獨(dú)的表格類的方法. 刷新計(jì)算正確的指示器位置並放置它. 它包含了刷新指示器的方法 - 指明不同的排序方向, 並適當(dāng)?shù)卦O(shè)置箭頭的顏色為列頭的背景色. 顏色計(jì)算使用顏色亮度屬性百分比來實(shí)現(xiàn)顯示線段來模仿凸起效果. 它也保證列頭的背景色很淺或很深時(shí)箭頭也是可見的.

只在運(yùn)行時(shí)使用指示器控制項(xiàng)是個(gè)不錯(cuò)的辦法. 在表格中, 它創(chuàng)建於 Init 事件並在表格的 Destroy 事件中清除. 對(duì)於多列排序你可能要使用一個(gè)陣列, 用於保存為排序列創(chuàng)建的所有指示器控制項(xiàng)的引用, 或在列頭中按排序引用為各列創(chuàng)建的指示器.

在雙擊時(shí)以資料寬度重調(diào)表格列寬

你可以在帶有表格或列表的應(yīng)用程式中看到一個(gè)有用的功能 - 雙擊列頭的 resize 區(qū)自動(dòng)調(diào)整列寬為列中顯示的資訊的寬度. 在示例中你可以看到在列頭的 DblClick 和 MouseUp 事件中我們?nèi)绾卧诹蓄^的 resize 區(qū)從其他的事件中區(qū)分開雙擊. 這會(huì)激發(fā) auto-resizing 演算法. 它從 controlsource 中得到欄位大小, 並用重複欄位大小次數(shù)的字元 &O&(它擁有字元的平均寬度)和 TxtWidth() 函數(shù)計(jì)算列的象素寬度. 特殊欄位如備註欄位和帶有長(zhǎng)空格的字元欄位. 另外, 列的 ControlSource 運(yùn)算式不能給出要顯示的最大字元數(shù). 不擴(kuò)展列寬為最大大小是一個(gè)好的辦法, 只擴(kuò)展為列中已有資訊的寬度 - 要適應(yīng)大多數(shù)行資訊而不佔(zhàn)用更多的列寬. 對(duì)於這種情況, 掃描當(dāng)前記錄位置附近的記錄並計(jì)算 TxtWidth() 函數(shù)返回的運(yùn)算式結(jié)果的最大值. 該值將成為列的寬度. 但是, 如果當(dāng)前記錄附近的所有行的值都是空值, 最好指定一個(gè)默認(rèn)的列寬, 如, 在列頭的 DefaultWidth 屬性中 (它也可用於一些其他場(chǎng)合).

表格訣竅

你可以用容器控制項(xiàng)在表格單格中顯示任何東西. 要刷新各行中的容器, 在 Dynamic* 屬性的運(yùn)算式中你可以使用函數(shù)調(diào)用. 函數(shù)將被各表格行調(diào)用, 因此在該函數(shù)代碼中你可以修改容器中的任何東西並刷新它. 在調(diào)用這樣的函數(shù)時(shí)記錄源中的記錄指標(biāo)是在正確的位置上.

要產(chǎn)生多行表頭, 在 VFP7 中使用列頭的 WordWrap 屬性. 在 VFP6 中多行表頭可以用複蓋在列頭上的標(biāo)籤代替, 或類似於表格列頭的容器控制項(xiàng) (可以在 Universal 的下載節(jié)中下載這樣的示例).

要為表格的不同部分顯示 tooltip, 在 timer 事件中用 MROW() 和 MCOL() 函數(shù)檢查滑鼠在特定時(shí)間內(nèi)是否沒有移動(dòng)並顯示你自己的控制項(xiàng). 表格的部分可以用表格的 GridHitTest 方法來檢查. (多行/奇特 ToolTip 控制項(xiàng)以該方式處理, 你可以為該用途修改它; 可以在 Universal 的下載節(jié)中下載這樣的示例).

表格警告

不要用表格列控制項(xiàng)的 "Value" 屬性進(jìn)行計(jì)算. 要從計(jì)算中得到資料, 直接訪問表格的別名. 這是因?yàn)樵诜腔顒?dòng)列中的控制項(xiàng)通常用於表格外觀的刷新, 並因此它的 Value 屬性對(duì)於當(dāng)前行的值是不正確的.

列頭總是表單上的其他 VFP 控制項(xiàng)上而不管它是被如何安排或放置的. 使用單獨(dú)的視窗控制項(xiàng)也有一些缺點(diǎn).

用 RecCount() 和當(dāng)前 RecNo() 計(jì)算縱向捲動(dòng)欄位置, 不會(huì)真正地顯示記錄數(shù). 當(dāng)記錄源是經(jīng)篩選的或包含大量有刪除標(biāo)記的記錄時(shí), 捲動(dòng)欄常常會(huì)以不正確的位置讓用戶糊塗. 在此情況下建議使用查詢或視圖.

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多