自繪畫的屬性編輯器 |
|
jackkcg
站務副站長 發表:891 回覆:1050 積分:848 註冊:2002-03-23 發送簡訊給我 |
此為轉貼資料 自繪畫的屬性編輯器
(http://www.tommstudio.com/newclub30/) --------------------------------------------------------------------------------
屬性編輯器對於大多數Delphi程式師來說無疑是很熟悉的,在物件編輯器的內核中有著大量的屬性編輯器,每個物件編輯器中的屬性都對應一個屬性編輯器類的實例。 Delphi5中提供了一些新的高級特性,使我們能夠定義新的屬性編輯器,?以有的屬性提供新的功能,或者設定和顯示新的控制項的新的屬性的顯示方法。在Delphi5以前,物件編輯器只能夠以文本的形式顯示屬性值。在Delphi 5中給屬性編輯器提供了新的特性,使我們能夠以任何形式顯示屬性的名稱和值,如下圖所示如果屬性有一個下拉清單,我們就可以?每一個列表項添加一個圖示。下面我們就來研究一下如何實現屬性編輯器的自繪畫的功能。 屬性編輯刷新器 所有的屬性編輯器都是從TpropertyEditor繼承下來的。我們可以?特定的屬性類型、屬性名或控制項註冊一個屬性編輯器。物件編輯器檢查每一個要顯示的屬性的名稱和類型,選擇合適的屬性編輯器類。然後它會創建這個類的一個實例(每個屬性對應一個實例)。當我們選擇了另一個控制項,物件編輯器會釋放全部的屬性編輯器物件,然後?新的控制項創建新的物件。 屬性編輯器可以決定如何顯示屬性的值以及用戶如何設定一個新的屬性值。比如,TintegerProperty調用IntToStr函數以字串的形式顯示整數值並用StrToInt函數來轉換用戶輸入的新值。 當用戶輸入了一個新的屬性值時,TcolorProperty同樣使用一個整型值來表示,但把整數解釋??色,並盡可能地映射?色值?一個名稱(如clBlack或clBtnFace) 。 一個屬性編輯器實現上述功能是通過重載TpropertyEditor的一個或多個方法來實現的。絕大多數的屬性編輯器需要重載GetValue方法,GetValue方法獲得屬性值的字串形式。以及SetValue方法,SetValue方法把一個字串轉化?屬性值。要想瞭解關於編寫屬性編輯器的進一步資訊,需要仔細研究DsgnIntf.pas文件(在Delphi5\Source\Toolsapi目錄下)以及Delphi 5 在線幫助(在"property editors, creating"部分裏)。 基礎步驟 要實現一個最基本的自繪畫屬性編輯器,我們只需要重載TpropertyEdiotr的PropDrawValue 方法。比如如前面圖中所見到的,TcolorProperty屬性重載了PropDrawValue方法在?色名前顯示一個對應於相應?色的彩色小方塊。?了理解如何使用PropDrawValue方法,我們?Tfont物件寫一個新的屬性編輯器,新的編輯器將會用當前字體名對應的字體來顯示Tfont對應的屬性。 Delphi本身已經提供了一個屬性編輯器TfontProperty,它在物件編輯器中添加了一個省略按鈕,用戶可以點擊按鈕調出標準的Windows字體選擇對話方塊來設定字體的屬性。我們可以直接從TfontProperty繼承新的編輯器,類的聲明如下: type
TVisualFontProperty = class(TFontProperty)
public
procedure PropDrawValue(Canvas: TCanvas;
const Rect: TRect; Selected: Boolean); override;
end; 當物件編輯器需要顯示屬性值的時候,IDE會調用PropDrawValue方法來畫屬性值。Delphi傳遞一個畫布物件(Canvas)及繪畫區域來供程式畫屬性值。Selected參數現在還沒用,我們可以忽略它。 注意:Delphi並不會?給定的繪畫區域設定剪裁區域,也就是說我們必須嚴格按照給定的區域繪畫,如果超出界限,會把別的屬性值給覆蓋掉。 TvisualFontProperty物件的唯一任務就是選擇相應於Font名字的字體來畫屬性值。它設定字體的名稱,樣式以及?色(當?色不同於背景色的時候),字體的大小顯示保留不動,以免使用非常大或非常小的字體大小畫值的時候會出現的問題。下面就是PropDrawValue的實現部分: // 替換乏味的Tfont屬性值的顯示方式,用選定的字體樣式
//和字體來畫相應的屬性值,用戶可能會選擇比較大的字體
//尺寸,所以這裏保留字體大小不動,只有當字體?色不同
//於背景色的時候,才用相應的?色畫,否則前景背景一樣
//的話就無法看到字體的屬性值了 procedure TVisualFontProperty.PropDrawValue(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
Font: TFont;
begin
Font := TFont(GetOrdValue);
if Font <> nil then begin
if ColorToRGB(Font.Color) <> ColorToRGB(clBtnFace) then
Canvas.Font.Color := Font.Color;
Canvas.Font.Name := Font.Name;
Canvas.Font.Style := Font.Style;
end;
inherited;
end; 另外我們重載GetValue方法來提供更多的資訊,比如字體名和大小。 function TVisualFontProperty.GetValue: string;
var
Font: TFont;
begin
Font := TFont(GetOrdValue);
if Font = nil then
Result := inherited GetValue
else
Result := Format('%s, %d', [Font.Name, Font.Size]);
end; 我們可以畫任何東西到畫布上,比如圖示和點陣圖的屬性編輯器是TgraphicProperty。它顯示把圖示屬性顯示?一個乏味的字串”TIcon”。 我們可以把圖示屬性顯示?對應的圖示,這樣的介面更加友好。這裏我們繼承一個TvisualGraphicProperty物件重載PropDrawValue來實現這一功能。 Tpicture屬性的情況也是類似的,所以我們用一個公用的過程DrawGraphic來實現,DraGraphic縮放圖形物件使之符合物件編輯器可用空間的大小,同時它維持原來的寬高比,縮放圖像?最小的可能的尺寸。對於圖示來說,由於Windows不能縮放圖示,所以DrawGraphic調用StretchIcon過程把圖示畫到點陣圖上,然後縮放點陣圖。下面是過程代碼: // Windows不能縮放圖示,所以如果圖示大小不匹配的話,
//把它畫到一個臨時的點陣圖上,然後縮放點陣圖。
procedure StretchIcon(Canvas: TCanvas;
const Rect: TRect; Icon: TIcon);
var
Bitmap: TBitmap;
begin
Bitmap := TBitmap.Create;
try
Bitmap.Height := Icon.Height;
Bitmap.Width := Icon.Width;
Bitmap.Canvas.Brush.Color := clBtnFace;
Bitmap.Canvas.FillRect(Rect);
Bitmap.Canvas.Draw(0, 0, Icon);
Canvas.StretchDraw(Rect, Bitmap);
finally
Bitmap.Free;
end;
end; procedure DrawGraphic(Canvas: TCanvas; const Rect: TRect;
Graphic: TGraphic; const Value: string);
var
R: TRect;
HeightRatio, WidthRatio: Single;
begin
Canvas.FillRect(Rect); //縮放圖像使其符合給定空間大小,
//同時保持圖像寬高比不變
HeightRatio := (Rect.Bottom - Rect.Top) / Graphic.Height;
WidthRatio := (Rect.Right - Rect.Left) / Graphic.Width;
R := Rect;
if HeightRatio < WidthRatio then
R.Right := R.Left Trunc(Graphic.Width * HeightRatio)
else
R.Bottom := R.Top Trunc(Graphic.Height * WidthRatio);
if (Graphic is TIcon) and
((HeightRatio > 1) or (WidthRatio > 1)) then
StretchIcon(Canvas, R, TIcon(Graphic))
else
Canvas.StretchDraw(R, Graphic); // 在圖像的右邊,讓繼承的編輯器畫缺省的文本,比如“Ticon“
R.Left := R.Right;
R.Right := Rect.Right;
R.Top := Rect.Top;
R.Bottom := Rect.Bottom;
Canvas.TextRect(R, R.Left 1, R.Top 1, Value);
end; 我們在DrawGraphic過程中寫了主要的代碼,所以PropDrawValue就顯得簡單多了,主要的作用是確保屬性有一個有效的圖形物件,如果沒有就調用繼承的方法來處理。代碼如下: procedure TVisualGraphicProperty.PropDrawValue(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
Graphic: TGraphic;
begin
Graphic := TGraphic(GetOrdValue);
if (Graphic = nil) or Graphic.Empty or
(Graphic.Height = 0) or (Graphic.Width = 0) then
inherited
else
DrawGraphic(Canvas, Rect, Graphic, GetVisualValue);
end; 自繪畫的名字 我們可以重載PropDrawName方法,它同PropDrawValue方法工作方式類似,只不過一個是畫值,一個是畫名稱。大多數屬性的名字不需要任何特殊的處理,對於BoldFace屬性的名字來說,把名字加粗可以便於用戶瞭解BoldFace屬性的情況。下面的代碼顯示了TboldComponentNameProperty類的如何實現PropDrawName方法的。 type
TBoldComponentNameProperty =
class(TComponentNameProperty)
public
procedure PropDrawName(Canvas: TCanvas;
const Rect: TRect; Selected: Boolean); override;
end; procedure TBoldComponentNameProperty.PropDrawName(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
Style: TFontStyles;
begin
Style := Canvas.Font.Style;
Canvas.Font.Style := Canvas.Font.Style [fsBold];
try
inherited;
finally //恢復字體的樣式以便Delphi正確的畫屬性值
Canvas.Font.Style := Style;
end;
end; 下拉清單 一個屬性編輯器可能會擁有一個下拉清單框,用戶可以通過選擇列表項來改變屬性值。Delphi 5使用了自繪畫的特性來改進Tcolor和Tcursor屬性的介面友好性,我們也可以作同樣的事情,通過重載ListDrawValue,ListMeasureHeight和ListMeasureWidth方法可以很容易的做到。下面是這幾個方法的聲明: procedure ListDrawValue(const Value: string;
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
procedure ListMeasureHeight(const Value: string;
Canvas: TCanvas; var Height: Integer);
procedure ListMeasureWidth(const Value: string;
Canvas: TCanvas; var Width: Integer); ListDrawValue方法類似於PropDrawValue方法,但它的Selected是有意義的,表示用戶已經選擇了這個列表項。Delphi會根據Selected參數自動設定畫布的?色?合適的值,所以通常情況下我們可以忽略這個參數。Value參數是要顯示的字串,Delphi調用GetValue方法來獲得這些字串, 在物件編輯器顯示列表框之前,它會調用ListMeasureHeight和ListMeasureWidth方法來獲得每個列表項的尺寸,我們可以設定Height和Width參數來獲得想要得到的高度和寬度。下拉清單框使用全部列表項中最大的尺寸,然後顯示相同區域大小的列表項。 當用戶滾動列表框時,Delphi調用ListDrawValue方法來畫心新的可見的列表項。用戶可能會前後滾動多次,如果列表項很多,每次重繪需要很多時間的話,我們應該建立一個臨時的點陣圖,把列表項先畫到點陣圖上,然後在ListDrawValue方法中快速顯示點陣圖。這實際上就是雙緩衝技術。 下面的例子是一個擴展的集合類型屬性,下拉清單顯示全部的集合元素,並在每個集合元素旁邊添加一個核取方塊。核取方塊是通過點陣圖來模仿的,屬性編輯器先取得核取方塊點陣圖,並在不同情況下顯示打叉和未打叉的點陣圖。總體變數Checked和Unchecked保存這兩個點陣圖 ?Tbitmap類型。下面的代碼顯示了TSetPropertyEx.類是如何實現自繪畫集合類型的: // 在下拉清單框的每一個列表項旁邊畫一個核取方塊
procedure TSetPropertyEx.ListDrawValue(const Value: string;
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
var
IsChecked: Boolean;
OrdValue: Integer;
begin
OrdValue := GetOrdValue;
IsChecked := GetEnumValue(EnumInfo, Value) in
TIntegerSet(OrdValue);
Canvas.FillRect(Rect);
Canvas.TextRect(Rect, Rect.Left Checked.Width 2,
Rect.Top 1, Value);
if IsChecked then
Canvas.Draw(Rect.Left 1, Rect.Top 1, Checked)
else
Canvas.Draw(Rect.Left 1, Rect.Top 1, Unchecked);
end; procedure TSetPropertyEx.ListMeasureHeight(
const Value: string; Canvas: TCanvas;
var Height: Integer);
begin
if Height < Checked.Height then
Height := Checked.Height;
end; procedure TSetPropertyEx.ListMeasureWidth(
const Value: string; Canvas: TCanvas;
var Width: Integer);
begin
Width := Width Checked.Width 2;
end; 類似于顯示集合元素,對於布耳類型的屬性我們也可以加一個核取方塊。下面我們要實現TBooleanPropertyEx 屬性編輯器對布耳類型進行了擴展,對於不同的布耳類型,比如ByteBool, WordBool和LongBool屬性的實現方式是類似的,當時需要不同的屬性編輯器。下面就是TbooleanPropertyEx的實現代碼,對於核取方塊如何相應消息,有點小問題,因?通常我們是希望單擊實現核取方塊切換狀態,Delphi不支援單擊,我們只好使用雙擊了(估計在Delphi 6中屬性編輯器可能會支援單擊),注意雙擊會調用屬性編輯器的Edit方法。對於集合元素或布耳屬性,雙擊可以切換屬性值。估計在Delphi 6中屬性編輯器可能會支援單擊。 //根據True或者False來畫一個核取方塊及布耳值的文本標簽
procedure DrawBoolCheckBox(Canvas: TCanvas;
const Rect: TRect; const Value: string);
begin
Canvas.FillRect(Rect);
Canvas.TextRect(Rect, Rect.Left Checked.Width 2,
Rect.Top 1, Value);
if Value = BooleanIdents[False] then
Canvas.Draw(Rect.Left 1, Rect.Top 1, UnChecked)
else
Canvas.Draw(Rect.Left 1, Rect.Top 1, Checked);
end; { TSetElementPropertyEx } // 每個列表項旁邊顯示一個核取方塊,用戶必須雙擊
//而不是單擊才能切換核取方塊狀態
procedure TSetElementPropertyEx.PropDrawValue(
Canvas: TCanvas; const Rect: TRect; Selected: Boolean);
begin
DrawBoolCheckBox(Canvas, Rect, Value);
end; { TBoolPropertyEx } // ?ByteBool, WordBool和LongBool類型顯示核取方塊
procedure TBoolPropertyEx.PropDrawValue(Canvas: TCanvas;
const Rect: TRect; Selected: Boolean);
begin
DrawBoolCheckBox(Canvas, Rect, Value);
end; 使用屬性編輯器 最後我們需要作的就是註冊這些新的屬性編輯器,大多數的編輯器比較容易註冊,但是新的集合類屬性編輯器存在一個問題,每一個集合都是一個獨立的類型,我們必須分別?每個集合類型註冊一遍屬性編輯器。幸運的是,Delphi有一個不?人知的特性就是允許?所有的集合類型註冊同一個屬性編輯器。同通常的?單獨一個類型註冊屬性編輯器不同的是,我們可以通過提供一個屬性映射函數來實現註冊,這個函數把物件和屬性資訊作?參數,然後返回屬性編輯器類或是nil。這種情況下,映射函數校驗屬性類型,並?所有屬性類型是tkSet的屬性返回新的集合屬性編輯器。下面是註冊過程的代碼: //?全部的集合屬性註冊一個統一的屬性編輯器
function SetMapper(Obj: TPersistent; PropInfo: PPropInfo):
TPropertyEditorClass;
begin
if PropInfo.PropType^.Kind = tkSet then
Result := TSetPropertyEx
else
Result := nil;
end; procedure Register;
begin
RegisterPropertyEditor(TypeInfo(TFont), nil, '',
TVisualFontProperty);
RegisterPropertyEditor(TypeInfo(TGraphic), nil, '',
TVisualGraphicProperty);
RegisterPropertyEditor(TypeInfo(TComponentName),
TComponent, 'Name', TBoldComponentNameProperty);
RegisterPropertyEditor(TypeInfo(Boolean), nil, '',
TBooleanPropertyEx);
RegisterPropertyEditor(TypeInfo(ByteBool), nil, '',
TBoolPropertyEx);
RegisterPropertyEditor(TypeInfo(WordBool), nil, '',
TBoolPropertyEx);
RegisterPropertyEditor(TypeInfo(LongBool), nil, '',
TBoolPropertyEx); RegisterPropertyMapper(SetMapper);
end; 寫完註冊代碼後,我們要作的只是把新的屬性編輯器添加到包中,然後在Delphi中安裝。關閉所有的表單,確保原來的屬性編輯器全部被釋放。新建一個表單,我們就可以看到如下圖所示的屬性編輯器了,看起來還是很漂亮的 其他新的屬性編輯器特性 這裏我們主要講了自繪畫的屬性編輯器,但Delphi 5還提供了許多其他的新特性,感興趣的朋友可以自己進行一下研究。 比如新增的GetVisualValue方法類似於GetValue方法,但它可以用來返回一個比簡單轉換的字串更有意義的描述字串(但不能用來編輯)。有的時候,我們可能想用更有意義的字串,而不是簡單的轉換來表示屬性值,這時我們通過重載GetVisualValue方法來返回一個用於顯示的字串,用GetValue方法來返回一個用於編輯的字串。 還有一個新的標誌paFullWidthName置位元的話,物件編輯器會完全顯示屬性名,而不會給屬性值留出空間。第一眼的印象是這好象是一個很奇怪的特性,但很多控制項都有類似於About的屬性(尤其是商業控制項),這些屬性(是PaDialog類型的)只是在左邊顯示一個帶省略號的按鈕。 點擊屬性編輯器只是顯示一個關於的對話方塊,它沒有什?特別有意義的值,這時paFullWidthName標誌就有用了,重載PropDrawName 方法我們可以把自己公司的Logo,顯示在屬性名的旁邊。 最後,我想要發發牢騷,Borland的這些新的特性從來就沒有很好的文檔(這點上borland比微軟可是差了好幾個數量級),所以要想研究的話,只能是多看看Delphi帶的源代碼了:(,不過不管怎?說,使用這些新的特性我們可以作出非常Cool的編輯介面來,這才是我們最關心的。
*********************************************************
哈哈&兵燹
最會的2大絕招 這個不會與那個也不會 哈哈哈 粉好 Delphi K.Top的K.Top分兩個字解釋Top代表尖端的意思,希望本討論區能提供Delphi的尖端新知
K.表Knowlege 知識,就是本站的標語:Open our mind to make knowledge together!
希望能大家敞開心胸,將知識寶庫結合一起
------
********************************************************** 哈哈&兵燹 最會的2大絕招 這個不會與那個也不會 哈哈哈 粉好 Delphi K.Top的K.Top分兩個字解釋Top代表尖端的意思,希望本討論區能提供Delphi的尖端新知 K.表Knowlege 知識,就是本站的標語:Open our mind |
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |