轉帖|其它|編輯:郝浩|2010-08-13 12:15:49.000|閱讀 850 次
概述:本文適用于 GUI 開發人員,這些開發人員要編寫可移植、可重用和速度更快的控件,用于看到量大且復雜的數據。當前存在一些常見的問題,如性能差,還存在一些可用性問題,如不能清楚地顯示大型數據集,所以用戶可以很容易地通過瀏覽本文進行分析。另外,程序數據結構和可視數據表示彼此之間的依賴性通常也變得非常強。因此,控件的專用性變得非常強,如果不進行重要修改,就不能在其他應用程序中使用。本文提供了一種方法,可用來設計復雜的控件,解決以上討論的問題。
# 界面/圖表報表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
本文適用于 GUI 開發人員,這些開發人員要編寫可移植、可重用和速度更快的控件,用于看到量大且復雜的數據。當前存在一些常見的問題,如性能差,還存在一些可用性問題,如不能清楚地顯示大型數據集,所以用戶可以很容易地通過瀏覽本文進行分析。另外,程序數據結構和可視數據表示彼此之間的依賴性通常也變得非常強。因此,控件的專用性變得非常強,如果不進行重要修改,就不能在其他應用程序中使用。本文提供了一種方法,可用來設計復雜的控件,解決以上討論的問題。本文中,將使用圖表查看器控件的一些示例來說明基本概念。這些概念還可以適用于多種多樣的其他控件。
定義
圖表代表一組對象及對象之間的關系。對象叫做節點。節點之間的關系叫做邊。因此,一個可視圖表就是一組節點(有或沒有標簽的正方形、長方形、圓等)和連接節點的邊(直線或曲線)。定義節點與邊的位置的算法叫做布局。
請注意,節點中可以包含其他節點和邊(子圖表)。如果某些節點的邊從這些節點起連接到任一個給定節點,那么這些節點叫做這個給定節點的父節點。如果某些節點的邊從任一個給定節點起連接到這些節點,那么這些節點叫做這個給定節點的子節點。
圖表控件中的常見問題
在很多應用程序中都使用圖表控件來顯示數據。如果要在不同的應用程序中使用相同的圖表控件,就必須提供一種方法來自定義該圖表控件。這種自定義一定不能影響應用程序的性能。以下是在不同應用程序中應用控件時可能遇到的各種問題列表:
圖表控件上必須提供滾動和縮放工具才能瀏覽大型圖表。下一部分說明如何靈活地解決這個問題而不降低任何性能。
自定義的外觀和用戶交互
通常您可以區別控件的兩個部分,即區別數據元素與核心部分,數據元素表示數據的各個部分,核心部分負責將數據組織為一個整體。圖表數據元素由節點和邊組成。當需要時,核心部分會使用數據元素的自定義版本來提供一些功能,如滾動、縮放、繪圖和事件處理。例如,當您單擊鼠標按鈕時,核心部分會定義這個事件發生的位置。如果事件發生在某個數據元素上,那么事件信息會傳遞到這個數據元素的處理程序,此外核心部分會處理事件本身。
圖表中有兩樣東西項目會隨著不同應用程序而改變,您應當進行自定義。一個是數據元素的外觀與行為;另一個是組織元素的方式。如果要很容易地自定義控件,就需要為這些東西定義界面,然后僅通過界面將這些東西用于控件。所以,如果要進行某種更改,只需要采用新的方式實現界面,無需更改控件中的代碼。這就是所謂的“策略”模式。
圖表控件采用了以下策略:
class INodeHandler
{
public:
// 繪制給定的節點
virtual void Draw(Node) = 0;
// 返回描述正方形的尺寸。此函數在布局中用來確定
// 節點位置,沒有交叉點。
virtual idvc::dsize GetSize(Node)= 0;
// 設置所有后續 Draw 調用函數中要使用的縮放系數。此函數
// 在實現縮放時由控件的核心部分使用。
virtual void SetZoomFactor(double f) {};
// 處理鼠標單擊事件
virtual ChangesType HandleClick(Node n, double inX, double inY,
int kstate, idvc::MouseButton Button);
// 處理工具提示事件
virtual ChangesType HandleOnTooltip(Node n, CGraphTooltipEvent* pEvent); }; // 結束 INodeHandler
}; // 結束 INodeHandler
class ILayout
{
public:
/// 這個函數應為給定節點內的所有節點
/// 生成新布局,并計算新布局的尺寸。內部節點的位置
/// 必須根據給定節點的左上角進行定義,
/// 假使給定節點的坐標是 (0,0)。
virtual void Make( Node ) = 0;
/// 與 Make 一樣,但是應使用以前布局
/// 的信息,然后嘗試保持已擺放節點
/// 的相對位置。
virtual void Update( Node ) = 0;
/// 這個函數在更改了已擁有節點的尺寸
/// 或跳過 ILayout 的參數時
/// 用于重新計算節點與邊的坐標。
/// 它假定以前調用過 Make 或 Update,而且
/// 所有已擁有節點的尺寸。與 Make 和 Update 不同,它
/// 不可遞歸。
virtual void Resize( Node ) = 0;
};
以上類定義了三種不同情況下的布局策略函數,即:
定義這種區別的主要目的是為了減少布局計算時間。如果向某個大型圖表中添加一個節點,就不需要重新計算整個圖表的布局。
快速繪制和事件處理
應解決的最后但并非不重要的問題是,如何快速對事件做出反應(至少是重新繪制事件)。當處理大型數據集時,控件應允許您快速地滾動和縮放內容。此處的主要問題是,事件處理和繪圖函數是由用戶定義的(通過上面描述的界面),而控件中的每個元素在繪圖和事件處理中可以采用自己的實現方式。因此,不能保證快速進行處理。不過,可以減少元素函數調用的數量。
void CContent::DrawContent(idvc::IPainter* p)
{
// 確定應重新繪制的無效長方形
idvcfrw::CInvalidRegion InvalidRegions(draw_rect, valid_rect);
for(int i = 0; i < InvalidRegions.size(); ++i)
{
// 得到對應于下一個無效區域的長方形
idvc::drect rect = InvalidRegions[i];
// 查找并重新繪制與無效長方形相交的節點
NodeSet nodes = graph->HitNodeTest(rect.left, rect.top, rect.right, rect.bottom);
for_each(nodes.begin(), nodes.end(), DrawNode(p,scale));
// 查找并重新繪制與無效長方形相交的邊
EdgeSet es = graph->HitEdgeTest(rect.left, rect.top, rect.right, rect.bottom);
for_each(es->Begin(), es->End(), DrawEdge(p,scale));
};
};
利用窗口事件中也擁有發生事件的點或長方形這一事實,可采用與繪圖類似的方式來組織事件處理(至少對于窗口事件而言)。這樣,控件可以確定節點與邊,這些節點與邊受任何給定事件的影響,而且只針對這些元素調用事件處理函數,因此大大地減少了處理時間。
數據加載
當處理大型數據集時,這些數據集通常存儲在某個外部數據源中。外部數據源可能隨著應用程序的不同而有所不同(文件、數據庫等)。因此,您需要使用一種機制來獨立地從外部數據源快速地加載數據。快速常常意味著加載部分數據,因為如果要真正地獲得大型數據集,無論如何都不能快速地執行加載。但是,控件一般只需要數據中的一小部分來進行處理,您應當只加載這一部分數據。
有兩種方法可用來實現部分加載。第一種類似于上面說明的快速繪圖和事件處理。您需要定義一個界面,這個界面與數據源無關,可用來加載數據。您應當嘗試實現以下方法,即可以用于定義需要加載的數據,而不是執行全部加載。然后可以定義應加載的數據元素,通過界面只針對這些元素調用加載函數。
不總是可以將元素定義為自動加載。另一種實現部分加載的方法是讓用戶輸入。這種情況下,用戶負責定義應何時加載數據,以及應加載哪些數據。
ChangesType PortNodeHandler::HandleClick(Node n, double inX, double inY,
int kstate, idvc::MouseButton Button)
{
ChangesType processed = ctNone;
idvc::dpoint pos = n->GetPosition();
idvc::dsize size = n->GetSize();
// 如果節點沒有嵌套的節點且使用鼠標左鍵對其單擊
if ( (n->GetOwned()->GetCount() == 0) && (Button == idvc::mbLeft) )
{
if (node_drawer.IsLeftPortClicked(n, inX, inY))
{
bool hide = ( CountAllParents(n) == CountVisibleParents(n) );
// 如果所有父節點可見
if( hide ) Fold(n, fdParents);
else Unfold(n, fdParents);
}
else if (node_drawer.IsRightPortClicked(n, inX, inY))
{
bool hide = ( CountAllChildren(n) == CountVisibleChildren(n) );
// 如果所有子節點可見
if( hide ) Fold(n, fdChildren);
else Unfold(n, fdChildren);
}
else
{
// 如果用戶單擊節點本身,則會選中它
SetFlag(n, Node::fSelected, !IsFlagSet(n, Node::fSelected));
};
processed = ctAll;
};
OnClick.fire(n, inX, inY, kstate, Button);
return processed;
};
結論
以下是創建可移植、快速控件中的主要概念:
下圖描述了如何將這些原則應用到圖表控件:

圖 1. 圖表設計
使用這些原則可獲得高度自定義、可移植且快速的控件,這些控件可處理大型數據集,也可進行調整以便用于很多應用程序。
本站文章除注明轉載外,均為本站原創或翻譯。歡迎任何形式的轉載,但請務必注明出處、不得修改原文相關鏈接,如果存在內容上的異議請郵件反饋至chenjj@ke049m.cn
文章轉載自:網絡轉載